diff --git a/packages/dev/s2-docs/pages/react-aria/CheckboxGroup.mdx b/packages/dev/s2-docs/pages/react-aria/CheckboxGroup.mdx index 18affe4b160..24a483bebaa 100644 --- a/packages/dev/s2-docs/pages/react-aria/CheckboxGroup.mdx +++ b/packages/dev/s2-docs/pages/react-aria/CheckboxGroup.mdx @@ -31,9 +31,10 @@ export const description = 'Allows a user to select multiple items from a list o ``` - ```tsx render docs={vanillaDocs.exports.CheckboxGroup} links={vanillaDocs.links} props={['label', 'description', 'isDisabled']} initialProps={{label: 'Favorite sports'}} type="tailwind" files={["starters/tailwind/src/Checkbox.tsx"]} + ```tsx render docs={vanillaDocs.exports.CheckboxGroup} links={vanillaDocs.links} props={['label', 'description', 'isDisabled']} initialProps={{label: 'Favorite sports'}} type="tailwind" files={["starters/tailwind/src/CheckboxGroup.tsx"]} "use client"; - import {CheckboxGroup, Checkbox} from 'tailwind-starter/Checkbox'; + import {CheckboxGroup} from 'tailwind-starter/CheckboxGroup'; + import {Checkbox} from 'tailwind-starter/Checkbox'; Soccer diff --git a/packages/dev/s2-docs/pages/react-aria/DisclosureGroup.mdx b/packages/dev/s2-docs/pages/react-aria/DisclosureGroup.mdx index 18e6026a831..cf44222889e 100644 --- a/packages/dev/s2-docs/pages/react-aria/DisclosureGroup.mdx +++ b/packages/dev/s2-docs/pages/react-aria/DisclosureGroup.mdx @@ -31,9 +31,10 @@ export const description = 'A grouping of related disclosures, sometimes called ``` - ```tsx render docs={vanillaDocs.exports.DisclosureGroup} links={vanillaDocs.links} props={['allowsMultipleExpanded', 'isDisabled']} type="tailwind" files={["starters/tailwind/src/Disclosure.tsx"]} + ```tsx render docs={vanillaDocs.exports.DisclosureGroup} links={vanillaDocs.links} props={['allowsMultipleExpanded', 'isDisabled']} type="tailwind" files={["starters/tailwind/src/DisclosureGroup.tsx"]} "use client"; - import {DisclosureGroup, Disclosure, DisclosureHeader, DisclosurePanel} from 'tailwind-starter/Disclosure'; + import {DisclosureGroup} from 'tailwind-starter/DisclosureGroup'; + import {Disclosure, DisclosureHeader, DisclosurePanel} from 'tailwind-starter/Disclosure'; diff --git a/packages/dev/s2-docs/pages/react-aria/FileTrigger.mdx b/packages/dev/s2-docs/pages/react-aria/FileTrigger.mdx index b957935f4bc..8dce090e681 100644 --- a/packages/dev/s2-docs/pages/react-aria/FileTrigger.mdx +++ b/packages/dev/s2-docs/pages/react-aria/FileTrigger.mdx @@ -12,7 +12,7 @@ export const description = 'Allows a user to access the file system with any pre {docs.exports.FileTrigger.description} -```tsx render docs={docs.exports.FileTrigger} links={docs.links} props={['acceptedFileTypes', 'allowsMultiple', 'acceptDirectory', 'defaultCamera']} initialProps={{acceptedFileTypes: ['image/*']}} type="vanilla" +```tsx render docs={docs.exports.FileTrigger} links={docs.links} props={['acceptedFileTypes', 'allowsMultiple', 'acceptDirectory', 'defaultCamera']} initialProps={{acceptedFileTypes: ['image/*']}} type="vanilla" hideShadcn "use client"; import {FileTrigger} from 'react-aria-components'; import {Button} from 'vanilla-starter/Button'; diff --git a/packages/dev/s2-docs/pages/react-aria/getting-started.mdx b/packages/dev/s2-docs/pages/react-aria/getting-started.mdx index 75bb9f0abfc..22f75a589c4 100644 --- a/packages/dev/s2-docs/pages/react-aria/getting-started.mdx +++ b/packages/dev/s2-docs/pages/react-aria/getting-started.mdx @@ -28,7 +28,7 @@ Install React Aria with your preferred package manager. ## Quick start -The documentation for each component includes vanilla CSS and [Tailwind](https://tailwindcss.com) examples. Copy and paste these into your project and make them your own. You can also download each example as a ZIP or open in StackBlitz. +Copy and paste the CSS or [Tailwind](https://tailwindcss.com) examples into your project and make them your own. You can also download each example as a ZIP, open in StackBlitz, or install with [shadcn](https://ui.shadcn.com/docs/cli). ```tsx render docs={docs.exports.Select} links={docs.links} props={[]} type="vanilla" files={["starters/docs/src/Select.tsx", "starters/docs/src/Select.css"]} showCoachMark @@ -63,9 +63,9 @@ The documentation for each component includes vanilla CSS and [Tailwind](https:/ ### shadcn CLI -Each example can also be installed with the [shadcn](https://ui.shadcn.com/docs/cli) CLI. This will add the component source code, styles, and all dependencies to your project automatically. +Use the [shadcn](https://ui.shadcn.com/docs/cli) CLI to add the example code, styles, and dependencies to your project. Install individual components using the menu on each example, or add all components with the command below. - + ### Storybook starter kits diff --git a/packages/dev/s2-docs/src/ShadcnCommand.tsx b/packages/dev/s2-docs/src/ShadcnCommand.tsx index 0ec25969c85..43eeaeb8051 100644 --- a/packages/dev/s2-docs/src/ShadcnCommand.tsx +++ b/packages/dev/s2-docs/src/ShadcnCommand.tsx @@ -7,7 +7,7 @@ import Prompt from '@react-spectrum/s2/icons/Prompt'; import {RefObject} from 'react'; import {useLocalStorage} from './useLocalStorage'; -export function ShadcnCommand({type, component, preRef}: {type: 'vanilla' | 'tailwind', component: string, preRef?: RefObject}) { +export function ShadcnCommand({type, component, preRef}: {type?: 'vanilla' | 'tailwind', component?: string, preRef?: RefObject}) { let [packageManager, setPackageManager] = useLocalStorage('packageManager', 'npm'); let command = packageManager; if (packageManager === 'npm') { @@ -20,10 +20,12 @@ export function ShadcnCommand({type, component, preRef}: {type: 'vanilla' | 'tai setPackageManager(String(value)); }; - let shadcnType = type === 'vanilla' ? 'css' : type; + let [storedType, setStoredType] = useLocalStorage('style', type || 'Vanilla CSS'); + let shadcnType = type === 'vanilla' || storedType === 'Vanilla CSS' ? 'css' : 'tailwind'; + let componentName = component ? '-' + component.toLowerCase() : ''; let specifier = process.env.DOCS_ENV === 'prod' - ? `@react-aria/${shadcnType}-${component.toLowerCase()}` - : `${getBaseUrl('react-aria')}/registry/${shadcnType}-${component.toLowerCase()}.json`; + ? `@react-aria/${shadcnType}${componentName}` + : `${getBaseUrl('react-aria')}/registry/${shadcnType}${componentName}.json`; let cmd = `${command} shadcn@latest add ${specifier}`; @@ -37,11 +39,24 @@ export function ShadcnCommand({type, component, preRef}: {type: 'vanilla' | 'tai flexDirection: 'column', gap: 16 })}> - - npm - yarn - pnpm - +
+ + npm + yarn + pnpm + + {!type && + setStoredType(String(v))}> + Vanilla CSS + Tailwind + + } +
{ @@ -119,7 +120,7 @@ export interface PropControl extends Omit { /** * Displays a component example with controls for changing the props. */ -export function VisualExample({component, docs, links, importSource, props, initialProps, controlOptions, files, downloadFiles, code, wide, slots, align, acceptOrientation, type, propsObject, showCoachMark}: VisualExampleProps) { +export function VisualExample({component, docs, links, importSource, props, initialProps, controlOptions, files, downloadFiles, code, wide, slots, align, acceptOrientation, type, propsObject, showCoachMark, hideShadcn}: VisualExampleProps) { let componentProps = docs.type === 'interface' ? docs : docs.props; if (componentProps?.type !== 'interface') { return null; @@ -188,7 +189,7 @@ export function VisualExample({component, docs, links, importSource, props, init return ( - +
{props.length > 0 &&
diff --git a/packages/react-aria-components/src/ColorField.tsx b/packages/react-aria-components/src/ColorField.tsx index 6bfbe0a7249..98dd799e728 100644 --- a/packages/react-aria-components/src/ColorField.tsx +++ b/packages/react-aria-components/src/ColorField.tsx @@ -44,6 +44,16 @@ export interface ColorFieldRenderProps { * @selector [data-invalid] */ isInvalid: boolean, + /** + * Whether the color field is read only. + * @selector [data-readonly] + */ + isReadOnly: boolean, + /** + * Whether the color field is required. + * @selector [data-required] + */ + isRequired: boolean, /** * The color channel that this field edits, or "hex" if no `channel` prop is set. * @selector [data-channel="hex | hue | saturation | ..."] @@ -192,7 +202,9 @@ function useChildren( state, channel: props.channel || 'hex', isDisabled: props.isDisabled || false, - isInvalid: validation.isInvalid || false + isInvalid: validation.isInvalid || false, + isReadOnly: props.isReadOnly || false, + isRequired: props.isRequired || false }, defaultClassName: 'react-aria-ColorField' }); @@ -222,7 +234,9 @@ function useChildren( slot={props.slot || undefined} data-channel={props.channel || 'hex'} data-disabled={props.isDisabled || undefined} - data-invalid={validation.isInvalid || undefined} /> + data-invalid={validation.isInvalid || undefined} + data-readonly={props.isReadOnly || undefined} + data-required={props.isRequired || undefined} /> ); } diff --git a/packages/react-aria-components/test/ColorField.test.js b/packages/react-aria-components/test/ColorField.test.js index 7b53495c326..987d3c31ae6 100644 --- a/packages/react-aria-components/test/ColorField.test.js +++ b/packages/react-aria-components/test/ColorField.test.js @@ -84,6 +84,30 @@ describe('ColorField', () => { expect(input).toHaveValue(''); }); + it('should support read-only state', async () => { + let {getByRole, rerender} = render( + + ); + + let input = getByRole('textbox'); + + expect(input.closest('.react-aria-ColorField')).not.toHaveAttribute('data-readonly'); + rerender(); + expect(input.closest('.react-aria-ColorField')).toHaveAttribute('data-readonly'); + }); + + it('should support required state', async () => { + let {getByRole, rerender} = render( + + ); + + let input = getByRole('textbox'); + + expect(input.closest('.react-aria-ColorField')).not.toHaveAttribute('data-required'); + rerender(); + expect(input.closest('.react-aria-ColorField')).toHaveAttribute('data-required'); + }); + it('should render data- attributes only on the outer element', () => { let {getAllByTestId} = render( diff --git a/scripts/buildRegistry.mjs b/scripts/buildRegistry.mjs index 5c0e7611526..c5e4a626ae4 100644 --- a/scripts/buildRegistry.mjs +++ b/scripts/buildRegistry.mjs @@ -13,8 +13,15 @@ fs.rmSync(distDir, {recursive: true, force: true}); fs.mkdirSync(distDir, {recursive: true}); let items = []; - -for (let file of globSync('starters/tailwind/src/*.{ts,tsx}')) { +let tailwind = { + $schema: 'https://ui.shadcn.com/schema/registry-item.json', + name: 'tailwind', + type: 'registry:style', + registryDependencies: [] +}; +items.push(tailwind); + +for (let file of globSync('starters/tailwind/src/*.{ts,tsx}').sort()) { let name = path.basename(file, path.extname(file)); let {dependencies, registryDependencies, content} = analyzeDeps(file, 'tailwind'); let type = name === 'utils' ? 'registry:lib' : 'registry:ui'; @@ -45,9 +52,20 @@ for (let file of globSync('starters/tailwind/src/*.{ts,tsx}')) { } items.push(item); + tailwind.registryDependencies.push(`${publicUrl}/tailwind-${name.toLowerCase()}.json`); } -for (let file of globSync('starters/docs/src/*.{ts,tsx}')) { +fs.writeFileSync(path.join(distDir, 'tailwind.json'), JSON.stringify(tailwind, null, 2) + '\n'); + +let css = { + $schema: 'https://ui.shadcn.com/schema/registry-item.json', + name: 'css', + type: 'registry:style', + registryDependencies: [] +}; +items.push(css); + +for (let file of globSync('starters/docs/src/*.{ts,tsx}').sort()) { let name = path.basename(file, path.extname(file)); let {dependencies, registryDependencies, content} = analyzeDeps(file, 'css'); let type = name === 'utils' ? 'registry:lib' : 'registry:ui'; @@ -79,8 +97,10 @@ for (let file of globSync('starters/docs/src/*.{ts,tsx}')) { } items.push(item); + css.registryDependencies.push(`${publicUrl}/css-${name.toLowerCase()}.json`); } +fs.writeFileSync(path.join(distDir, 'css.json'), JSON.stringify(css, null, 2) + '\n'); fs.writeFileSync(path.join(distDir, 'registry.json'), JSON.stringify({ '$schema': 'https://ui.shadcn.com/schema/registry.json', name: 'react-aria', diff --git a/starters/tailwind/src/Checkbox.tsx b/starters/tailwind/src/Checkbox.tsx index c87371232ea..fe4afef5027 100644 --- a/starters/tailwind/src/Checkbox.tsx +++ b/starters/tailwind/src/Checkbox.tsx @@ -1,28 +1,9 @@ 'use client'; import { Check, Minus } from 'lucide-react'; -import React, { ReactNode } from 'react'; -import { Checkbox as AriaCheckbox, CheckboxGroup as AriaCheckboxGroup, CheckboxGroupProps as AriaCheckboxGroupProps, CheckboxProps, ValidationResult, composeRenderProps } from 'react-aria-components'; +import React from 'react'; +import { Checkbox as AriaCheckbox, CheckboxProps, composeRenderProps } from 'react-aria-components'; import { tv } from 'tailwind-variants'; -import { Description, FieldError, Label } from './Field'; -import { composeTailwindRenderProps, focusRing } from './utils'; - -export interface CheckboxGroupProps extends Omit { - label?: string, - children?: ReactNode, - description?: string; - errorMessage?: string | ((validation: ValidationResult) => string); -} - -export function CheckboxGroup(props: CheckboxGroupProps) { - return ( - - - {props.children} - {props.description && {props.description}} - {props.errorMessage} - - ); -} +import { focusRing } from './utils'; const checkboxStyles = tv({ base: 'flex gap-2 items-center group font-sans text-sm transition relative [-webkit-tap-highlight-color:transparent]', diff --git a/starters/tailwind/src/CheckboxGroup.tsx b/starters/tailwind/src/CheckboxGroup.tsx new file mode 100644 index 00000000000..524b1ce9eed --- /dev/null +++ b/starters/tailwind/src/CheckboxGroup.tsx @@ -0,0 +1,23 @@ +'use client'; +import React, { ReactNode } from 'react'; +import { CheckboxGroup as AriaCheckboxGroup, CheckboxGroupProps as AriaCheckboxGroupProps, ValidationResult } from 'react-aria-components'; +import { Description, FieldError, Label } from './Field'; +import { composeTailwindRenderProps } from './utils'; + +export interface CheckboxGroupProps extends Omit { + label?: string, + children?: ReactNode, + description?: string, + errorMessage?: string | ((validation: ValidationResult) => string) +} + +export function CheckboxGroup(props: CheckboxGroupProps) { + return ( + + + {props.children} + {props.description && {props.description}} + {props.errorMessage} + + ); +} diff --git a/starters/tailwind/src/Disclosure.tsx b/starters/tailwind/src/Disclosure.tsx index b4562ddf5e4..e184a451dfe 100644 --- a/starters/tailwind/src/Disclosure.tsx +++ b/starters/tailwind/src/Disclosure.tsx @@ -2,9 +2,7 @@ import React, { useContext } from "react"; import { Disclosure as AriaDisclosure, - DisclosureGroup as AriaDisclosureGroup, DisclosureProps as AriaDisclosureProps, - DisclosureGroupProps as AriaDisclosureGroupProps, DisclosurePanel as AriaDisclosurePanel, DisclosurePanelProps as AriaDisclosurePanelProps, composeRenderProps, @@ -81,15 +79,3 @@ export function DisclosurePanel({ children, ...props }: DisclosurePanelProps) { ); } - -export interface DisclosureGroupProps extends AriaDisclosureGroupProps { - children: React.ReactNode; -} - -export function DisclosureGroup({ children, ...props }: DisclosureGroupProps) { - return ( - - {children} - - ); -} diff --git a/starters/tailwind/src/DisclosureGroup.tsx b/starters/tailwind/src/DisclosureGroup.tsx new file mode 100644 index 00000000000..c2cfd1b2c6d --- /dev/null +++ b/starters/tailwind/src/DisclosureGroup.tsx @@ -0,0 +1,18 @@ +'use client'; +import React from "react"; +import { + DisclosureGroup as AriaDisclosureGroup, + DisclosureGroupProps as AriaDisclosureGroupProps, +} from "react-aria-components"; + +export interface DisclosureGroupProps extends AriaDisclosureGroupProps { + children: React.ReactNode +} + +export function DisclosureGroup({ children, ...props }: DisclosureGroupProps) { + return ( + + {children} + + ); +} diff --git a/starters/tailwind/stories/CheckboxGroup.stories.tsx b/starters/tailwind/stories/CheckboxGroup.stories.tsx index 19e39c5a28c..bdffcbd9d42 100644 --- a/starters/tailwind/stories/CheckboxGroup.stories.tsx +++ b/starters/tailwind/stories/CheckboxGroup.stories.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { Form } from 'react-aria-components'; import { Button } from '../src/Button'; -import { Checkbox, CheckboxGroup } from '../src/Checkbox'; +import { Checkbox } from '../src/Checkbox'; +import { CheckboxGroup } from '../src/CheckboxGroup'; export default { title: 'CheckboxGroup', diff --git a/starters/tailwind/stories/DisclosureGroup.stories.tsx b/starters/tailwind/stories/DisclosureGroup.stories.tsx index 56e9446e82b..a8088d21d7d 100644 --- a/starters/tailwind/stories/DisclosureGroup.stories.tsx +++ b/starters/tailwind/stories/DisclosureGroup.stories.tsx @@ -3,9 +3,9 @@ import React from "react"; import { Disclosure, DisclosureHeader, - DisclosurePanel, - DisclosureGroup, + DisclosurePanel } from "../src/Disclosure"; +import { DisclosureGroup } from "../src/DisclosureGroup"; const meta: Meta = { component: DisclosureGroup,