-
Notifications
You must be signed in to change notification settings - Fork 4.5k
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 all commits
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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -4,7 +4,7 @@ import type { SpectrumFieldProps } from "@react-types/label"; | |||||
|
|
||||||
| import { Label } from "./Label"; | ||||||
| import { HelpText } from "./HelpText"; | ||||||
| import type { StyleProps } from "@react-types/shared"; | ||||||
| import type { StyleProps, ValidationState } from "@react-types/shared"; | ||||||
|
|
||||||
| export type FieldProps = Omit< | ||||||
| SpectrumFieldProps, | ||||||
|
|
@@ -13,6 +13,7 @@ export type FieldProps = Omit< | |||||
| fieldType?: "field" | "field-group"; | ||||||
| labelClassName?: string; | ||||||
| helpTextClassName?: string; | ||||||
| validationState?: ValidationState; | ||||||
| }; | ||||||
|
|
||||||
| export type FieldRef = Ref<HTMLDivElement>; | ||||||
|
|
@@ -83,7 +84,7 @@ const _Field = (props: FieldProps, ref: FieldRef) => { | |||||
| <div | ||||||
| {...wrapperProps} | ||||||
| className={wrapperClassName} | ||||||
| data-disabled={isDisabled ? "" : undefined} | ||||||
| data-disabled={Boolean(isDisabled) ? "" : undefined} | ||||||
|
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 - data-disabled={Boolean(isDisabled) ? "" : undefined}
+ data-disabled={isDisabled}Committable suggestion
Suggested change
|
||||||
| data-field="" | ||||||
| data-field-type={fieldType} | ||||||
| ref={ref} | ||||||
|
|
||||||
| 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,36 @@ | ||
| import clsx from "clsx"; | ||
| import React from "react"; | ||
| import { | ||
| Tag as HeadlessTag, | ||
| Button as HeadlessButton, | ||
| } from "react-aria-components"; | ||
| import { getTypographyClassName } from "@design-system/theming"; | ||
| import type { TagProps as HeadlessTagProps } from "react-aria-components"; | ||
KelvinOm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| import styles from "./styles.module.css"; | ||
| import { CloseIcon } from "../../Modal/src/CloseIcon"; | ||
KelvinOm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| function Tag({ children, ...props }: HeadlessTagProps) { | ||
| const textValue = typeof children === "string" ? children : undefined; | ||
|
|
||
| return ( | ||
| <HeadlessTag | ||
| textValue={textValue} | ||
| {...props} | ||
| className={clsx(styles["tag"], getTypographyClassName("footnote"))} | ||
| > | ||
| {({ allowsRemoving }) => ( | ||
| <> | ||
| <span>{children}</span> | ||
| {allowsRemoving && ( | ||
| <HeadlessButton slot="remove"> | ||
| <CloseIcon /> | ||
| </HeadlessButton> | ||
| )} | ||
| </> | ||
| )} | ||
| </HeadlessTag> | ||
| ); | ||
| } | ||
|
|
||
| export { Tag }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| 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"; | ||
| import { getTypographyClassName } from "@design-system/theming"; | ||
|
|
||
| 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 | ||
| className={getTypographyClassName("footnote")} | ||
| slot="description" | ||
| > | ||
| {description} | ||
| </HeadlessText> | ||
| )} | ||
| {Boolean(errorMessage) && ( | ||
| <HeadlessText | ||
| className={getTypographyClassName("footnote")} | ||
| slot="errorMessage" | ||
| > | ||
| {errorMessage} | ||
| </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,106 @@ | ||
| .tag-group { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: var(--inner-spacing-2); | ||
|
|
||
| /** | ||
| * ---------------------------------------------------------------------------- | ||
| * ERROR MESSAGE | ||
| *----------------------------------------------------------------------------- | ||
| */ | ||
| [slot="errorMessage"] { | ||
| color: var(--color-fg-negative); | ||
| } | ||
| } | ||
|
|
||
| .tag-list { | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| gap: var(--inner-spacing-2); | ||
| } | ||
|
|
||
| .tag { | ||
| height: var(--sizing-6); | ||
| color: var(--color-fg); | ||
| background-color: var(--color-bg-neutral-subtle); | ||
| border-radius: var(--border-radius-1); | ||
| padding: 0 var(--inner-spacing-2); | ||
| outline: none; | ||
| cursor: default; | ||
| display: flex; | ||
| align-items: center; | ||
| transition: border-color 200ms; | ||
| overflow: hidden; | ||
|
|
||
| &:has([slot="remove"]) { | ||
| padding-inline-end: 0; | ||
| } | ||
|
|
||
| /** | ||
| * ---------------------------------------------------------------------------- | ||
| * HOVERED | ||
| *----------------------------------------------------------------------------- | ||
| */ | ||
| &[data-hovered] { | ||
| background-color: var(--color-bg-neutral-subtle-hover); | ||
| } | ||
|
|
||
| &[data-focus-visible] { | ||
| outline: 2px solid var(--color-bd-focus); | ||
| outline-offset: 2px; | ||
| } | ||
|
|
||
| /** | ||
| * ---------------------------------------------------------------------------- | ||
| * SELECTED TAG | ||
| *----------------------------------------------------------------------------- | ||
| */ | ||
| &[data-selected] { | ||
| border-color: var(--color-bd-neutral); | ||
| background: var(--color-bg-neutral); | ||
| color: var(--color-fg-on-neutral); | ||
| } | ||
|
|
||
| /** | ||
| * ---------------------------------------------------------------------------- | ||
| * REMOVE BUTTON | ||
| *----------------------------------------------------------------------------- | ||
| */ | ||
| [slot="remove"] { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| height: var(--sizing-6); | ||
| width: var(--sizing-6); | ||
| background: none; | ||
| border: none; | ||
| padding: 0; | ||
| margin-inline-start: var(--inner-spacing-1); | ||
| color: var(--color-fg); | ||
| transition: color 200ms; | ||
| outline: none; | ||
| font-size: 0.95em; | ||
|
|
||
| &[data-hovered] { | ||
| background: var(--color-bg-neutral-subtle-hover); | ||
| } | ||
|
|
||
| svg { | ||
| width: 1em; | ||
| height: 1em; | ||
| } | ||
|
|
||
| &[data-hovered] { | ||
| color: var(--remove-button-color-hovered); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * ---------------------------------------------------------------------------- | ||
| * DISABLED TAG | ||
| *----------------------------------------------------------------------------- | ||
| */ | ||
| &[data-disabled] { | ||
| opacity: var(--opacity-disabled); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❤️