-
Notifications
You must be signed in to change notification settings - Fork 4.6k
chore: Add tag group component #29387
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
b4ecf11
8c2b1d6
d634241
e38a3a5
ee9d67c
ad30334
c0118b0
bfe924b
b1c12f2
3976aaa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from "./src"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import React from "react"; | ||
| import { | ||
| Tag as HeadlessTag, | ||
| Button as HeadlessButton, | ||
| } from "react-aria-components"; | ||
| import type { TagProps as HeadlessTagProps } from "react-aria-components"; | ||
|
|
||
| import styles from "./styles.module.css"; | ||
| import { CloseIcon } from "../../Modal/src/CloseIcon"; | ||
|
KelvinOm marked this conversation as resolved.
|
||
| import { getTypographyClassName } from "@design-system/theming"; | ||
|
|
||
| function Tag({ children, ...props }: HeadlessTagProps) { | ||
| const textValue = typeof children === "string" ? children : undefined; | ||
|
|
||
| return ( | ||
| <HeadlessTag textValue={textValue} {...props} className={styles["tag"]}> | ||
| {({ allowsRemoving }) => ( | ||
| <> | ||
| <span className={getTypographyClassName("footnote")}>{children}</span> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we pass className to the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| {allowsRemoving && ( | ||
| <HeadlessButton slot="remove"> | ||
| <CloseIcon /> | ||
| </HeadlessButton> | ||
| )} | ||
| </> | ||
| )} | ||
| </HeadlessTag> | ||
| ); | ||
| } | ||
|
|
||
| export { Tag }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import React from "react"; | ||
| import { | ||
| Label as HeadlessLabel, | ||
| TagGroup as HeadlessTagGroup, | ||
| TagList as HeadlessTagList, | ||
| Text as HeadlessText, | ||
| } from "react-aria-components"; | ||
| import type { | ||
| TagGroupProps as HeadlessTagGroupProps, | ||
| TagListProps as HeadlessTagListProps, | ||
| } from "react-aria-components"; | ||
|
|
||
| import { Text } from "../../Text"; | ||
| import styles from "./styles.module.css"; | ||
|
|
||
| interface TagGroupProps<T> | ||
| extends Omit<HeadlessTagGroupProps, "children">, | ||
| Pick<HeadlessTagListProps<T>, "items" | "children" | "renderEmptyState"> { | ||
| label?: string; | ||
| description?: string; | ||
| errorMessage?: string; | ||
| } | ||
|
|
||
| function TagGroup<T extends object>(props: TagGroupProps<T>) { | ||
| const { | ||
| children, | ||
| description, | ||
| errorMessage, | ||
| items, | ||
| label, | ||
| renderEmptyState, | ||
| ...rest | ||
| } = props; | ||
|
|
||
| return ( | ||
| <HeadlessTagGroup {...rest} className={styles["tag-group"]}> | ||
| {Boolean(label) && <HeadlessLabel>{<Text>{label}</Text>}</HeadlessLabel>} | ||
| <HeadlessTagList | ||
| className={styles["tag-list"]} | ||
| items={items} | ||
| renderEmptyState={renderEmptyState} | ||
| > | ||
| {children} | ||
| </HeadlessTagList> | ||
| {Boolean(description) && ( | ||
| <HeadlessText elementType="div" slot="description"> | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two components ( HeadlessText and Text ) are required because we need slot feature of HeadlessText and we need styles from our Text.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ignore. This is fixed. |
||
| <Text variant="footnote">{description}</Text> | ||
| </HeadlessText> | ||
| )} | ||
| {Boolean(errorMessage) && ( | ||
| <HeadlessText elementType="div" slot="errorMessage"> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Each HeadlessText component will create an additional DOM node. Is it necessary? Can we use just WDS Text component in the same way? If not, then may be we can throw WDS typography classes into the spectrum component.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| <Text color="negative" variant="footnote"> | ||
| {errorMessage} | ||
| </Text> | ||
| </HeadlessText> | ||
| )} | ||
| </HeadlessTagGroup> | ||
| ); | ||
| } | ||
|
|
||
| export { TagGroup }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export { TagGroup } from "./TagGroup"; | ||
| export { Tag } from "./Tag"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| .tag-group { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: var(--inner-spacing-2); | ||
| } | ||
|
|
||
| .tag-list { | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| gap: var(--inner-spacing-2); | ||
| } | ||
|
|
||
| .tag { | ||
| max-height: var(--sizing-6); | ||
| color: var(--color-fg); | ||
| border: 1px solid var(--color-bd); | ||
| border-radius: var(--border-radius-1); | ||
| padding: var(--inner-spacing-1) var(--inner-spacing-2); | ||
| outline: none; | ||
| cursor: default; | ||
| display: flex; | ||
| align-items: center; | ||
| gap: var(--inner-spacing-1); | ||
| transition: border-color 200ms; | ||
|
|
||
| &[data-hovered] { | ||
| border-color: var(--color-bd-hover); | ||
| } | ||
|
|
||
| &[data-focus-visible] { | ||
| outline: 2px solid var(--color-bd-focus); | ||
| outline-offset: 2px; | ||
| } | ||
|
|
||
| &[data-selected] { | ||
| border-color: var(--color-bd-neutral); | ||
| background: var(--color-bg-neutral); | ||
| color: var(--color-fg-on-neutral); | ||
| } | ||
|
|
||
| [slot="remove"] { | ||
| display: flex; | ||
| align-items: center; | ||
| background: none; | ||
| border: none; | ||
| padding: 0; | ||
| color: var(--color-fg); | ||
| transition: color 200ms; | ||
| outline: none; | ||
| font-size: 0.95em; | ||
|
|
||
| svg { | ||
| width: 1em; | ||
| height: 1em; | ||
| } | ||
|
|
||
| &[data-hovered] { | ||
| color: var(--remove-button-color-hovered); | ||
| } | ||
| } | ||
|
|
||
| &[data-disabled] { | ||
| opacity: var(--opacity-disabled); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,148 @@ | ||||||
| import { TagGroup, Tag } from "@design-system/widgets"; | ||||||
| import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs"; | ||||||
|
|
||||||
| <Meta | ||||||
| title="Design-system/Widgets/TagGroup" | ||||||
| component={TagGroup} | ||||||
| args={{ | ||||||
| children: ( | ||||||
| <> | ||||||
| <Tag id="value-1">Value 1</Tag> | ||||||
| <Tag id="value-2">Value 2</Tag> | ||||||
| <Tag id="value-3">Value 3</Tag> | ||||||
| <Tag id="value-4">Value 4</Tag> | ||||||
| </> | ||||||
| ), | ||||||
| }} | ||||||
| /> | ||||||
|
|
||||||
| export const Template = (args) => <TagGroup {...args} />; | ||||||
|
KelvinOm marked this conversation as resolved.
|
||||||
|
|
||||||
| # Tag Group | ||||||
|
|
||||||
| Switch Group is a group of checkboxes that can be selected together. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The title "Switch Group" seems to be a copy-paste error and should be corrected to "Tag Group" to match the context of the component being documented. - Switch Group is a group of checkboxes that can be selected together.
+ Tag Group is a group of tags that can be interacted with.Committable suggestion
Suggested change
|
||||||
|
|
||||||
| <Canvas> | ||||||
| <Story name="Tag Group">{Template.bind({})}</Story> | ||||||
| </Canvas> | ||||||
|
|
||||||
| # Single Selection Mode | ||||||
|
|
||||||
| Switch Group can be configured to allow only one selection at a time with the `selectionMode` prop. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reference to "Switch Group" should be updated to "Tag Group" to maintain consistency with the component being described. - Switch Group can be configured to allow only one selection at a time with the `selectionMode` prop.
+ Tag Group can be configured to allow only one selection at a time with the `selectionMode` prop.Committable suggestion
Suggested change
|
||||||
|
|
||||||
| <Canvas> | ||||||
| <Story | ||||||
| name="Single Selection Mode" | ||||||
| args={{ | ||||||
| selectionMode: "single", | ||||||
| }} | ||||||
| > | ||||||
| {Template.bind({})} | ||||||
| </Story> | ||||||
| </Canvas> | ||||||
|
|
||||||
| # Multiple Selection Mode | ||||||
|
|
||||||
| Switch Group can be configured to allow multiple selections at a time with the `selectionMode` prop with value `multiple`. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reference to "Switch Group" should be corrected to "Tag Group" to accurately describe the component's functionality. - Switch Group can be configured to allow multiple selections at a time with the `selectionMode` prop with value `multiple`.
+ Tag Group can be configured to allow multiple selections at a time with the `selectionMode` prop with value `multiple`.Committable suggestion
Suggested change
|
||||||
|
|
||||||
| <Canvas> | ||||||
|
KelvinOm marked this conversation as resolved.
|
||||||
| <Story | ||||||
| name="Multiple Selection Mode" | ||||||
| args={{ | ||||||
| selectionMode: "multiple", | ||||||
| }} | ||||||
| > | ||||||
| {Template.bind({})} | ||||||
| </Story> | ||||||
| </Canvas> | ||||||
|
|
||||||
| ## Removing Tags | ||||||
|
|
||||||
| Tags can be removed with the `onRemove` prop. | ||||||
|
|
||||||
| <Canvas> | ||||||
| <Story | ||||||
| name="Removing Tags" | ||||||
| args={{ | ||||||
| onRemove: (id) => { | ||||||
| console.log(id); | ||||||
| }, | ||||||
| }} | ||||||
| > | ||||||
| {Template.bind({})} | ||||||
| </Story> | ||||||
| </Canvas> | ||||||
|
|
||||||
| ## Disabled Tags | ||||||
|
|
||||||
| Tags can be disabled with the `disabledKeys` prop. | ||||||
|
|
||||||
| <Canvas> | ||||||
| <Story | ||||||
| name="Disabled Tags" | ||||||
| args={{ | ||||||
| selectionMode: "multiple", | ||||||
| disabledKeys: ["value-2"], | ||||||
| }} | ||||||
| > | ||||||
| {Template.bind({})} | ||||||
| </Story> | ||||||
| </Canvas> | ||||||
|
|
||||||
| ## Empty State | ||||||
|
|
||||||
| <Canvas> | ||||||
| <Story | ||||||
| name="Empty State" | ||||||
| args={{ | ||||||
| renderEmptyState: () => "No categories.", | ||||||
| children: undefined, | ||||||
| }} | ||||||
| > | ||||||
| {Template.bind({})} | ||||||
| </Story> | ||||||
| </Canvas> | ||||||
|
|
||||||
| ## With Label | ||||||
|
|
||||||
| <Canvas> | ||||||
| <Story | ||||||
| name="With Label" | ||||||
| args={{ | ||||||
| label: "Categories", | ||||||
| selectionMode: "multiple", | ||||||
| }} | ||||||
| > | ||||||
| {Template.bind({})} | ||||||
| </Story> | ||||||
| </Canvas> | ||||||
|
|
||||||
| ## With Description | ||||||
|
|
||||||
| <Canvas> | ||||||
| <Story | ||||||
| name="With Description" | ||||||
| args={{ | ||||||
| label: "Categories", | ||||||
| description: "Select one or more categories.", | ||||||
| selectionMode: "multiple", | ||||||
| }} | ||||||
| > | ||||||
| {Template.bind({})} | ||||||
| </Story> | ||||||
| </Canvas> | ||||||
|
|
||||||
| ## With Error | ||||||
|
|
||||||
| <Canvas> | ||||||
| <Story | ||||||
| name="With Error" | ||||||
| args={{ | ||||||
| label: "Categories", | ||||||
| selectionMode: "multiple", | ||||||
| errorMessage: "Please select at least one category.", | ||||||
| }} | ||||||
| > | ||||||
| {Template.bind({})} | ||||||
| </Story> | ||||||
| </Canvas> | ||||||
Uh oh!
There was an error while loading. Please reload this page.