diff --git a/.changeset/hip-jokes-look.md b/.changeset/hip-jokes-look.md new file mode 100644 index 00000000..8308b101 --- /dev/null +++ b/.changeset/hip-jokes-look.md @@ -0,0 +1,5 @@ +--- +"@spear-ai/ui": minor +--- + +Created Checkbox components. diff --git a/packages/ui/src/components/checkbox/checkbox.stories.tsx b/packages/ui/src/components/checkbox/checkbox.stories.tsx index d00970b0..289ed875 100644 --- a/packages/ui/src/components/checkbox/checkbox.stories.tsx +++ b/packages/ui/src/components/checkbox/checkbox.stories.tsx @@ -1,7 +1,20 @@ -import { CheckIcon, MinusIcon } from "@radix-ui/react-icons"; import type { Meta, StoryObj } from "@storybook/react"; -import { Checkbox, CheckboxGroup, Form, Label } from "react-aria-components"; +import { Form } from "react-aria-components"; import { useIntl } from "react-intl"; +import { + Checkbox, + CheckboxButton, + CheckboxCheckedIcon, + CheckboxDescription, + CheckboxField, + CheckboxFields, + CheckboxGroup, + CheckboxGroupDescription, + CheckboxGroupError, + CheckboxGroupLabel, + CheckboxIndeterminateIcon, + CheckboxLabel, +} from "./checkbox"; const PreviewCheckbox = (properties: { firstOptionIsDisabled: boolean; @@ -33,7 +46,7 @@ const PreviewCheckbox = (properties: {
{hasGroupLabel ? ( - + ) : null} {hasGroupLabel && hasGroupLabelDescription ? ( -

+ {intl.formatMessage({ defaultMessage: "Control your post’s privacy settings.", id: "dBZIgO", })} -

+ ) : null} -
- -
- - -
- - {intl.formatMessage({ - defaultMessage: "Show only to friends", - id: "709cRj", - })} - -
- {hasLabelDescription ? ( -

- {intl.formatMessage({ - defaultMessage: "Whether only friends can see this post.", - id: "WuPCNC", - })} -

- ) : null} -
-
- -
- - -
- - {intl.formatMessage({ - defaultMessage: "Allow sharing", - id: "MmQn2H", - })} - -
- {hasLabelDescription ? ( -

- {intl.formatMessage({ - defaultMessage: "Whether this post can be shared by others.", - id: "qysWYY", - })} -

- ) : null} -
+ + + + + + + + + {intl.formatMessage({ + defaultMessage: "Show only to friends", + id: "709cRj", + })} + + + {hasLabelDescription ? ( + + {intl.formatMessage({ + defaultMessage: "Whether only friends can see this post.", + id: "WuPCNC", + })} + + ) : null} + + + + + + + + + {intl.formatMessage({ + defaultMessage: "Allow sharing", + id: "MmQn2H", + })} + + + {hasLabelDescription ? ( + + {intl.formatMessage({ + defaultMessage: "Whether this post can be shared by others.", + id: "qysWYY", + })} + + ) : null} + + {groupIsInvalid ? ( -

+ {intl.formatMessage({ defaultMessage: "There’s a problem with the selection", id: "Nfuwo5", })} -

+ ) : null}
diff --git a/packages/ui/src/components/checkbox/checkbox.tsx b/packages/ui/src/components/checkbox/checkbox.tsx new file mode 100644 index 00000000..168d8235 --- /dev/null +++ b/packages/ui/src/components/checkbox/checkbox.tsx @@ -0,0 +1,171 @@ +/* eslint-disable react/jsx-props-no-spreading */ +import { CheckIcon, MinusIcon } from "@radix-ui/react-icons"; +import { Slot } from "@radix-ui/react-slot"; +import { ComponentPropsWithoutRef, ElementRef, forwardRef, HTMLAttributes, SVGAttributes } from "react"; +import { + Checkbox as CheckboxPrimitive, + CheckboxGroup as CheckboxGroupPrimitive, + Label as LabelPrimitive, +} from "react-aria-components"; +import { cx } from "@/helpers/cx"; + +export const CheckboxGroup = forwardRef< + ElementRef, + ComponentPropsWithoutRef & { className?: string | undefined } +>(({ className, ...properties }, reference) => { + const mergedClassName = cx("group/group", className); + return ; +}); + +CheckboxGroup.displayName = "CheckboxGroup"; + +export const CheckboxGroupLabel = forwardRef< + ElementRef, + ComponentPropsWithoutRef & { className?: string | undefined } +>(({ className, ...properties }, reference) => { + const mergedClassName = cx( + "block select-none text-base/6 text-neutral-12 group-disabled/group:text-neutral-11 sm:text-sm/6", + className, + ); + return ( + + ); +}); + +CheckboxGroupLabel.displayName = "CheckboxGroupLabel"; + +export const CheckboxGroupDescription = forwardRef< + HTMLParagraphElement, + HTMLAttributes & { className?: string | undefined } +>(({ className, ...properties }, reference) => { + const mergedClassName = cx( + "mt-1 text-base/6 text-neutral-11 group-disabled/group:text-neutral-9 sm:text-sm/6", + className, + ); + return

; +}); + +CheckboxGroupLabel.displayName = "CheckboxGroupDescription"; + +export const CheckboxGroupError = forwardRef< + HTMLParagraphElement, + HTMLAttributes & { className?: string | undefined } +>(({ className, ...properties }, reference) => { + const mergedClassName = cx( + "mt-3 text-sm text-x-negative-11 group-disabled/group:text-x-negative-9", + className, + ); + return

; +}); + +CheckboxGroupError.displayName = "CheckboxGroupError"; + +export const CheckboxFields = forwardRef< + HTMLDivElement, + HTMLAttributes & { asChild?: boolean | undefined; className?: string | undefined } +>(({ asChild = false, className, ...properties }, reference) => { + const Component = asChild ? Slot : "div"; + const mergedClassName = cx( + "space-y-3 group-has-[[data-slot=group-description]]/group:mt-3 group-has-[[data-slot=group-label]]/group:mt-3", + className, + ); + return ; +}); + +CheckboxFields.displayName = "CheckboxFields"; + +export const CheckboxField = forwardRef< + HTMLDivElement, + HTMLAttributes & { asChild?: boolean | undefined; className?: string | undefined } +>(({ asChild = false, ...properties }, reference) => { + const Component = asChild ? Slot : "div"; + return ; +}); + +CheckboxField.displayName = "CheckboxField"; + +export const Checkbox = forwardRef< + ElementRef, + ComponentPropsWithoutRef & { + className?: string | undefined; + isPrimary?: boolean | undefined; + } +>(({ className, isPrimary = false, ...properties }, reference) => { + const mergedClassName = cx("group peer inline-flex", className); + return ( + + ); +}); + +Checkbox.displayName = "Checkbox"; + +export const CheckboxLabel = forwardRef< + HTMLSpanElement, + HTMLAttributes & { className?: string | undefined } +>(({ className, ...properties }, reference) => { + const mergedClassName = cx( + "-mt-1 flex-1 text-sm font-medium leading-6 text-neutral-12 group-invalid:text-x-negative-11 group-disabled:text-neutral-11 group-invalid:group-disabled:text-x-negative-9", + className, + ); + return ; +}); + +CheckboxLabel.displayName = "CheckboxLabel"; + +export const CheckboxDescription = forwardRef< + HTMLParagraphElement, + HTMLAttributes & { className?: string | undefined } +>(({ className, ...properties }, reference) => { + const mergedClassName = cx( + "ms-7 text-sm leading-6 text-neutral-11 peer-disabled:text-neutral-9", + className, + ); + return

; +}); + +CheckboxLabel.displayName = "CheckboxDescription"; + +export const CheckboxButton = forwardRef< + HTMLSpanElement, + HTMLAttributes & { className?: string | undefined } +>(({ className, ...properties }, reference) => { + const mergedClassName = cx( + "relative me-3 inline-flex size-4 items-center rounded border border-neutral-a-7 bg-white-a-3 text-neutral-12 group-invalid:border-x-negative-a-7 group-focus-visible:outline group-focus-visible:outline-2 group-focus-visible:outline-primary-7 group-selected:border-transparent group-selected:bg-primary-9 group-selected:text-primary-contrast group-invalid:group-selected:border-x-negative-a-7 group-disabled:border-neutral-a-6 group-disabled:bg-neutral-a-3 group-disabled:text-neutral-a-8 group-invalid:group-disabled:border-x-negative-a-6 theme-dfs:bg-canvas-1 theme-dfs:group-invalid:border-x-negative-a-7 theme-dfs:group-disabled:bg-neutral-a-3 theme-forerunner:group-selected:bg-black-a-12 theme-forerunner:group-selected:text-white theme-forerunner:group-disabled:border-neutral-a-6 theme-forerunner:group-disabled:bg-neutral-a-3 theme-forerunner:group-disabled:text-neutral-a-8 theme-forerunner:group-invalid:group-disabled:border-x-negative-a-6 theme-galapago:bg-white dark:bg-white-a-3 theme-dfs:dark:bg-white-a-3 theme-dfs:group-selected:dark:border-neutral-a-7 theme-dfs:group-invalid:group-selected:dark:border-x-negative-a-7 theme-forerunner:dark:bg-black-a-3 theme-forerunner:group-selected:dark:border-neutral-a-7 theme-forerunner:group-selected:dark:bg-primary-a-5 theme-forerunner:group-selected:dark:text-primary-12 theme-forerunner:group-invalid:group-selected:dark:border-x-negative-a-7 theme-forerunner:group-disabled:dark:border-neutral-a-6 theme-forerunner:group-disabled:dark:bg-neutral-a-3 theme-forerunner:group-disabled:dark:text-neutral-a-8 theme-forerunner:group-invalid:group-disabled:dark:border-x-negative-a-6 theme-galapago:dark:bg-black-a-3 theme-galapago:dark:group-selected:bg-primary-a-9 theme-galapago:dark:disabled:bg-neutral-a-3 theme-galapago:group-disabled:dark:bg-neutral-a-3 theme-galapago:group-disabled:dark:text-neutral-a-8", + className, + ); + return ; +}); + +CheckboxButton.displayName = "CheckboxButton"; + +export const CheckboxCheckedIcon = forwardRef< + SVGSVGElement, + SVGAttributes & { asChild?: boolean | undefined; className?: string | undefined } +>(({ asChild = false, className, ...properties }, reference) => { + const Component = asChild ? Slot : CheckIcon; + const mergedClassName = cx( + "absolute -left-px -top-px size-4 opacity-0 group-indeterminate:!opacity-0 group-selected:opacity-100", + className, + ); + // @ts-expect-error the Slot component’s type definition doesn’t play nice with SVGs + return ; +}); + +CheckboxCheckedIcon.displayName = "CheckboxCheckedIcon"; + +export const CheckboxIndeterminateIcon = forwardRef< + SVGSVGElement, + SVGAttributes & { asChild?: boolean | undefined; className?: string | undefined } +>(({ asChild = false, className, ...properties }, reference) => { + const Component = asChild ? Slot : MinusIcon; + const mergedClassName = cx("h-full opacity-0 group-indeterminate:group-selected:opacity-100", className); + // @ts-expect-error the Slot component’s type definition doesn’t play nice with SVGs + return ; +}); + +CheckboxIndeterminateIcon.displayName = "CheckboxIndeterminateIcon";