diff --git a/app/client/packages/design-system/headless/src/components/Radio/src/Radio.tsx b/app/client/packages/design-system/headless/src/components/Radio/src/Radio.tsx deleted file mode 100644 index 995ede5142..0000000000 --- a/app/client/packages/design-system/headless/src/components/Radio/src/Radio.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { useRadio } from "@react-aria/radio"; -import { mergeProps } from "@react-aria/utils"; -import { useFocusRing } from "@react-aria/focus"; -import { useHover } from "@react-aria/interactions"; -import { useFocusableRef } from "@react-spectrum/utils"; -import type { SpectrumRadioProps } from "@react-types/radio"; -import React, { forwardRef, useContext, useRef } from "react"; -import { useVisuallyHidden } from "@react-aria/visually-hidden"; -import type { FocusableRef, StyleProps } from "@react-types/shared"; - -import { RadioContext } from "./context"; -import type { RadioGroupContext } from "./context"; - -export interface RadioProps extends Omit { - className?: string; - labelPosition?: "start" | "end"; -} - -export type RadioRef = FocusableRef; - -const _Radio = (props: RadioProps, ref: RadioRef) => { - const { - autoFocus, - children, - className, - isDisabled: isDisabledProp = false, - labelPosition = "end", - } = props; - const inputRef = useRef(null); - const domRef = useFocusableRef(ref, inputRef); - const { visuallyHiddenProps } = useVisuallyHidden(); - const radioGroupProps = useContext(RadioContext) as RadioGroupContext; - const { state, validationState } = radioGroupProps; - const isDisabled = isDisabledProp || radioGroupProps.isDisabled; - const { hoverProps, isHovered } = useHover({ isDisabled }); - const { focusProps, isFocusVisible } = useFocusRing({ autoFocus }); - const { inputProps } = useRadio( - { - ...props, - ...radioGroupProps, - isDisabled, - }, - state, - inputRef, - ); - - return ( - - ); -}; - -export const Radio = forwardRef(_Radio); diff --git a/app/client/packages/design-system/headless/src/components/Radio/src/RadioGroup.tsx b/app/client/packages/design-system/headless/src/components/Radio/src/RadioGroup.tsx deleted file mode 100644 index 570ddefe7c..0000000000 --- a/app/client/packages/design-system/headless/src/components/Radio/src/RadioGroup.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React, { forwardRef, useRef } from "react"; -import { Field } from "@design-system/headless"; -import { useDOMRef } from "@react-spectrum/utils"; -import type { DOMRef } from "@react-types/shared"; -import { useRadioGroup } from "@react-aria/radio"; -import { useRadioGroupState } from "@react-stately/radio"; - -import { RadioContext } from "./context"; -import type { RadioGroupProps } from "./types"; -import { useGroupOrientation } from "../../../hooks"; - -export type RadioGroupRef = DOMRef; - -const _RadioGroup = (props: RadioGroupProps, ref: RadioGroupRef) => { - const { - children, - fieldClassName, - isDisabled = false, - validationState, - } = props; - const domRef = useDOMRef(ref); - const state = useRadioGroupState(props); - const { descriptionProps, errorMessageProps, labelProps, radioGroupProps } = - useRadioGroup(props, state); - const containerRef = useRef(null); - const { orientation } = useGroupOrientation( - { orientation: props.orientation }, - containerRef, - ); - - return ( - -
- - {children} - -
-
- ); -}; - -export const RadioGroup = forwardRef(_RadioGroup); diff --git a/app/client/packages/design-system/headless/src/components/Radio/src/context.ts b/app/client/packages/design-system/headless/src/components/Radio/src/context.ts deleted file mode 100644 index eacc81f141..0000000000 --- a/app/client/packages/design-system/headless/src/components/Radio/src/context.ts +++ /dev/null @@ -1,15 +0,0 @@ -import React, { useContext } from "react"; -import type { RadioGroupState } from "@react-stately/radio"; - -export interface RadioGroupContext { - name?: string; - validationState?: "valid" | "invalid"; - state: RadioGroupState; - isDisabled?: boolean; -} - -export const RadioContext = React.createContext(null); - -export function useRadioProvider() { - return useContext(RadioContext) as RadioGroupContext; -} diff --git a/app/client/packages/design-system/headless/src/components/Radio/src/index.ts b/app/client/packages/design-system/headless/src/components/Radio/src/index.ts deleted file mode 100644 index d790211b61..0000000000 --- a/app/client/packages/design-system/headless/src/components/Radio/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { Radio } from "./Radio"; -export { RadioGroup } from "./RadioGroup"; -export type { RadioGroupProps } from "./types"; -export type { RadioProps, RadioRef } from "./Radio"; -export type { RadioGroupRef } from "./RadioGroup"; diff --git a/app/client/packages/design-system/headless/src/components/Radio/src/types.ts b/app/client/packages/design-system/headless/src/components/Radio/src/types.ts deleted file mode 100644 index 02e96756f3..0000000000 --- a/app/client/packages/design-system/headless/src/components/Radio/src/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { StyleProps } from "@react-types/shared"; -import type { SpectrumRadioGroupProps } from "@react-types/radio"; -import type { TextInputProps } from "../../TextInput"; - -export interface RadioGroupProps - extends Omit< - SpectrumRadioGroupProps, - keyof StyleProps | "labelPosition" | "labelAlign" | "isEmphasized" - >, - Pick< - TextInputProps, - "fieldClassName" | "labelClassName" | "helpTextClassName" | "className" - > {} diff --git a/app/client/packages/design-system/headless/src/components/Radio/stories/Radio.stories.tsx b/app/client/packages/design-system/headless/src/components/Radio/stories/Radio.stories.tsx deleted file mode 100644 index 9554d7d98f..0000000000 --- a/app/client/packages/design-system/headless/src/components/Radio/stories/Radio.stories.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import type { Meta, StoryObj } from "@storybook/react"; -import { RadioGroup, Radio } from "@design-system/headless"; - -/** - * A Radio group is a group of radio buttons that are related to each other in some way. For example, they may all represent a single question on a survey. The Radio group component is a headless component that provides the logic and accessibility implementation for a group of radio buttons. - * - * Note: The `` is visually hidden by default. Use the `` to render custom looking radio. - */ -const meta: Meta = { - component: RadioGroup, - title: "Design-system/headless/RadioGroup", - subcomponents: { - //@ts-expect-error: don't need props to pass here - Radio, - }, - render: (args) => ( - - Option 1 - Option 2 - - ), -}; - -export default meta; -type Story = StoryObj; - -export const Main: Story = {}; diff --git a/app/client/packages/design-system/headless/src/index.ts b/app/client/packages/design-system/headless/src/index.ts index 8323476010..dbf6ee4d55 100644 --- a/app/client/packages/design-system/headless/src/index.ts +++ b/app/client/packages/design-system/headless/src/index.ts @@ -2,7 +2,6 @@ export * from "./components/Field"; export * from "./components/Icon"; export * from "./components/Tooltip"; -export * from "./components/Radio"; export * from "./components/TextInput"; export * from "./components/TextArea"; export * from "./components/Popover"; diff --git a/app/client/packages/design-system/widgets/src/components/Checkbox/src/Checkbox.tsx b/app/client/packages/design-system/widgets/src/components/Checkbox/src/Checkbox.tsx index 75f76ca25e..9c978f012f 100644 --- a/app/client/packages/design-system/widgets/src/components/Checkbox/src/Checkbox.tsx +++ b/app/client/packages/design-system/widgets/src/components/Checkbox/src/Checkbox.tsx @@ -2,13 +2,8 @@ import React, { forwardRef } from "react"; import { Checkbox as HeadlessCheckbox } from "react-aria-components"; import { Text, Icon } from "@design-system/widgets"; import styles from "./styles.module.css"; -import type { POSITION } from "@design-system/widgets"; import type { ForwardedRef } from "react"; -import type { CheckboxProps as HeadlessCheckboxProps } from "react-aria-components"; - -export interface CheckboxProps extends HeadlessCheckboxProps { - labelPosition?: keyof typeof POSITION; -} +import type { CheckboxProps } from "./types"; const _Checkbox = ( props: CheckboxProps, diff --git a/app/client/packages/design-system/widgets/src/components/Checkbox/src/index.ts b/app/client/packages/design-system/widgets/src/components/Checkbox/src/index.ts index 312f26b45b..f783616eba 100644 --- a/app/client/packages/design-system/widgets/src/components/Checkbox/src/index.ts +++ b/app/client/packages/design-system/widgets/src/components/Checkbox/src/index.ts @@ -1,2 +1,2 @@ export { Checkbox } from "./Checkbox"; -export type { CheckboxProps } from "./Checkbox"; +export type { CheckboxProps } from "./types"; diff --git a/app/client/packages/design-system/widgets/src/components/Checkbox/src/types.ts b/app/client/packages/design-system/widgets/src/components/Checkbox/src/types.ts new file mode 100644 index 0000000000..8d7c570781 --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/Checkbox/src/types.ts @@ -0,0 +1,6 @@ +import type { CheckboxProps as HeadlessCheckboxProps } from "react-aria-components"; +import type { POSITION } from "../../../shared"; + +export interface CheckboxProps extends HeadlessCheckboxProps { + labelPosition?: keyof typeof POSITION; +} diff --git a/app/client/packages/design-system/headless/src/components/Radio/index.ts b/app/client/packages/design-system/widgets/src/components/ErrorMessage/index.ts similarity index 100% rename from app/client/packages/design-system/headless/src/components/Radio/index.ts rename to app/client/packages/design-system/widgets/src/components/ErrorMessage/index.ts diff --git a/app/client/packages/design-system/widgets/src/components/ErrorMessage/src/ErrorMessage.tsx b/app/client/packages/design-system/widgets/src/components/ErrorMessage/src/ErrorMessage.tsx new file mode 100644 index 0000000000..de3f95e07f --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/ErrorMessage/src/ErrorMessage.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import { Text } from "@design-system/widgets"; +import styles from "./styles.module.css"; +import type { ErrorMessageProps } from "./types"; + +export const ErrorMessage = (props: ErrorMessageProps) => { + const { text } = props; + + if (!Boolean(text)) return null; + + return ( + + {text} + + ); +}; diff --git a/app/client/packages/design-system/widgets/src/components/ErrorMessage/src/index.ts b/app/client/packages/design-system/widgets/src/components/ErrorMessage/src/index.ts new file mode 100644 index 0000000000..435effc80d --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/ErrorMessage/src/index.ts @@ -0,0 +1 @@ +export * from "./ErrorMessage"; diff --git a/app/client/packages/design-system/widgets/src/components/ErrorMessage/src/styles.module.css b/app/client/packages/design-system/widgets/src/components/ErrorMessage/src/styles.module.css new file mode 100644 index 0000000000..4725547b24 --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/ErrorMessage/src/styles.module.css @@ -0,0 +1,3 @@ +.errorMessage { + margin-block-start: var(--inner-spacing-2); +} diff --git a/app/client/packages/design-system/widgets/src/components/ErrorMessage/src/types.ts b/app/client/packages/design-system/widgets/src/components/ErrorMessage/src/types.ts new file mode 100644 index 0000000000..e150723ebc --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/ErrorMessage/src/types.ts @@ -0,0 +1,3 @@ +export interface ErrorMessageProps { + text?: string; +} diff --git a/app/client/packages/design-system/widgets/src/components/Label/src/Label.tsx b/app/client/packages/design-system/widgets/src/components/Label/src/Label.tsx index a9976dd207..2b7790f2bc 100644 --- a/app/client/packages/design-system/widgets/src/components/Label/src/Label.tsx +++ b/app/client/packages/design-system/widgets/src/components/Label/src/Label.tsx @@ -6,11 +6,15 @@ import styles from "./styles.module.css"; import type { LabelProps } from "./types"; export const Label = (props: LabelProps) => { - const { className, contextualHelp, isRequired, text, ...rest } = props; + const { className, contextualHelp, isDisabled, isRequired, text, ...rest } = + props; + + if (!Boolean(text) && !Boolean(contextualHelp)) return null; return ( = { - component: Radio, - title: "Design System/Widgets/Radio", -}; - -export default meta; - -type Story = StoryObj; - -const states = ["", "data-hovered", "data-focused", "data-disabled"]; - -export const LightMode: Story = { - render: () => ( - - {states.map((state) => ( - <> - - - unchecked {state} - - - - - checked {state} - - - - ))} - - ), -}; - -export const DarkMode: Story = Object.assign({}, LightMode); - -DarkMode.parameters = { - colorMode: "dark", -}; diff --git a/app/client/packages/design-system/widgets/src/components/Radio/index.ts b/app/client/packages/design-system/widgets/src/components/Radio/index.ts deleted file mode 100644 index 3bd16e178a..0000000000 --- a/app/client/packages/design-system/widgets/src/components/Radio/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./src"; diff --git a/app/client/packages/design-system/widgets/src/components/Radio/src/Radio.tsx b/app/client/packages/design-system/widgets/src/components/Radio/src/Radio.tsx deleted file mode 100644 index 06e5271228..0000000000 --- a/app/client/packages/design-system/widgets/src/components/Radio/src/Radio.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import clsx from "clsx"; -import React, { forwardRef } from "react"; - -import type { - RadioRef as HeadlessRadioRef, - RadioProps as HeadlessRadioProps, -} from "@design-system/headless"; -import { Radio as HeadlessRadio } from "@design-system/headless"; - -import { Text } from "@design-system/widgets"; -import radioStyles from "./styles.module.css"; -import { inlineLabelStyles } from "../../../styles"; - -export type RadioProps = HeadlessRadioProps; - -const _Radio = (props: RadioProps, ref: HeadlessRadioRef) => { - const { children, labelPosition = "end", ...rest } = props; - - return ( - - {Boolean(children) && {children}} - - ); -}; - -export const Radio = forwardRef(_Radio); diff --git a/app/client/packages/design-system/widgets/src/components/Radio/src/index.ts b/app/client/packages/design-system/widgets/src/components/Radio/src/index.ts deleted file mode 100644 index 03bf29daf5..0000000000 --- a/app/client/packages/design-system/widgets/src/components/Radio/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { Radio } from "./Radio"; -export type { RadioProps } from "./Radio"; diff --git a/app/client/packages/design-system/widgets/src/components/RadioGroup/chromatic/RadioGroup.chromatic.stories.tsx b/app/client/packages/design-system/widgets/src/components/RadioGroup/chromatic/RadioGroup.chromatic.stories.tsx new file mode 100644 index 0000000000..f00633db83 --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/RadioGroup/chromatic/RadioGroup.chromatic.stories.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import type { Checkbox } from "@design-system/widgets"; +import { RadioGroup } from "@design-system/widgets"; +import type { Meta, StoryObj } from "@storybook/react"; +import { StoryGrid, DataAttrWrapper } from "@design-system/storybook"; + +const meta: Meta = { + component: RadioGroup, + title: "Design System/Widgets/RadioGroup", +}; + +export default meta; + +type Story = StoryObj; + +const states = ["", "data-hovered", "data-focus-visible", "data-disabled"]; + +const items = [{ label: "Value 1", value: "value-1" }]; + +export const LightMode: Story = { + render: () => ( + + {states.map((state) => ( + + + + ))} + {states.map((state) => ( + + + + ))} + + ), +}; + +export const DarkMode: Story = Object.assign({}, LightMode); + +DarkMode.parameters = { + colorMode: "dark", +}; diff --git a/app/client/packages/design-system/widgets/src/components/RadioGroup/src/RadioGroup.tsx b/app/client/packages/design-system/widgets/src/components/RadioGroup/src/RadioGroup.tsx index 9c15b3b4e1..644356442e 100644 --- a/app/client/packages/design-system/widgets/src/components/RadioGroup/src/RadioGroup.tsx +++ b/app/client/packages/design-system/widgets/src/components/RadioGroup/src/RadioGroup.tsx @@ -1,35 +1,63 @@ -import React, { forwardRef } from "react"; +import React, { forwardRef, useRef } from "react"; +import { RadioGroup as HeadlessRadioGroup, Radio } from "react-aria-components"; +import { + Label, + Flex, + Text, + ErrorMessage, + useGroupOrientation, +} from "@design-system/widgets"; +import styles from "./styles.module.css"; +import type { ForwardedRef } from "react"; +import type { RadioGroupProps } from "./types"; -import type { - RadioGroupRef as HeadlessRadioGroupRef, - RadioGroupProps as HeadlessRadioGroupProps, -} from "@design-system/headless"; -import { getTypographyClassName } from "@design-system/theming"; -import { RadioGroup as HeadlessRadioGroup } from "@design-system/headless"; - -import { fieldStyles } from "../../../styles"; -import { ContextualHelp } from "../../ContextualHelp"; - -export interface RadioGroupProps extends HeadlessRadioGroupProps { - className?: string; -} - -const _RadioGroup = (props: RadioGroupProps, ref: HeadlessRadioGroupRef) => { - const { contextualHelp: contextualHelpProp, ...rest } = props; - - const contextualHelp = Boolean(contextualHelpProp) && ( - +const _RadioGroup = ( + props: RadioGroupProps, + ref: ForwardedRef, +) => { + const { + contextualHelp, + errorMessage, + isDisabled, + isRequired, + items, + label, + ...rest + } = props; + const containerRef = useRef(null); + const { orientation } = useGroupOrientation( + { orientation: props.orientation }, + containerRef, ); return ( + > + ); }; diff --git a/app/client/packages/design-system/widgets/src/components/RadioGroup/src/index.ts b/app/client/packages/design-system/widgets/src/components/RadioGroup/src/index.ts index 9bfd6e6515..453318cc10 100644 --- a/app/client/packages/design-system/widgets/src/components/RadioGroup/src/index.ts +++ b/app/client/packages/design-system/widgets/src/components/RadioGroup/src/index.ts @@ -1,2 +1,2 @@ export { RadioGroup } from "./RadioGroup"; -export type { RadioGroupProps } from "./RadioGroup"; +export type { RadioGroupProps } from "./types"; diff --git a/app/client/packages/design-system/widgets/src/components/Radio/src/styles.module.css b/app/client/packages/design-system/widgets/src/components/RadioGroup/src/styles.module.css similarity index 60% rename from app/client/packages/design-system/widgets/src/components/Radio/src/styles.module.css rename to app/client/packages/design-system/widgets/src/components/RadioGroup/src/styles.module.css index 8e43021eae..4da345ac74 100644 --- a/app/client/packages/design-system/widgets/src/components/Radio/src/styles.module.css +++ b/app/client/packages/design-system/widgets/src/components/RadioGroup/src/styles.module.css @@ -1,8 +1,29 @@ -.radio { - position: relative; - padding-inline-start: calc(var(--sizing-5) + var(--inner-spacing-2)); +.radioGroup { + display: flex; + flex-direction: column; + width: 100%; - [data-icon] { + &[data-orientation="vertical"] { + align-items: start; + + .radio { + margin-inline-end: auto; + } + } + + &[data-disabled] { + cursor: not-allowed; + } +} + +.radio { + display: flex; + align-items: center; + gap: var(--inner-spacing-2); + cursor: pointer; + min-width: fit-content; + + &:before { --radio-border-width: var(--border-width-2); --radio-border-color: var(--color-bd-neutral); /* Note: we are using box-shadow as the border to avoid the border from @@ -10,12 +31,7 @@ --radio-box-shadow: 0px 0px 0px var(--radio-border-width) var(--radio-border-color) inset; - /** - Checkbox icon are positioned absolutely because we need to align the elements along the baseline - but icon takes more space than the text content. - */ - position: absolute; - left: 0; + content: ""; width: var(--sizing-5); height: var(--sizing-5); box-shadow: var(--radio-box-shadow); @@ -28,7 +44,7 @@ flex-shrink: 0; } - &[data-hovered]:not([data-disabled]) [data-icon] { + &[data-hovered]:not([data-disabled="true"]):before { --radio-border-color: var(--color-bd-neutral-hover); } @@ -37,14 +53,14 @@ * CHECKED AND INDETERMINATE - BUT NOT DISABLED *----------------------------------------------------------------------------- */ - &[data-state="selected"] [data-icon] { + &[data-selected="true"]:before { --radio-border-color: var(--color-bg-accent); --radio-box-shadow: 0px 0px 0px 4px var(--color-bg-accent) inset; background: var(--color-fg-on-accent); } - &[data-hovered][data-state="selected"]:not([data-disabled]) [data-icon] { + &[data-hovered][data-selected="true"]:not([data-disabled="true"]):before { --radio-border-color: var(--color-bg-accent-hover); --radio-box-shadow: 0px 0px 0px 4px var(--color-bg-accent-hover) inset; @@ -56,7 +72,7 @@ * FOCUS *----------------------------------------------------------------------------- */ - &[data-focused] [data-icon] { + &[data-focus-visible]:before { box-shadow: var(--radio-box-shadow), 0 0 0 2px var(--color-bg), @@ -68,11 +84,29 @@ * ERROR ( INVALID ) *----------------------------------------------------------------------------- */ - &[data-invalid] [data-icon] { + &[data-invalid]:before { --radio-border-color: var(--color-bd-negative); } - &[data-hovered][data-invalid] [data-icon] { + &[data-hovered][data-invalid]:before { --radio-border-color: var(--color-bd-negative-hover); } + + &[data-invalid][data-selected="true"]:before { + --radio-box-shadow: 0px 0px 0px 4px var(--color-bg-negative) inset; + } + + &[data-hovered][data-invalid][data-selected="true"]:before { + --radio-box-shadow: 0px 0px 0px 4px var(--color-bg-negative-hover) inset; + } + + /** + * ---------------------------------------------------------------------------- + * DISABLED + *----------------------------------------------------------------------------- + */ + &[data-disabled] { + opacity: var(--opacity-disabled); + cursor: not-allowed; + } } diff --git a/app/client/packages/design-system/widgets/src/components/RadioGroup/src/types.ts b/app/client/packages/design-system/widgets/src/components/RadioGroup/src/types.ts new file mode 100644 index 0000000000..dd2260c32e --- /dev/null +++ b/app/client/packages/design-system/widgets/src/components/RadioGroup/src/types.ts @@ -0,0 +1,34 @@ +import type { RadioGroupProps as HeadlessRadioGroupProps } from "react-aria-components"; +import type { ORIENTATION } from "../../../shared"; + +interface RadioGroupItemProps { + value: string; + label?: string; + isSelected?: boolean; + isDisabled?: boolean; + index?: number; +} + +export interface RadioGroupProps extends HeadlessRadioGroupProps { + /** + * A ContextualHelp element to place next to the label. + */ + contextualHelp?: string; + /** + * The content to display as the label. + */ + label?: string; + /** + * Radio that belong to this group. + */ + items: RadioGroupItemProps[]; + /** + * The axis the checkboxes should align with. + * @default 'horizontal' + */ + orientation?: keyof typeof ORIENTATION; + /** + * An error message for the field. + */ + errorMessage?: string; +} diff --git a/app/client/packages/design-system/widgets/src/components/RadioGroup/stories/RadioGroup.stories.tsx b/app/client/packages/design-system/widgets/src/components/RadioGroup/stories/RadioGroup.stories.tsx index 7b2670ecec..2fab4f0c1c 100644 --- a/app/client/packages/design-system/widgets/src/components/RadioGroup/stories/RadioGroup.stories.tsx +++ b/app/client/packages/design-system/widgets/src/components/RadioGroup/stories/RadioGroup.stories.tsx @@ -1,6 +1,6 @@ import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; -import { RadioGroup, Radio, Flex } from "@design-system/widgets"; +import { RadioGroup, Flex } from "@design-system/widgets"; /** * Radio group is a component that allows users to select one option from a set of options. @@ -13,17 +13,18 @@ const meta: Meta = { export default meta; type Story = StoryObj; +const items = [ + { label: "Value 1", value: "value-1" }, + { label: "Value 2", value: "value-2" }, +]; + export const Main: Story = { args: { label: "Radio Group", defaultValue: "value-1", + items: items, }, - render: (args) => ( - - Value 1 - Value 2 - - ), + render: (args) => , }; /** @@ -32,14 +33,8 @@ export const Main: Story = { export const Orientation: Story = { render: () => ( - - Value 1 - Value 2 - - - Value 1 - Value 2 - + + ), }; @@ -49,13 +44,9 @@ export const Disabled: Story = { label: "Radio Group", defaultValue: "value-1", isDisabled: true, + items: items, }, - render: (args) => ( - - Value 1 - Value 2 - - ), + render: (args) => , }; export const Required: Story = { @@ -63,25 +54,17 @@ export const Required: Story = { label: "Radio Group", defaultValue: "value-1", isRequired: true, + items: items, }, - render: (args) => ( - - Value 1 - Value 2 - - ), + render: (args) => , }; export const Invalid: Story = { args: { label: "Radio Group", - validationState: "invalid", + isInvalid: true, errorMessage: "This is a error message", + items: items, }, - render: (args) => ( - - Value 1 - Value 2 - - ), + render: (args) => , }; diff --git a/app/client/packages/design-system/widgets/src/components/RadioGroup/tests/RadioGroup.test.tsx b/app/client/packages/design-system/widgets/src/components/RadioGroup/tests/RadioGroup.test.tsx index 184dae5435..9fa7719150 100644 --- a/app/client/packages/design-system/widgets/src/components/RadioGroup/tests/RadioGroup.test.tsx +++ b/app/client/packages/design-system/widgets/src/components/RadioGroup/tests/RadioGroup.test.tsx @@ -2,17 +2,17 @@ import React from "react"; import "@testing-library/jest-dom"; import userEvent from "@testing-library/user-event"; import { render, screen } from "@testing-library/react"; - -import { RadioGroup } from "../"; -import { Radio } from "../../Radio"; +import { RadioGroup } from "@design-system/widgets"; describe("@design-system/widgets/RadioGroup", () => { + const items = [ + { label: "Value 1", value: "value-1" }, + { label: "Value 2", value: "value-2" }, + ]; + it("should render the Radio group", async () => { const { container } = render( - - Value 1 - Value 2 - , + , ); expect(screen.getByText("Value 1")).toBeInTheDocument(); @@ -43,11 +43,11 @@ describe("@design-system/widgets/RadioGroup", () => { it("should support custom props", () => { render( - - Value 1 - Value 2 - Value 3 - , + , ); const radioGroup = screen.getByTestId("t--radio-group"); @@ -56,10 +56,7 @@ describe("@design-system/widgets/RadioGroup", () => { it("should render checked checkboxes when value is passed", () => { render( - - Value 1 - Value 2 - , + , ); const options = screen.getAllByRole("radio"); @@ -71,10 +68,11 @@ describe("@design-system/widgets/RadioGroup", () => { const onChangeSpy = jest.fn(); render( - - Value 1 - Value 2 - , + , ); const options = screen.getAllByRole("radio"); @@ -83,12 +81,7 @@ describe("@design-system/widgets/RadioGroup", () => { }); it("should be able to render disabled checkboxes", () => { - render( - - Value 1 - Value 2 - , - ); + render(); const options = screen.getAllByRole("radio"); expect(options[0]).toBeDisabled(); diff --git a/app/client/packages/design-system/widgets/src/components/ToggleGroup/chromatic/Group.chromatic.stories.tsx b/app/client/packages/design-system/widgets/src/components/ToggleGroup/chromatic/Group.chromatic.stories.tsx index 5d05c32e92..3da1834c2f 100644 --- a/app/client/packages/design-system/widgets/src/components/ToggleGroup/chromatic/Group.chromatic.stories.tsx +++ b/app/client/packages/design-system/widgets/src/components/ToggleGroup/chromatic/Group.chromatic.stories.tsx @@ -6,7 +6,6 @@ import { RadioGroup, Switch, Checkbox, - Radio, } from "@design-system/widgets"; import { StoryGrid } from "@design-system/storybook"; @@ -28,11 +27,7 @@ const items = [ export const LightMode: Story = { render: () => ( - - Option 1 - Option 2 - Opion 3 - + {({ label, value }) => ( diff --git a/app/client/packages/design-system/widgets/src/components/ToggleGroup/src/ToggleGroup.tsx b/app/client/packages/design-system/widgets/src/components/ToggleGroup/src/ToggleGroup.tsx index cc01ef0f06..6dfb6e6048 100644 --- a/app/client/packages/design-system/widgets/src/components/ToggleGroup/src/ToggleGroup.tsx +++ b/app/client/packages/design-system/widgets/src/components/ToggleGroup/src/ToggleGroup.tsx @@ -1,7 +1,11 @@ -import { useGroupOrientation } from "@design-system/headless/src/hooks"; import React, { forwardRef, useRef } from "react"; import { CheckboxGroup as HeadlessToggleGroup } from "react-aria-components"; -import { Text, Label, Flex } from "@design-system/widgets"; +import { + ErrorMessage, + Label, + Flex, + useGroupOrientation, +} from "@design-system/widgets"; import styles from "./styles.module.css"; import type { ForwardedRef } from "react"; import type { ToggleGroupProps } from "./types"; @@ -14,6 +18,7 @@ const _ToggleGroup = ( children, contextualHelp, errorMessage, + isDisabled, isRequired, items, label, @@ -29,35 +34,26 @@ const _ToggleGroup = ( - {(Boolean(label) || Boolean(contextualHelp)) && ( - ); }; diff --git a/app/client/packages/design-system/widgets/src/components/ToggleGroup/src/styles.module.css b/app/client/packages/design-system/widgets/src/components/ToggleGroup/src/styles.module.css index cee1ae5f8a..63f5af4c38 100644 --- a/app/client/packages/design-system/widgets/src/components/ToggleGroup/src/styles.module.css +++ b/app/client/packages/design-system/widgets/src/components/ToggleGroup/src/styles.module.css @@ -7,11 +7,6 @@ cursor: not-allowed; } - &[data-disabled] .label { - cursor: not-allowed; - opacity: var(--opacity-disabled); - } - &[data-orientation="horizontal"] [data-label-position="end"] { margin-inline-end: 0; } @@ -20,7 +15,3 @@ width: fit-content; } } - -.error { - margin-block-start: var(--inner-spacing-2); -} diff --git a/app/client/packages/design-system/widgets/src/components/ToggleGroup/src/types.ts b/app/client/packages/design-system/widgets/src/components/ToggleGroup/src/types.ts index f95335c02e..cc6daacae6 100644 --- a/app/client/packages/design-system/widgets/src/components/ToggleGroup/src/types.ts +++ b/app/client/packages/design-system/widgets/src/components/ToggleGroup/src/types.ts @@ -5,6 +5,7 @@ interface ToggleGroupItemProps { value: string; label?: string; isSelected?: boolean; + isDisabled?: boolean; index?: number; } diff --git a/app/client/packages/design-system/headless/src/hooks/index.ts b/app/client/packages/design-system/widgets/src/hooks/index.ts similarity index 100% rename from app/client/packages/design-system/headless/src/hooks/index.ts rename to app/client/packages/design-system/widgets/src/hooks/index.ts diff --git a/app/client/packages/design-system/headless/src/hooks/useGroupOrientation.ts b/app/client/packages/design-system/widgets/src/hooks/useGroupOrientation.ts similarity index 100% rename from app/client/packages/design-system/headless/src/hooks/useGroupOrientation.ts rename to app/client/packages/design-system/widgets/src/hooks/useGroupOrientation.ts diff --git a/app/client/packages/design-system/widgets/src/index.ts b/app/client/packages/design-system/widgets/src/index.ts index bbe17a4cc1..deb079b734 100644 --- a/app/client/packages/design-system/widgets/src/index.ts +++ b/app/client/packages/design-system/widgets/src/index.ts @@ -7,7 +7,6 @@ export * from "./components/Text"; export * from "./components/ToggleGroup"; export * from "./components/Tooltip"; export * from "./components/Flex"; -export * from "./components/Radio"; export * from "./components/RadioGroup"; export * from "./components/Switch"; export * from "./components/TextInput"; @@ -23,8 +22,9 @@ export * from "./components/ContextualHelp"; export * from "./components/Link"; export * from "./components/Popover"; export * from "./components/Label"; +export * from "./components/ErrorMessage"; export * from "./utils"; export * from "./styles"; - +export * from "./hooks"; export * from "./shared"; diff --git a/app/client/packages/design-system/widgets/src/testing/ComplexForm.tsx b/app/client/packages/design-system/widgets/src/testing/ComplexForm.tsx index 0b8a755508..ee2ff6d668 100644 --- a/app/client/packages/design-system/widgets/src/testing/ComplexForm.tsx +++ b/app/client/packages/design-system/widgets/src/testing/ComplexForm.tsx @@ -12,7 +12,6 @@ import { Flex, Switch, RadioGroup, - Radio, IconButton, TextArea, Modal, @@ -105,12 +104,27 @@ export const ComplexForm = () => { )} - - S - M - L - XL - + diff --git a/app/client/packages/storybook/src/components/DataAttrWrapper.tsx b/app/client/packages/storybook/src/components/DataAttrWrapper.tsx index 34829096e8..d89ff6b278 100644 --- a/app/client/packages/storybook/src/components/DataAttrWrapper.tsx +++ b/app/client/packages/storybook/src/components/DataAttrWrapper.tsx @@ -3,10 +3,11 @@ import React, { useEffect, useRef } from "react"; interface DataAttrWrapperProps { children: React.ReactNode; attr: string; + target?: string; } export const DataAttrWrapper = (props: DataAttrWrapperProps) => { - const { attr, children } = props; + const { attr, children, target } = props; // Adding any type here because WDS components has different types for ref // some are HTMLElement and some are objects only ( For e.g - CheckboxRef ) @@ -19,7 +20,11 @@ export const DataAttrWrapper = (props: DataAttrWrapperProps) => { Boolean(ref.current.setAttribute) && typeof ref.current.setAttribute === "function" ) { - ref.current.setAttribute(attr, ""); + if (Boolean(target)) { + ref.current.querySelector(target).setAttribute(attr, ""); + } else { + ref.current.setAttribute(attr, ""); + } return; } diff --git a/app/client/src/widgets/wds/WDSRadioGroupWidget/widget/index.tsx b/app/client/src/widgets/wds/WDSRadioGroupWidget/widget/index.tsx index c38b639359..3769be1a04 100644 --- a/app/client/src/widgets/wds/WDSRadioGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/wds/WDSRadioGroupWidget/widget/index.tsx @@ -6,7 +6,7 @@ import type { import isNumber from "lodash/isNumber"; import BaseWidget from "widgets/BaseWidget"; import type { WidgetState } from "widgets/BaseWidget"; -import { Radio, RadioGroup } from "@design-system/widgets"; +import { RadioGroup } from "@design-system/widgets"; import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; @@ -122,8 +122,7 @@ class WDSRadioGroupWidget extends BaseWidget< }; getWidgetView() { - const { labelTooltip, options, selectedOptionValue, widgetId, ...rest } = - this.props; + const { labelTooltip, options, selectedOptionValue, ...rest } = this.props; const validation = validateInput(this.props); @@ -132,16 +131,11 @@ class WDSRadioGroupWidget extends BaseWidget< {...rest} contextualHelp={labelTooltip} errorMessage={validation.errorMessage} + isInvalid={validation.validationStatus === "invalid"} + items={options} onChange={this.onRadioSelectionChange} - validationState={validation.validationStatus} value={selectedOptionValue} - > - {options.map((option, index) => ( - - {option.label} - - ))} - + /> ); } } diff --git a/app/client/src/widgets/wds/WDSRadioGroupWidget/widget/types.ts b/app/client/src/widgets/wds/WDSRadioGroupWidget/widget/types.ts index e0a68ac38f..266a0e7812 100644 --- a/app/client/src/widgets/wds/WDSRadioGroupWidget/widget/types.ts +++ b/app/client/src/widgets/wds/WDSRadioGroupWidget/widget/types.ts @@ -1,13 +1,8 @@ import type { RadioGroupProps } from "@design-system/widgets"; import type { WidgetProps } from "widgets/BaseWidget"; -export interface RadioOption { - label: string; - value: string; -} - export interface RadioGroupWidgetProps extends WidgetProps { - options: RadioOption[]; + options: RadioGroupProps["items"]; selectedOptionValue: string; onSelectionChange: string; defaultOptionValue: string;