From 7b688867cd8071cf9a4a77127058b474aaa41c7d Mon Sep 17 00:00:00 2001 From: Michael Salzmann Date: Fri, 11 Oct 2024 15:48:43 +0200 Subject: [PATCH] FCT-1188: filters: quick filters: create quick filters component (#2954) * feat: deprecate Tag's `type` property, introduce `tone` as replacement * chore(tag): add visual tests for new 'tone' prop * chore: add more docs * chore: changeset added * fix(tag): layout adjustments (font-size, spacings, gaps) * fix(tag): disable text-selection (cause ui-component) * fix(tag): fix html semantics + add missing focus outlines * chore(quick-filters): add visual test * test(quick-filters): add unit-test * fix(tag): display as regular flex-element again, to prevent side-effects * fix: fix visual test * fix(tag): fix visual test * fix: visualtest for QuickFilters * fix: remove Tag reference from QuickFilters test * fix: Update packages/components/quick-filters/src/quick-filters.stories.tsx Co-authored-by: Byron Wall <87667330+ByronDWall@users.noreply.github.com> * fix(tag): ux review fixes (revert font-size change, add missing gap between drag-icon & label) * fix(tag): add not-allowed cursor when isDisabled is true * fix(quick-filters): remove leftover comment * fix(quick-filters): update handleItemClick in QuickFilters test * fix(quick-filters): update tone prop in QuickFilters component --------- Co-authored-by: Byron Wall <87667330+ByronDWall@users.noreply.github.com> --- .changeset/spicy-candles-love.md | 5 ++ packages/components/quick-filters/README.md | 63 ++++++++++++-- .../quick-filters/docs/description.md | 2 - .../quick-filters/docs/usage-example.js | 31 ++++++- .../components/quick-filters/package.json | 2 + .../src/quick-filters.readme.mdx | 2 +- .../quick-filters/src/quick-filters.spec.tsx | 26 ++++-- .../src/quick-filters.stories.tsx | 53 ++++++++++-- .../quick-filters/src/quick-filters.tsx | 52 +++++++++++- .../src/quick-filters.visualroute.jsx | 19 ++++- .../src/quick-filters.visualspec.js | 8 +- packages/components/tag/README.md | 23 +++--- packages/components/tag/src/tag-body.tsx | 33 ++++---- packages/components/tag/src/tag.stories.tsx | 55 +++++++++---- packages/components/tag/src/tag.styles.ts | 75 +++++++++++++++++ packages/components/tag/src/tag.tsx | 82 ++++++++++++------- .../components/tag/src/tag.visualroute.jsx | 19 +++-- yarn.lock | 4 +- 18 files changed, 433 insertions(+), 121 deletions(-) create mode 100644 .changeset/spicy-candles-love.md create mode 100644 packages/components/tag/src/tag.styles.ts diff --git a/.changeset/spicy-candles-love.md b/.changeset/spicy-candles-love.md new file mode 100644 index 0000000000..c1c87af40c --- /dev/null +++ b/.changeset/spicy-candles-love.md @@ -0,0 +1,5 @@ +--- +'@commercetools-uikit/tag': minor +--- + +Deprecated 'type' property in favor of 'tone' for better clarity and flexibility. New tone 'surface' added. diff --git a/packages/components/quick-filters/README.md b/packages/components/quick-filters/README.md index 0c7990a60d..bbba3ed99c 100644 --- a/packages/components/quick-filters/README.md +++ b/packages/components/quick-filters/README.md @@ -7,8 +7,6 @@ The `QuickFilters` component displays a selection of `Tag` components that represent the available filter actions. -This description is a stub and shold be expanded as development continues. - ## Installation ``` @@ -32,16 +30,63 @@ npm --save install react ## Usage ```jsx +import { useState } from 'react'; import QuickFilters from '@commercetools-uikit/quick-filters'; -/**TODO: EXPAND THIS */ -const Example = () => ; - -export default Example; +const App = () => { + const [items, setItems] = useState([ + { + id: '1', + label: 'Accepted', + isActive: true, + }, + { + id: '2', + label: 'Rejected', + isActive: false, + }, + ]); + + const onItemClick = (clickedItem) => { + const updatedItems = items.map((item) => { + return { + ...item, + isActive: item.id === clickedItem.id ? !item.isActive : false, + }; + }); + setItems(updatedItems); + }; + + return ; +}; + +export default App; ``` ## Properties -| Props | Type | Required | Default | Description | -| ------- | -------- | :------: | ------- | -------------------- | -| `label` | `string` | ✅ | | This is a stub prop! | +| Props | Type | Required | Default | Description | +| ------------- | ------------------------------------------------------------------- | :------: | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `items` | `Array: TQuickFiltersItem[]`
[See signature.](#signature-items) | ✅ | | collection of quick filter items@param item.id unique identifier for the item. @param item.label label to display @param item.isActive the current active state of the item | +| `onItemClick` | `Function`
[See signature.](#signature-onItemClick) | ✅ | | callback fn, executed when an item is clicked | + +## Signatures + +### Signature `items` + +```ts +{ + /** unique identifier for the item. */ + id: string; + /* label to display */ + label: string; + /* the current active state of the item */ + isActive: boolean; +} +``` + +### Signature `onItemClick` + +```ts +(item: TQuickFiltersItem) => void +``` diff --git a/packages/components/quick-filters/docs/description.md b/packages/components/quick-filters/docs/description.md index 80cb874bc0..6718e8ade8 100644 --- a/packages/components/quick-filters/docs/description.md +++ b/packages/components/quick-filters/docs/description.md @@ -1,3 +1 @@ The `QuickFilters` component displays a selection of `Tag` components that represent the available filter actions. - -This description is a stub and shold be expanded as development continues. diff --git a/packages/components/quick-filters/docs/usage-example.js b/packages/components/quick-filters/docs/usage-example.js index c7ea15221d..8a3413146d 100644 --- a/packages/components/quick-filters/docs/usage-example.js +++ b/packages/components/quick-filters/docs/usage-example.js @@ -1,6 +1,31 @@ +import { useState } from 'react'; import QuickFilters from '@commercetools-uikit/quick-filters'; -/**TODO: EXPAND THIS */ -const Example = () => ; +const App = () => { + const [items, setItems] = useState([ + { + id: '1', + label: 'Accepted', + isActive: true, + }, + { + id: '2', + label: 'Rejected', + isActive: false, + }, + ]); -export default Example; + const onItemClick = (clickedItem) => { + const updatedItems = items.map((item) => { + return { + ...item, + isActive: item.id === clickedItem.id ? !item.isActive : false, + }; + }); + setItems(updatedItems); + }; + + return ; +}; + +export default App; diff --git a/packages/components/quick-filters/package.json b/packages/components/quick-filters/package.json index fd54c8b0ab..54fbdf95b5 100644 --- a/packages/components/quick-filters/package.json +++ b/packages/components/quick-filters/package.json @@ -21,6 +21,8 @@ "dependencies": { "@babel/runtime": "^7.20.13", "@babel/runtime-corejs3": "^7.20.13", + "@commercetools-uikit/design-system": "workspace:^", + "@commercetools-uikit/tag": "workspace:^", "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", "prop-types": "15.8.1", diff --git a/packages/components/quick-filters/src/quick-filters.readme.mdx b/packages/components/quick-filters/src/quick-filters.readme.mdx index 88ce506a15..2e129f5561 100644 --- a/packages/components/quick-filters/src/quick-filters.readme.mdx +++ b/packages/components/quick-filters/src/quick-filters.readme.mdx @@ -1,6 +1,6 @@ import { Meta, Markdown } from '@storybook/blocks'; import ReadMe from './../README.md?raw' - + {ReadMe} diff --git a/packages/components/quick-filters/src/quick-filters.spec.tsx b/packages/components/quick-filters/src/quick-filters.spec.tsx index 456c7a5536..a44dda649d 100644 --- a/packages/components/quick-filters/src/quick-filters.spec.tsx +++ b/packages/components/quick-filters/src/quick-filters.spec.tsx @@ -1,12 +1,26 @@ import { screen, render } from '../../../../test/test-utils'; import QuickFilters from './quick-filters'; -/** - * THIS IS A PLACEHOLDER, PLEASE UPDATE IT - */ describe('QuickFilters', () => { - it('should render the quick-filters', async () => { - await render(); - await screen.findByText('quickfilters'); + it('should render items and propagate item-clicks', async () => { + const items = [ + { id: '1', label: 'Foo', isActive: true }, + { id: '2', label: 'Bar', isActive: false }, + ]; + + const handleItemClick = jest.fn(); + + await render(); + + const button1 = (await screen.findByText('Foo')).closest('button'); + const button2 = (await screen.findByText('Bar')).closest('button'); + + expect(button1).toBeInTheDocument(); + expect(button2).toBeInTheDocument(); + + button2?.click(); + expect(handleItemClick).toHaveBeenCalledWith(items[1]); + button1?.click(); + expect(handleItemClick).toHaveBeenCalledWith(items[0]); }); }); diff --git a/packages/components/quick-filters/src/quick-filters.stories.tsx b/packages/components/quick-filters/src/quick-filters.stories.tsx index 5b59f011a1..3b4bd16d91 100644 --- a/packages/components/quick-filters/src/quick-filters.stories.tsx +++ b/packages/components/quick-filters/src/quick-filters.stories.tsx @@ -1,22 +1,57 @@ import type { Meta, StoryObj } from '@storybook/react'; -import QuickFilters from './quick-filters'; +import QuickFilters, { + TQuickFiltersItem, + TQuickFiltersProps, +} from './quick-filters'; +import { useState } from 'react'; const meta: Meta = { title: 'components/QuickFilters', component: QuickFilters, - tags: ['local-dev'], - argTypes: { - label: { - control: 'text', - }, - }, + //tags: ['local-dev'], }; export default meta; type Story = StoryObj; +/** The `QuickFilters` component displays a selection of `Tag` components that represent the available filter actions. */ export const BasicExample: Story = { - args: { - label: 'A component for applying static filter controls', + render: (args) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const [items, setItems] = useState([ + { + id: '1', + label: 'Accepted (234)', + isActive: true, + }, + { + id: '2', + label: 'Rejected (25)', + isActive: false, + }, + { + id: '3', + label: 'Canceled (3)', + isActive: false, + }, + { + id: '4', + label: 'Drafts (58)', + isActive: false, + }, + ]); + + const onItemClick = (clickedItem: TQuickFiltersItem) => { + const updatedItems = items.map((item) => { + return { + ...item, + isActive: item.id === clickedItem.id ? !item.isActive : false, + }; + }); + setItems(updatedItems); + }; + + return ; }, + args: {}, }; diff --git a/packages/components/quick-filters/src/quick-filters.tsx b/packages/components/quick-filters/src/quick-filters.tsx index 91e0e40882..5d5fb88790 100644 --- a/packages/components/quick-filters/src/quick-filters.tsx +++ b/packages/components/quick-filters/src/quick-filters.tsx @@ -1,12 +1,56 @@ +import Tag from '@commercetools-uikit/tag'; +import { css } from '@emotion/react'; +import { designTokens } from '@commercetools-uikit/design-system'; + +export type TQuickFiltersItem = { + /** unique identifier for the item. */ + id: string; + /* label to display */ + label: string; + /* the current active state of the item */ + isActive: boolean; +}; + export type TQuickFiltersProps = { /** - * This is a stub prop! + * collection of quick filter items + * + * @param item.id unique identifier for the item. + * @param item.label label to display + * @param item.isActive the current active state of the item */ - label: string; + items: TQuickFiltersItem[]; + + /** callback fn, executed when an item is clicked */ + onItemClick: (item: TQuickFiltersItem) => void; }; -function QuickFilters(props: TQuickFiltersProps) { - return
{props.label}
; +const listStyles = css` + all: unset; + display: inline-flex; + flex-wrap: wrap; + gap: ${designTokens.spacing20}; +`; + +const listItemStyles = css` + all: unset; +`; + +function QuickFilters({ items, onItemClick }: TQuickFiltersProps) { + return ( +
    + {items.map((item) => ( +
  • + onItemClick(item)} + > + {item.label} + +
  • + ))} +
+ ); } export default QuickFilters; diff --git a/packages/components/quick-filters/src/quick-filters.visualroute.jsx b/packages/components/quick-filters/src/quick-filters.visualroute.jsx index 2727b1e430..5acd0be4c1 100644 --- a/packages/components/quick-filters/src/quick-filters.visualroute.jsx +++ b/packages/components/quick-filters/src/quick-filters.visualroute.jsx @@ -1,11 +1,26 @@ import { Suite, Spec } from '../../../../test/percy'; +import QuickFilters from './quick-filters'; export const routePath = '/quick-filters'; export const component = () => ( - - {/**ACTUAL TEST GOES HERE */} + + {}} + /> ); diff --git a/packages/components/quick-filters/src/quick-filters.visualspec.js b/packages/components/quick-filters/src/quick-filters.visualspec.js index f86dac56fa..09d0dcae31 100644 --- a/packages/components/quick-filters/src/quick-filters.visualspec.js +++ b/packages/components/quick-filters/src/quick-filters.visualspec.js @@ -1,12 +1,12 @@ import percySnapshot from '@percy/puppeteer'; -import { getDocument, queries } from 'pptr-testing-library'; -describe('FiltersList', () => { +describe('QuickFilters', () => { beforeAll(async () => { await page.goto(`${globalThis.HOST}/quick-filters`); }); - it.skip('Default', async () => { - // THIS IS A STUB + it('Default', async () => { + await page.waitForSelector('text/Renders an active + inactive item'); + await percySnapshot(page, 'QuickFilters'); }); }); diff --git a/packages/components/tag/README.md b/packages/components/tag/README.md index 25b75bce70..987ab23870 100644 --- a/packages/components/tag/README.md +++ b/packages/components/tag/README.md @@ -55,17 +55,18 @@ export default Example; ## Properties -| Props | Type | Required | Default | Description | -| ---------------------- | ----------------------------------------------------------------------------------------------------------- | :------: | ---------- | ------------------------------------------------------------------------------------- | -| `type` | `union`
Possible values:
`'normal' , 'warning'` | | `'normal'` | Indicates color scheme of the tag. | -| `styles` | `Record` | | | Styles object that is spread into the tag body. | -| `to` | `union`
Possible values:
`string , LocationDescriptor` | | | Link of the tag when not disabled | -| `isDisabled` | `boolean` | | `false` | Disable the tag element along with the option to remove it. | -| `isDraggable` | `boolean` | | `false` | Adds the draggable icon on the left side. | -| `onRemove` | `Function`
[See signature.](#signature-onRemove) | | | Called when remove button is clicked. | -| `onClick` | `Function`
[See signature.](#signature-onClick) | | | Called when tag element is clicked. This is not called when remove button is clicked. | -| `horizontalConstraint` | `union`
Possible values:
`, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 'scale', 'auto'` | | `'scale'` | Horizontal size limit of the input field. | -| `children` | `ReactNode` | ✅ | | Content rendered within the tag | +| Props | Type | Required | Default | Description | +| ---------------------- | ----------------------------------------------------------------------------------------------------------- | :------: | ----------- | ------------------------------------------------------------------------------------- | +| `type` | `union`
Possible values:
`'normal' , 'warning'` | | | Indicates color scheme of the tag. @deprecated use `tone` instead | +| `styles` | `Record` | | | Styles object that is spread into the tag body. | +| `to` | `union`
Possible values:
`string , LocationDescriptor` | | | Link of the tag when not disabled | +| `isDisabled` | `boolean` | | `false` | Disable the tag element along with the option to remove it. | +| `isDraggable` | `boolean` | | `false` | Adds the draggable icon on the left side. | +| `onRemove` | `Function`
[See signature.](#signature-onRemove) | | | Called when remove button is clicked. | +| `onClick` | `Function`
[See signature.](#signature-onClick) | | | Called when tag element is clicked. This is not called when remove button is clicked. | +| `horizontalConstraint` | `union`
Possible values:
`, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 'scale', 'auto'` | | `'scale'` | Horizontal size limit of the input field. | +| `children` | `ReactNode` | ✅ | | Content rendered within the tag | +| `tone` | `union`
Possible values:
`'primary' , 'warning' , 'surface'` | | `'primary'` | Indicates the color scheme of the tag. | ## Signatures diff --git a/packages/components/tag/src/tag-body.tsx b/packages/components/tag/src/tag-body.tsx index ebbe0406df..40b40d8eb7 100644 --- a/packages/components/tag/src/tag-body.tsx +++ b/packages/components/tag/src/tag-body.tsx @@ -1,18 +1,15 @@ import type { TTagProps } from './tag'; -import { ReactNode } from 'react'; +import { ElementType, ReactNode } from 'react'; import styled from '@emotion/styled'; import { css } from '@emotion/react'; -import { Link } from 'react-router-dom'; import { designTokens } from '@commercetools-uikit/design-system'; import Text from '@commercetools-uikit/text'; import { DragIcon } from '@commercetools-uikit/icons'; -import Spacings from '@commercetools-uikit/spacings'; export type TTagBodyProps = { to?: TTagProps['to']; - as?: typeof Link; - type?: TTagProps['type']; + as?: ElementType; onClick?: TTagProps['onClick']; onRemove?: TTagProps['onRemove']; isDisabled?: boolean; @@ -21,8 +18,7 @@ export type TTagBodyProps = { styles?: TTagProps['styles']; }; -const defaultProps: Pick = { - type: 'normal', +const defaultProps: Pick = { isDisabled: false, isDraggable: false, }; @@ -37,10 +33,17 @@ const getTextDetailColor = (isDisabled: TTagBodyProps['isDisabled']) => { const getContentWrapperStyles = (props: TTagBodyProps) => { return css` + all: unset; position: relative; display: flex; align-items: center; - padding: ${designTokens.spacing05} ${designTokens.spacing25}; + gap: ${designTokens.spacing20}; + padding-top: ${designTokens.spacing10}; + padding-bottom: ${designTokens.spacing10}; + padding-left: ${designTokens.spacing25}; + padding-right: ${!props.isDisabled && props.onRemove + ? '0' + : designTokens.spacing25}; white-space: normal; text-align: left; min-width: 0; @@ -85,14 +88,12 @@ const TagBody = (props: TTagBodyProps) => { ]} onClick={props.isDisabled ? undefined : props.onClick} > - - {props.isDraggable && !props.isDisabled ? ( - - ) : null} - - {props.children} - - + {props.isDraggable && !props.isDisabled ? ( + + ) : null} + + {props.children} + ); }; diff --git a/packages/components/tag/src/tag.stories.tsx b/packages/components/tag/src/tag.stories.tsx index d509eb4bd3..7f81ec679d 100644 --- a/packages/components/tag/src/tag.stories.tsx +++ b/packages/components/tag/src/tag.stories.tsx @@ -1,6 +1,6 @@ import { BrowserRouter as Router } from 'react-router-dom'; import type { Meta, StoryObj } from '@storybook/react'; -import Tag, { TTagProps } from './tag'; +import Tag from './tag'; const meta: Meta = { title: 'components/Tags/Tag', @@ -10,19 +10,46 @@ export default meta; type Story = StoryObj; -/** Displays a single ``.*/ -export const BasicExample: Story = (args: TTagProps) => { - return ( - - - - ); +/** Display a plain tag, unlinked and without any actions attached */ +export const BasicExample: Story = { + render: (args) => { + return ( + + + + ); + }, + args: { + tone: 'primary', + children: 'Ice Cream', + onRemove: undefined, + onClick: undefined, + }, }; -BasicExample.args = { - type: 'normal', - isDisabled: false, - to: '/project-key/products/icecream', - onRemove: () => alert('Remove tag request'), - children: 'Ice Cream', +/** displays the tag as a react-router link, (no hover effects) */ +export const LinkedTag: Story = { + ...BasicExample, + args: { + ...BasicExample.args, + to: '/ice-cream', + }, +}; + +/** a tag with a click-action attached*/ +export const ClickableTag: Story = { + ...BasicExample, + args: { + ...BasicExample.args, + onClick: () => alert('You clicked me!'), + }, +}; + +/** supply an `onRemove` handler to display an `x`-button*/ +export const RemovableTag: Story = { + ...BasicExample, + args: { + ...BasicExample.args, + onRemove: () => alert('You clicked remove!'), + }, }; diff --git a/packages/components/tag/src/tag.styles.ts b/packages/components/tag/src/tag.styles.ts new file mode 100644 index 0000000000..39e8cfee50 --- /dev/null +++ b/packages/components/tag/src/tag.styles.ts @@ -0,0 +1,75 @@ +import { css } from '@emotion/react'; +import { TTagProps } from './tag'; +import { designTokens } from '@commercetools-uikit/design-system'; + +type visualVariantType = Exclude< + 'disabled' | TTagProps['type'] | TTagProps['tone'], + undefined +>; + +/** + * Returns the required tone styles for the tag based on the props. + */ +export const getToneStyles = ({ + isDisabled, + type, + tone, + onClick, +}: TTagProps) => { + let variant: visualVariantType = isDisabled ? 'disabled' : type || tone; + + // map deprecated 'normal' type to 'primary' tone + if (variant === 'normal') variant = 'primary'; + + const variants: Record< + Exclude, + { + textColor: string; + borderColor: string; + bgColor: string; + bgColorHover: string; + } + > = { + primary: { + textColor: designTokens.colorPrimary20, + borderColor: designTokens.colorPrimary90, + bgColor: designTokens.colorPrimary95, + bgColorHover: designTokens.colorPrimary90, + }, + warning: { + textColor: designTokens.colorSolid, + borderColor: designTokens.colorWarning85, + bgColor: designTokens.colorWarning95, + bgColorHover: designTokens.colorWarning85, + }, + surface: { + textColor: designTokens.colorNeutral40, + borderColor: designTokens.colorNeutral85, + bgColor: designTokens.colorSurface, + bgColorHover: designTokens.colorNeutral98, + }, + disabled: { + textColor: designTokens.colorNeutral60, + borderColor: designTokens.colorNeutral, + bgColor: designTokens.colorNeutral95, + bgColorHover: designTokens.colorNeutral95, + }, + }; + + const { textColor, borderColor, bgColor, bgColorHover } = variants[variant]; + + return css(` + color: ${textColor}; + background-color: ${bgColor}; + border-color: ${borderColor}; + + ${ + onClick && + ` + &:hover { + background-color: ${bgColorHover}; + } + ` + } + `); +}; diff --git a/packages/components/tag/src/tag.tsx b/packages/components/tag/src/tag.tsx index aaad874a71..68ab30f95a 100644 --- a/packages/components/tag/src/tag.tsx +++ b/packages/components/tag/src/tag.tsx @@ -1,5 +1,5 @@ import type { LocationDescriptor } from 'history'; -import { ReactNode, MouseEvent, KeyboardEvent } from 'react'; +import { ReactNode, MouseEvent, KeyboardEvent, ElementType } from 'react'; import { css, type SerializedStyles } from '@emotion/react'; import { Link } from 'react-router-dom'; import { designTokens } from '@commercetools-uikit/design-system'; @@ -7,10 +7,12 @@ import Constraints from '@commercetools-uikit/constraints'; import AccessibleButton from '@commercetools-uikit/accessible-button'; import { CloseBoldIcon } from '@commercetools-uikit/icons'; import TagBody from './tag-body'; +import { getToneStyles } from './tag.styles'; export type TTagProps = { /** * Indicates color scheme of the tag. + * @deprecated use `tone` instead */ type?: 'normal' | 'warning'; /** @@ -40,7 +42,7 @@ export type TTagProps = { */ onClick?: (event: MouseEvent) => void; /** - * Horizontal size limit of the input field. + * Horizontal size limit of the tag. */ horizontalConstraint?: | 1 @@ -65,60 +67,72 @@ export type TTagProps = { * Content rendered within the tag */ children: ReactNode; + /** + * Indicates the color scheme of the tag. + */ + tone: 'primary' | 'warning' | 'surface'; }; const defaultProps: Pick< TTagProps, - 'type' | 'isDisabled' | 'isDraggable' | 'horizontalConstraint' + 'tone' | 'isDisabled' | 'isDraggable' | 'horizontalConstraint' > = { - type: 'normal', + tone: 'primary', isDisabled: false, isDraggable: false, horizontalConstraint: 'scale', }; const Tag = (props: TTagProps) => { - const linkProps = - props.to && !props.isDisabled ? { as: Link, to: props.to } : {}; + let tagBodyProps; + + switch (true) { + case props.isDisabled: + tagBodyProps = {}; + break; + case Boolean(props.to): + tagBodyProps = { as: Link, to: props.to }; + break; + case Boolean(props.onClick): + tagBodyProps = { as: 'button' as ElementType }; + break; + default: + tagBodyProps = {}; + } + + const isInteractive = + !props.isDisabled && (Boolean(props.onClick) || Boolean(props.to)); + return (
{ onClick={props.onRemove} css={[ css` - padding: 0 ${designTokens.spacing25}; - display: flex; align-items: center; + border-radius: ${designTokens.borderRadius20}; + display: flex; fill: ${designTokens.colorNeutral40}; + padding: 0 ${designTokens.spacing25}; + &:disabled { fill: ${designTokens.colorNeutral60}; } + + &:focus-visible { + outline: ${designTokens.borderWidth2} solid + ${designTokens.colorPrimary40}; + outline-offset: calc(-1 * ${designTokens.borderWidth2}); + } `, ]} > diff --git a/packages/components/tag/src/tag.visualroute.jsx b/packages/components/tag/src/tag.visualroute.jsx index 87903edcf2..22e0d2a9da 100644 --- a/packages/components/tag/src/tag.visualroute.jsx +++ b/packages/components/tag/src/tag.visualroute.jsx @@ -199,9 +199,7 @@ export const component = () => ( {longText} - + {examplesLong.map((tag, index) => ( {}} key={index}> @@ -210,9 +208,7 @@ export const component = () => ( ))} - + {examplesLong.map((tag, index) => ( @@ -221,9 +217,7 @@ export const component = () => ( ))} - + {examplesLong.map((tag, index) => ( @@ -234,5 +228,12 @@ export const component = () => ( + +
+ primary tone + warning tone + surface tone +
+
); diff --git a/yarn.lock b/yarn.lock index 602cf09abb..d2fc02918b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4023,6 +4023,8 @@ __metadata: dependencies: "@babel/runtime": ^7.20.13 "@babel/runtime-corejs3": ^7.20.13 + "@commercetools-uikit/design-system": "workspace:^" + "@commercetools-uikit/tag": "workspace:^" "@emotion/react": ^11.10.5 "@emotion/styled": ^11.10.5 prop-types: 15.8.1 @@ -4478,7 +4480,7 @@ __metadata: languageName: unknown linkType: soft -"@commercetools-uikit/tag@19.12.1, @commercetools-uikit/tag@workspace:packages/components/tag": +"@commercetools-uikit/tag@19.12.1, @commercetools-uikit/tag@workspace:^, @commercetools-uikit/tag@workspace:packages/components/tag": version: 0.0.0-use.local resolution: "@commercetools-uikit/tag@workspace:packages/components/tag" dependencies: