Skip to content

Commit

Permalink
chore: Add tag group component (appsmithorg#29387)
Browse files Browse the repository at this point in the history
Fixes appsmithorg#29134 

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced `TagGroup` and `Tag` components with features like label,
description, error message, and tag removal options.
- Added new stories for `TagGroup` to demonstrate various configurations
and use cases.

- **Enhancements**
  - Updated `Switch` component with improved validation logic.
- Enhanced `TextArea` initialization to handle undefined default values
more gracefully.
- Added `validationState` property to `HelpText` and `Field` components
to support form validation states.

- **Style Updates**
- Implemented new CSS styles for `TagGroup` and `Tag` components to
improve UI consistency and interactivity.

- **Documentation**
- Expanded Storybook documentation with examples and usage guidelines
for `TagGroup` and `Tag` components.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
jsartisan authored Dec 11, 2023
1 parent 26039e9 commit 544c370
Show file tree
Hide file tree
Showing 17 changed files with 1,831 additions and 425 deletions.
3 changes: 1 addition & 2 deletions app/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,6 @@
"@blueprintjs/core@^3.47.0": "patch:@blueprintjs/core@npm%3A3.47.0#./.yarn/patches/@blueprintjs-core-npm-3.47.0-a5bc1ea927.patch",
"@blueprintjs/icons": "3.22.0",
"@types/react": "^17.0.2",
"postcss": "8.4.31",
"@react-types/shared": "3.19.0"
"postcss": "8.4.31"
}
}
2 changes: 1 addition & 1 deletion app/client/packages/design-system/headless/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@react-types/checkbox": "^3.4.3",
"@react-types/label": "^3.7.3",
"@react-types/menu": "^3.9.5",
"@react-types/shared": "^3.17.0",
"@react-types/shared": "^3.22.0",
"classnames": "*"
},
"peerDependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import React, { forwardRef } from "react";
import type { HTMLAttributes } from "react";
import { useDOMRef } from "@react-spectrum/utils";
import type { DOMRef, SpectrumHelpTextProps } from "@react-types/shared";
import type {
DOMRef,
SpectrumHelpTextProps,
ValidationState,
} from "@react-types/shared";

interface HelpTextProps extends Omit<SpectrumHelpTextProps, "showErrorIcon"> {
/** Props for the help text description element. */
descriptionProps?: HTMLAttributes<HTMLElement>;
/** Props for the help text error message element. */
errorMessageProps?: HTMLAttributes<HTMLElement>;
/** validation state for help text */
validationState: ValidationState;
}

function _HelpText(props: HelpTextProps, ref: DOMRef<HTMLDivElement>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -13,6 +13,7 @@ export type FieldProps = Omit<
fieldType?: "field" | "field-group";
labelClassName?: string;
helpTextClassName?: string;
validationState?: ValidationState;
};

export type FieldRef = Ref<HTMLDivElement>;
Expand Down Expand Up @@ -83,7 +84,7 @@ const _Field = (props: FieldProps, ref: FieldRef) => {
<div
{...wrapperProps}
className={wrapperClassName}
data-disabled={isDisabled ? "" : undefined}
data-disabled={Boolean(isDisabled) ? "" : undefined}
data-field=""
data-field-type={fieldType}
ref={ref}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import React, { forwardRef } from "react";
import type { HTMLAttributes } from "react";
import { useDOMRef } from "@react-spectrum/utils";
import type { DOMRef, SpectrumHelpTextProps } from "@react-types/shared";
import type {
DOMRef,
SpectrumHelpTextProps,
ValidationState,
} from "@react-types/shared";

interface HelpTextProps extends Omit<SpectrumHelpTextProps, "showErrorIcon"> {
/** Props for the help text description element. */
Expand All @@ -10,6 +14,8 @@ interface HelpTextProps extends Omit<SpectrumHelpTextProps, "showErrorIcon"> {
errorMessageProps?: HTMLAttributes<HTMLElement>;
/** classname */
className?: string;
/** validation state for help text */
validationState?: ValidationState;
}

function _HelpText(props: HelpTextProps, ref: DOMRef<HTMLDivElement>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import type { CheckboxGroupContextType } from "../Checkbox";

export interface SwitchProps
extends Omit<SpectrumSwitchProps, keyof StyleProps>,
Validation,
InlineLabelProps {
InlineLabelProps,
Validation<boolean> {
className?: string;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function TextArea(props: TextAreaProps, ref: TextAreaRef) {
// not in stately because this is so we know when to re-measure, which is a spectrum design
const [inputValue, setInputValue] = useControlledState(
props.value,
props.defaultValue,
props.defaultValue ?? "",
() => {
//
},
Expand Down
3 changes: 2 additions & 1 deletion app/client/packages/design-system/widgets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"@react-aria/visually-hidden": "^3.8.0",
"clsx": "^2.0.0",
"colorjs.io": "^0.4.3",
"lodash": "*"
"lodash": "*",
"react-aria-components": "^1.0.0-rc.0"
},
"devDependencies": {
"eslint-plugin-storybook": "^0.6.10"
Expand Down
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";

import styles from "./styles.module.css";
import { CloseIcon } from "../../Modal/src/CloseIcon";

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);
}
}
Loading

0 comments on commit 544c370

Please sign in to comment.