Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/dev/s2-docs/pages/react-aria/CheckboxGroup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ export const description = 'Allows a user to select multiple items from a list o
</CheckboxGroup>
```

```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';

<CheckboxGroup/* PROPS */>
<Checkbox value="soccer">Soccer</Checkbox>
Expand Down
5 changes: 3 additions & 2 deletions packages/dev/s2-docs/pages/react-aria/DisclosureGroup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ export const description = 'A grouping of related disclosures, sometimes called
</DisclosureGroup>
```

```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';

<DisclosureGroup/* PROPS */>
<Disclosure>
Expand Down
2 changes: 1 addition & 1 deletion packages/dev/s2-docs/pages/react-aria/FileTrigger.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const description = 'Allows a user to access the file system with any pre

<PageDescription>{docs.exports.FileTrigger.description}</PageDescription>

```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';
Expand Down
6 changes: 3 additions & 3 deletions packages/dev/s2-docs/pages/react-aria/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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).

<ExampleSwitcher>
```tsx render docs={docs.exports.Select} links={docs.links} props={[]} type="vanilla" files={["starters/docs/src/Select.tsx", "starters/docs/src/Select.css"]} showCoachMark
Expand Down Expand Up @@ -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.

<ShadcnCommand type="vanilla" component="Select" />
<ShadcnCommand />

### Storybook starter kits

Expand Down
33 changes: 24 additions & 9 deletions packages/dev/s2-docs/src/ShadcnCommand.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLPreElement | null>}) {
export function ShadcnCommand({type, component, preRef}: {type?: 'vanilla' | 'tailwind', component?: string, preRef?: RefObject<HTMLPreElement | null>}) {
let [packageManager, setPackageManager] = useLocalStorage('packageManager', 'npm');
let command = packageManager;
if (packageManager === 'npm') {
Expand All @@ -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}`;

Expand All @@ -37,11 +39,24 @@ export function ShadcnCommand({type, component, preRef}: {type: 'vanilla' | 'tai
flexDirection: 'column',
gap: 16
})}>
<SegmentedControl aria-label="Package manager" selectedKey={packageManager} onSelectionChange={onSelectionChange}>
<SegmentedControlItem id="npm">npm</SegmentedControlItem>
<SegmentedControlItem id="yarn">yarn</SegmentedControlItem>
<SegmentedControlItem id="pnpm">pnpm</SegmentedControlItem>
</SegmentedControl>
<div
className={style({
display: 'flex',
flexWrap: 'wrap',
gap: 16
})}>
<SegmentedControl aria-label="Package manager" selectedKey={packageManager} onSelectionChange={onSelectionChange}>
<SegmentedControlItem id="npm">npm</SegmentedControlItem>
<SegmentedControlItem id="yarn">yarn</SegmentedControlItem>
<SegmentedControlItem id="pnpm">pnpm</SegmentedControlItem>
</SegmentedControl>
{!type &&
<SegmentedControl aria-label="Style" selectedKey={storedType} onSelectionChange={v => setStoredType(String(v))}>
<SegmentedControlItem id="Vanilla CSS">Vanilla CSS</SegmentedControlItem>
<SegmentedControlItem id="Tailwind">Tailwind</SegmentedControlItem>
</SegmentedControl>
}
</div>
<div
className={style({
display: 'flex',
Expand Down
7 changes: 4 additions & 3 deletions packages/dev/s2-docs/src/VisualExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ export interface VisualExampleProps {
align?: 'center' | 'start' | 'end',
acceptOrientation?: boolean,
propsObject?: string,
showCoachMark?: boolean
showCoachMark?: boolean,
hideShadcn?: boolean
}

export interface PropControl extends Omit<TProperty, 'description'> {
Expand All @@ -119,7 +120,7 @@ export interface PropControl extends Omit<TProperty, 'description'> {
/**
* 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;
Expand Down Expand Up @@ -188,7 +189,7 @@ export function VisualExample({component, docs, links, importSource, props, init
return (
<VisualExampleClient component={component} name={docs.name} importSource={importSource} controls={controls} initialProps={initialProps} propsObject={propsObject}>
<FileProvider value={downloadFiles}>
<ShadcnProvider value={!type || type === 's2' || docs.type !== 'component' ? null : {type, component: docs.name}}>
<ShadcnProvider value={!type || type === 's2' || docs.type !== 'component' || hideShadcn ? null : {type, component: docs.name}}>
<div role="group" aria-label="Example" className={exampleStyle({layout: files || wide ? 'wide' : 'narrow'})}>
<Output align={align} acceptOrientation={acceptOrientation} />
{props.length > 0 && <div role="group" aria-label="Controls" className={controlsStyle}>
Expand Down
18 changes: 16 additions & 2 deletions packages/react-aria-components/src/ColorField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 | ..."]
Expand Down Expand Up @@ -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'
});
Expand Down Expand Up @@ -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} />
</Provider>
);
}
24 changes: 24 additions & 0 deletions packages/react-aria-components/test/ColorField.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,30 @@ describe('ColorField', () => {
expect(input).toHaveValue('');
});

it('should support read-only state', async () => {
let {getByRole, rerender} = render(
<TestColorField />
);

let input = getByRole('textbox');

expect(input.closest('.react-aria-ColorField')).not.toHaveAttribute('data-readonly');
rerender(<TestColorField isReadOnly />);
expect(input.closest('.react-aria-ColorField')).toHaveAttribute('data-readonly');
});

it('should support required state', async () => {
let {getByRole, rerender} = render(
<TestColorField />
);

let input = getByRole('textbox');

expect(input.closest('.react-aria-ColorField')).not.toHaveAttribute('data-required');
rerender(<TestColorField isRequired />);
expect(input.closest('.react-aria-ColorField')).toHaveAttribute('data-required');
});

it('should render data- attributes only on the outer element', () => {
let {getAllByTestId} = render(
<TestColorField data-testid="number-field" />
Expand Down
26 changes: 23 additions & 3 deletions scripts/buildRegistry.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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',
Expand Down
25 changes: 3 additions & 22 deletions starters/tailwind/src/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -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<AriaCheckboxGroupProps, 'children'> {
label?: string,
children?: ReactNode,
description?: string;
errorMessage?: string | ((validation: ValidationResult) => string);
}

export function CheckboxGroup(props: CheckboxGroupProps) {
return (
<AriaCheckboxGroup {...props} className={composeTailwindRenderProps(props.className, 'flex flex-col gap-2 font-sans')}>
<Label>{props.label}</Label>
{props.children}
{props.description && <Description>{props.description}</Description>}
<FieldError>{props.errorMessage}</FieldError>
</AriaCheckboxGroup>
);
}
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]',
Expand Down
23 changes: 23 additions & 0 deletions starters/tailwind/src/CheckboxGroup.tsx
Original file line number Diff line number Diff line change
@@ -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<AriaCheckboxGroupProps, 'children'> {
label?: string,
children?: ReactNode,
description?: string,
errorMessage?: string | ((validation: ValidationResult) => string)
}

export function CheckboxGroup(props: CheckboxGroupProps) {
return (
<AriaCheckboxGroup {...props} className={composeTailwindRenderProps(props.className, 'flex flex-col gap-2 font-sans')}>
<Label>{props.label}</Label>
{props.children}
{props.description && <Description>{props.description}</Description>}
<FieldError>{props.errorMessage}</FieldError>
</AriaCheckboxGroup>
);
}
14 changes: 0 additions & 14 deletions starters/tailwind/src/Disclosure.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -81,15 +79,3 @@ export function DisclosurePanel({ children, ...props }: DisclosurePanelProps) {
</AriaDisclosurePanel>
);
}

export interface DisclosureGroupProps extends AriaDisclosureGroupProps {
children: React.ReactNode;
}

export function DisclosureGroup({ children, ...props }: DisclosureGroupProps) {
return (
<AriaDisclosureGroup {...props}>
{children}
</AriaDisclosureGroup>
);
}
18 changes: 18 additions & 0 deletions starters/tailwind/src/DisclosureGroup.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<AriaDisclosureGroup {...props}>
{children}
</AriaDisclosureGroup>
);
}
3 changes: 2 additions & 1 deletion starters/tailwind/stories/CheckboxGroup.stories.tsx
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
4 changes: 2 additions & 2 deletions starters/tailwind/stories/DisclosureGroup.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof DisclosureGroup> = {
component: DisclosureGroup,
Expand Down
Loading