From 3e51af6fa26b6e2605f0cfe5eda733b104529a87 Mon Sep 17 00:00:00 2001 From: Michael Taranto Date: Thu, 27 Jun 2024 12:11:04 +1000 Subject: [PATCH] =?UTF-8?q?Tag:=20Introduce=20=E2=80=9Caddable=E2=80=9D=20?= =?UTF-8?q?support=20(#1521)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/sharp-bears-remember.md | 18 +++ .../src/lib/components/Tag/Tag.docs.tsx | 147 +++++++++++------- .../src/lib/components/Tag/Tag.gallery.tsx | 41 ++--- .../lib/components/Tag/Tag.screenshots.tsx | 56 +++++-- .../src/lib/components/Tag/Tag.snippets.tsx | 16 ++ .../src/lib/components/Tag/Tag.tsx | 71 +++++++-- .../src/__snapshots__/contract.test.ts.snap | 2 + 7 files changed, 225 insertions(+), 126 deletions(-) create mode 100644 .changeset/sharp-bears-remember.md diff --git a/.changeset/sharp-bears-remember.md b/.changeset/sharp-bears-remember.md new file mode 100644 index 00000000000..0df91094882 --- /dev/null +++ b/.changeset/sharp-bears-remember.md @@ -0,0 +1,18 @@ +--- +'braid-design-system': minor +--- + +--- +updated: + - Tag +--- + +**Tag:** Introduce "addable" support + +Tag actions have been extended to now support being “added”. +A `Tag` will include a small add icon button when both an `onAdd` handler and `addLabel` prop are provided. + +**EXAMPLE USAGE:** +```jsx + {...}} addLabel="Add Tag" /> +``` diff --git a/packages/braid-design-system/src/lib/components/Tag/Tag.docs.tsx b/packages/braid-design-system/src/lib/components/Tag/Tag.docs.tsx index 8a72a8d4fd1..bd8ec64e7f0 100644 --- a/packages/braid-design-system/src/lib/components/Tag/Tag.docs.tsx +++ b/packages/braid-design-system/src/lib/components/Tag/Tag.docs.tsx @@ -1,12 +1,11 @@ import React from 'react'; import type { ComponentDocs } from 'site/types'; import { - Card, Inline, Tag, Strong, Text, - TextLinkButton, + TextLink, Stack, IconLanguage, IconTag, @@ -17,13 +16,11 @@ const docs: ComponentDocs = { category: 'Content', Example: () => source( - - - One - Two - Three - - , + + One + Two + Three + , ), alternatives: [{ name: 'Badge', description: 'For static labels.' }], additional: [ @@ -66,68 +63,98 @@ const docs: ComponentDocs = { ), }, { - label: 'Clearable', + label: 'Actionable tags', description: ( <> - Tags can be made clearable, by providing an onClear{' '} - handler and a clearLabel to describe what clicking - it will do. + Tags can be made actionable to support either being “cleared” or + “added”. + + + + By providing an onClear handler and a{' '} + clearLabel, a{' '} + ButtonIcon{' '} + component with a clear icon will be added to the right of the tag + label. + + + + Alternatively, providing an onAdd handler and an{' '} + addLabel will insert a{' '} + ButtonIcon{' '} + component with an add icon to the right of the tag label. The{' '} - aria-label for the clear button can be customised - by providing the clearLabel prop. + aria-label for the button can be customised by + providing the relevant clearLabel or{' '} + addLabel prop. ), background: 'surface', - Example: ({ getState, setState, toggleState }) => - source( - - {!getState('clearOne') ? ( - toggleState('clearOne')} - clearLabel={'Clear "One"'} - id="clear-1" - > - One - - ) : null} - {!getState('clearTwo') ? ( - toggleState('clearTwo')} - clearLabel={'Clear "Two"'} - id="clear-2" - > - Two - - ) : null} - {!getState('clearThree') ? ( - toggleState('clearThree')} - clearLabel={'Clear "Three"'} - id="clear-3" - > - Three - - ) : null} - - { - setState('clearOne', false); - setState('clearTwo', false); - setState('clearThree', false); - }} - > - Reset - - - , - ), + Example: ({ setDefaultState, getState, setState }) => { + const { code, value } = source( + <> + {setDefaultState('selected', ['One', 'Two', 'Three'])} + + + + + ADDABLE + + + {['One', 'Two', 'Three', 'Four', 'Five'] + .filter((tag) => !getState('selected').includes(tag)) + .map((tag) => ( + + setState('selected', [...getState('selected'), tag]) + } + addLabel={`Add "${tag}"`} + id={`add-${tag}`} + > + {tag} + + ))} + + + + + CLEARABLE + + + {getState('selected').map((selectedTag: string) => ( + { + setState( + 'selected', + getState('selected').filter( + (tag: string) => tag !== selectedTag, + ), + ); + }} + clearLabel={`Clear "${selectedTag}"`} + id={`clear-${selectedTag}`} + > + {selectedTag} + + ))} + + + + , + ); + + return { + code: code.replaceAll(': string', ''), + value, + }; + }, }, { label: 'Inserting an icon', diff --git a/packages/braid-design-system/src/lib/components/Tag/Tag.gallery.tsx b/packages/braid-design-system/src/lib/components/Tag/Tag.gallery.tsx index f1fea2222fc..3b4598a9e56 100644 --- a/packages/braid-design-system/src/lib/components/Tag/Tag.gallery.tsx +++ b/packages/braid-design-system/src/lib/components/Tag/Tag.gallery.tsx @@ -1,6 +1,6 @@ import React from 'react'; import type { GalleryComponent } from 'site/types'; -import { Tag, Inline, Text, TextLinkButton, IconTag } from '../'; +import { Tag, Inline, IconTag } from '../'; import source from '@braid-design-system/source.macro'; export const galleryItems: GalleryComponent = { @@ -21,40 +21,17 @@ export const galleryItems: GalleryComponent = { Example: () => source(}>Tag), }, { - label: 'Clearable', + label: 'Actionable', background: 'surface', - Example: ({ getState, setState }) => + Example: () => source( - One - {!getState('clearTwo') ? ( - setState('clearTwo', true)} - clearLabel={'Clear "Two"'} - > - Two - - ) : null} - {!getState('clearThree') ? ( - setState('clearThree', true)} - clearLabel={'Clear "Three"'} - > - Three - - ) : null} - - { - setState('clearTwo', false); - setState('clearThree', false); - }} - > - Reset - - + {}}> + Addable + + {}}> + Clearable + , ), }, diff --git a/packages/braid-design-system/src/lib/components/Tag/Tag.screenshots.tsx b/packages/braid-design-system/src/lib/components/Tag/Tag.screenshots.tsx index 43063c274e6..04d1f055bc9 100644 --- a/packages/braid-design-system/src/lib/components/Tag/Tag.screenshots.tsx +++ b/packages/braid-design-system/src/lib/components/Tag/Tag.screenshots.tsx @@ -4,7 +4,7 @@ import { Tag, Inline, IconTag, Stack } from '../'; import { debugTouchableAttrForDataProp } from '../private/touchable/debugTouchable'; export const screenshots: ComponentScreenshot = { - screenshotWidths: [320], + screenshotWidths: [768], examples: [ { label: 'Standard Tag', @@ -16,6 +16,9 @@ export const screenshots: ComponentScreenshot = { } onClear={handler} clearLabel="Clear"> Tag + } onAdd={handler} addLabel="Add"> + Tag + ), }, @@ -36,6 +39,9 @@ export const screenshots: ComponentScreenshot = { > Tag + } onAdd={handler} addLabel="Add"> + Tag + ), }, @@ -43,13 +49,36 @@ export const screenshots: ComponentScreenshot = { label: 'Truncated Tag', background: 'surface', Example: ({ handler }) => ( - - The quick brown fox jumps over the lazy dog. The quick brown fox jumps - over the lazy dog. The quick brown fox jumps over the lazy dog. The - quick brown fox jumps over the lazy dog. The quick brown fox jumps - over the lazy dog. The quick brown fox jumps over the lazy dog. The - quick brown fox jumps over the lazy dog. - + + + The quick brown fox jumps over the lazy dog. The quick brown fox + jumps over the lazy dog. The quick brown fox jumps over the lazy + dog. The quick brown fox jumps over the lazy dog. The quick brown + fox jumps over the lazy dog. The quick brown fox jumps over the lazy + dog. The quick brown fox jumps over the lazy dog. + + }> + The quick brown fox jumps over the lazy dog. The quick brown fox + jumps over the lazy dog. The quick brown fox jumps over the lazy + dog. The quick brown fox jumps over the lazy dog. The quick brown + fox jumps over the lazy dog. The quick brown fox jumps over the lazy + dog. The quick brown fox jumps over the lazy dog. + + + The quick brown fox jumps over the lazy dog. The quick brown fox + jumps over the lazy dog. The quick brown fox jumps over the lazy + dog. The quick brown fox jumps over the lazy dog. The quick brown + fox jumps over the lazy dog. The quick brown fox jumps over the lazy + dog. The quick brown fox jumps over the lazy dog. + + } onClear={handler} clearLabel="Clear tag"> + The quick brown fox jumps over the lazy dog. The quick brown fox + jumps over the lazy dog. The quick brown fox jumps over the lazy + dog. The quick brown fox jumps over the lazy dog. The quick brown + fox jumps over the lazy dog. The quick brown fox jumps over the lazy + dog. The quick brown fox jumps over the lazy dog. + + ), }, { @@ -58,26 +87,19 @@ export const screenshots: ComponentScreenshot = { Example: ({ handler }) => ( - Tag Tag - } - onClear={handler} - clearLabel="Clear" - > + Tag - Tag Tag - } onClear={handler} clearLabel="Clear"> + Tag diff --git a/packages/braid-design-system/src/lib/components/Tag/Tag.snippets.tsx b/packages/braid-design-system/src/lib/components/Tag/Tag.snippets.tsx index e788e87a757..5c0f7135aad 100644 --- a/packages/braid-design-system/src/lib/components/Tag/Tag.snippets.tsx +++ b/packages/braid-design-system/src/lib/components/Tag/Tag.snippets.tsx @@ -40,6 +40,22 @@ export const snippets: Snippets = [ , ), }, + { + name: 'Addable', + code: source( + + {}} addLabel="Add"> + Tag + + {}} addLabel="Add"> + Tag + + {}} addLabel="Add"> + Tag + + , + ), + }, { name: 'With icon', code: source( diff --git a/packages/braid-design-system/src/lib/components/Tag/Tag.tsx b/packages/braid-design-system/src/lib/components/Tag/Tag.tsx index 50f17733fee..cf5bd5c9937 100644 --- a/packages/braid-design-system/src/lib/components/Tag/Tag.tsx +++ b/packages/braid-design-system/src/lib/components/Tag/Tag.tsx @@ -1,26 +1,41 @@ -import React, { type AllHTMLAttributes } from 'react'; +import React from 'react'; import assert from 'assert'; import { Box } from '../Box/Box'; import { type TextProps, Text } from '../Text/Text'; -import { ButtonIcon } from '../ButtonIcon/ButtonIcon'; -import { IconClear } from '../icons'; +import { ButtonIcon, type ButtonIconProps } from '../ButtonIcon/ButtonIcon'; +import { IconAdd, IconClear } from '../icons'; import buildDataAttributes, { type DataAttributeMap, } from '../private/buildDataAttributes'; -import type { AllOrNone } from '../private/AllOrNone'; import type { Space } from '../../css/atoms/atoms'; import * as styles from './Tag.css'; export const tagSizes = ['small', 'standard'] as const; -type NativeButtonProps = AllHTMLAttributes; -export type TagProps = { +type CommonProps = { children: string; size?: (typeof tagSizes)[number]; data?: DataAttributeMap; id?: string; icon?: TextProps['icon']; -} & AllOrNone<{ onClear: NativeButtonProps['onClick']; clearLabel: string }>; +}; + +interface AddableTag extends CommonProps { + onAdd: ButtonIconProps['onClick']; + addLabel: string; +} +interface ClearableTag extends CommonProps { + onClear: ButtonIconProps['onClick']; + clearLabel: string; +} +interface BaseTag extends CommonProps { + onClear?: never; + clearLabel?: never; + onAdd?: never; + addLabel?: never; +} + +export type TagProps = ClearableTag | AddableTag | BaseTag; const paddingXForSize: Record, Space> = { small: 'xsmall', @@ -28,8 +43,6 @@ const paddingXForSize: Record, Space> = { }; export const Tag = ({ - onClear, - clearLabel = 'Clear', size = 'standard', data, id, @@ -47,6 +60,24 @@ export const Tag = ({ "Icons cannot set the 'size' or 'tone' prop when passed to a Tag component", ); + let label = 'Clear'; + let buttonType = 'clear'; + let handler; + + const clearable = 'onClear' in restProps && restProps.onClear; + const addable = 'onAdd' in restProps && restProps.onAdd; + + if (clearable) { + label = restProps.clearLabel; + handler = restProps.onClear; + buttonType = 'clear'; + } else if (addable) { + label = restProps.addLabel; + handler = restProps.onAdd; + buttonType = 'add'; + } + const hasButton = clearable || addable; + return ( - + {icon} {children} - {onClear ? ( + {hasButton ? ( } - label={clearLabel} + id={id ? `${id}-${buttonType}` : undefined} + icon={addable ? : } + label={label} size="small" + tone={addable ? 'formAccent' : 'neutral'} variant="transparent" - onClick={onClear} + onClick={handler} /> ) : null} diff --git a/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap b/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap index 19919266caa..f97cd7d2760 100644 --- a/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap +++ b/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap @@ -7574,11 +7574,13 @@ exports[`Tag 1`] = ` { exportType: component, props: { + addLabel?: string children: string clearLabel?: string data?: DataAttributeMap icon?: ReactElement> id?: string + onAdd?: MouseEventHandler onClear?: MouseEventHandler size?: | "small"