From cc653c910b40a335444bf47adc08426e5f8f9e29 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Thu, 17 Jul 2025 16:24:46 -0500 Subject: [PATCH 001/193] added llms.txt to markdown generation --- packages/dev/s2-docs/scripts/generateMd.mjs | 53 +++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/packages/dev/s2-docs/scripts/generateMd.mjs b/packages/dev/s2-docs/scripts/generateMd.mjs index d82686ae2ad..ee6473e388e 100644 --- a/packages/dev/s2-docs/scripts/generateMd.mjs +++ b/packages/dev/s2-docs/scripts/generateMd.mjs @@ -843,6 +843,13 @@ function generateFunctionOptionsTable(functionName, file) { */ async function main() { const mdxFiles = await getMdxFiles(S2_DOCS_PAGES_ROOT); + + // Collect generated markdown filenames for each library so we can build llms.txt files. + const docsByLibrary = { + 's2': new Set(), + 'react-aria': new Set() + }; + for (const filePath of mdxFiles) { const mdContent = fs.readFileSync(filePath, 'utf8'); const processor = unified() @@ -863,7 +870,53 @@ async function main() { fs.mkdirSync(path.dirname(outPath), {recursive: true}); fs.writeFileSync(outPath, markdown, 'utf8'); console.log('Generated', path.relative(REPO_ROOT, outPath)); + + // Track markdown files by library (first path segment e.g. "s2/Button.mdx" -> "s2"). + const relativePathParts = relativePath.split(path.sep); + const libKey = relativePathParts[0]; + if (docsByLibrary[libKey]) { + docsByLibrary[libKey].add(path.basename(outPath)); + } } + + // Generate llms.txt for each library. + const makeLlmsTxt = (lib, files) => { + if (!files.size) {return;} + + const titleMap = { + 's2': 'React Spectrum S2 Documentation', + 'react-aria': 'React Aria Components Documentation' + }; + + const summaryMap = { + 's2': 'Plain-text markdown documentation for React Spectrum S2 components.', + 'react-aria': 'Plain-text markdown documentation for React Aria components.' + }; + + const title = titleMap[lib] || `${lib} documentation`; + const summary = summaryMap[lib] || ''; + + let txt = `# ${title}\n\n`; + if (summary) { + txt += `> ${summary}\n\n`; + } + + txt += '## Documentation\n'; + const sorted = Array.from(files).sort((a, b) => a.localeCompare(b)); + for (const file of sorted) { + const display = file.replace(/\.md$/, ''); + txt += `- [${display}](${lib}/${file})\n`; + } + + const libDistDir = path.join(DIST_ROOT, lib); + fs.mkdirSync(libDistDir, {recursive: true}); + const llmsPath = path.join(libDistDir, 'llms.txt'); + fs.writeFileSync(llmsPath, txt.trim() + '\n', 'utf8'); + console.log('Generated', path.relative(REPO_ROOT, llmsPath)); + }; + + makeLlmsTxt('s2', docsByLibrary['s2']); + makeLlmsTxt('react-aria', docsByLibrary['react-aria']); } main().catch((err) => { From e3fc6273110b17a5f3fe6fee010f0bf918cfee75 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Thu, 17 Jul 2025 16:49:58 -0500 Subject: [PATCH 002/193] add aria-expanded and aria-controls to search menu trigger action button --- packages/dev/s2-docs/src/Header.tsx | 7 ++++--- packages/dev/s2-docs/src/SearchMenu.tsx | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/dev/s2-docs/src/Header.tsx b/packages/dev/s2-docs/src/Header.tsx index 46e66f33eaf..17f1740b5b6 100644 --- a/packages/dev/s2-docs/src/Header.tsx +++ b/packages/dev/s2-docs/src/Header.tsx @@ -7,7 +7,7 @@ import GithubLogo from './icons/GithubLogo'; import {InternationalizedLogo} from './icons/InternationalizedLogo'; import {MarkdownMenu} from './MarkdownMenu'; import {PageProps} from '@parcel/rsc'; -import React, {CSSProperties, useState} from 'react'; +import React, {CSSProperties, useId, useState} from 'react'; import {ReactAriaLogo} from './icons/ReactAriaLogo'; import SearchMenu from './SearchMenu'; import {style} from '@react-spectrum/s2/style' with { type: 'macro' }; @@ -33,6 +33,7 @@ function getButtonIcon(currentPage) { export default function Header(props: PageProps) { const {pages, currentPage} = props; const [searchOpen, setSearchOpen] = useState(false); + const searchMenuId = useId(); let toggleShowSearchMenu = () => { if (!document.startViewTransition) { @@ -86,7 +87,7 @@ export default function Header(props: PageProps) { alignItems: 'center' })}>
- +
{getButtonIcon(currentPage)} @@ -98,7 +99,7 @@ export default function Header(props: PageProps) {
- +
diff --git a/packages/dev/s2-docs/src/SearchMenu.tsx b/packages/dev/s2-docs/src/SearchMenu.tsx index d7957b03f44..7a410941ffd 100644 --- a/packages/dev/s2-docs/src/SearchMenu.tsx +++ b/packages/dev/s2-docs/src/SearchMenu.tsx @@ -18,7 +18,8 @@ interface SearchMenuProps { currentPage: Page, toggleShowSearchMenu: () => void, closeSearchMenu: () => void, - isSearchOpen: boolean + isSearchOpen: boolean, + overlayId: string } interface FakeSearchFieldButtonProps extends Omit { @@ -124,7 +125,7 @@ const getCurrentLibrary = (currentPage: Page) => { }; export default function SearchMenu(props: SearchMenuProps) { - let {pages, currentPage, toggleShowSearchMenu, closeSearchMenu, isSearchOpen} = props; + let {pages, currentPage, toggleShowSearchMenu, closeSearchMenu, isSearchOpen, overlayId} = props; const currentLibrary = getCurrentLibrary(currentPage); let [selectedLibrary, setSelectedLibrary] = useState<'react-spectrum' | 'react-aria' | 'internationalized'>(currentLibrary); @@ -302,7 +303,7 @@ export default function SearchMenu(props: SearchMenuProps) { })}> - + Date: Thu, 17 Jul 2025 16:53:30 -0500 Subject: [PATCH 003/193] add aria-expanded and aria-controls to fake search field button --- packages/dev/s2-docs/src/SearchMenu.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/dev/s2-docs/src/SearchMenu.tsx b/packages/dev/s2-docs/src/SearchMenu.tsx index 7a410941ffd..b48ac1caee5 100644 --- a/packages/dev/s2-docs/src/SearchMenu.tsx +++ b/packages/dev/s2-docs/src/SearchMenu.tsx @@ -24,14 +24,17 @@ interface SearchMenuProps { interface FakeSearchFieldButtonProps extends Omit { onKeyDown: (e: React.KeyboardEvent) => void, - isSearchOpen: boolean + isSearchOpen: boolean, + overlayId: string } -function FakeSearchFieldButton({onPress, onKeyDown, isSearchOpen, ...props}: FakeSearchFieldButtonProps) { +function FakeSearchFieldButton({onPress, onKeyDown, isSearchOpen, overlayId, ...props}: FakeSearchFieldButtonProps) { return ( - + diff --git a/starters/tailwind/src/Checkbox.tsx b/starters/tailwind/src/Checkbox.tsx index 10d8709957f..ef2cfde5cf5 100644 --- a/starters/tailwind/src/Checkbox.tsx +++ b/starters/tailwind/src/Checkbox.tsx @@ -36,7 +36,7 @@ const checkboxStyles = tv({ const boxStyles = tv({ extend: focusRing, - base: 'w-5 h-5 shrink-0 rounded-sm flex items-center justify-center border-2 transition', + base: 'w-5 h-5 box-border shrink-0 rounded-sm flex items-center justify-center border-2 transition', variants: { isSelected: { false: 'bg-white dark:bg-zinc-900 border-(--color) [--color:var(--color-gray-400)] dark:[--color:var(--color-zinc-400)] group-pressed:[--color:var(--color-gray-500)] dark:group-pressed:[--color:var(--color-zinc-300)]', diff --git a/starters/tailwind/src/ColorPicker.tsx b/starters/tailwind/src/ColorPicker.tsx index 68787a1d22f..30391f433d7 100644 --- a/starters/tailwind/src/ColorPicker.tsx +++ b/starters/tailwind/src/ColorPicker.tsx @@ -12,7 +12,7 @@ import { focusRing } from './utils'; const buttonStyles = tv({ extend: focusRing, - base: 'flex gap-2 items-center cursor-default rounded-xs text-sm text-gray-800 dark:text-gray-200' + base: 'border-0 bg-transparent flex gap-2 items-center cursor-default rounded-xs text-sm text-gray-800 dark:text-gray-200' }); export interface ColorPickerProps extends AriaColorPickerProps { diff --git a/starters/tailwind/src/Field.tsx b/starters/tailwind/src/Field.tsx index 80930ed7ef6..0ac3136cdc8 100644 --- a/starters/tailwind/src/Field.tsx +++ b/starters/tailwind/src/Field.tsx @@ -34,7 +34,7 @@ export const fieldBorderStyles = tv({ export const fieldGroupStyles = tv({ extend: focusRing, - base: 'group flex items-center h-9 bg-white dark:bg-zinc-900 forced-colors:bg-[Field] border-2 rounded-lg overflow-hidden', + base: 'group flex items-center h-9 box-border bg-white dark:bg-zinc-900 forced-colors:bg-[Field] border-2 rounded-lg overflow-hidden', variants: fieldBorderStyles.variants }); @@ -43,5 +43,5 @@ export function FieldGroup(props: GroupProps) { } export function Input(props: InputProps) { - return + return } diff --git a/starters/tailwind/src/NumberField.tsx b/starters/tailwind/src/NumberField.tsx index fae91b461a7..0387a8bad6f 100644 --- a/starters/tailwind/src/NumberField.tsx +++ b/starters/tailwind/src/NumberField.tsx @@ -44,5 +44,5 @@ export function NumberField( } function StepperButton(props: ButtonProps) { - return
))} From 731f36dd06ba115659c55907ccbf8a2191c8bdd9 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Fri, 25 Jul 2025 18:00:14 -0500 Subject: [PATCH 013/193] add image styles to thumbnails --- packages/dev/s2-docs/src/CardList.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/dev/s2-docs/src/CardList.tsx b/packages/dev/s2-docs/src/CardList.tsx index 64334b13d11..0a3ff671b83 100644 --- a/packages/dev/s2-docs/src/CardList.tsx +++ b/packages/dev/s2-docs/src/CardList.tsx @@ -177,6 +177,14 @@ const linkCardStyles = style({ ...focusRing() }); +const illustrationStyles = style({ + width: 'full', + aspectRatio: '3/2', + objectFit: 'cover', + userSelect: 'none', + pointerEvents: 'none' +}); + export function CardList({selectedLibrary, pages}: CardListProps) { let sectionsData = useMemo(() => { if (!pages || !Array.isArray(pages)) { @@ -276,7 +284,10 @@ export function CardList({selectedLibrary, pages}: CardListProps) { size="S"> {IllustrationComponent && ( - )} From e5f538da18625aa8af49fec0e1d5155286e594a0 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Mon, 28 Jul 2025 11:36:44 -0500 Subject: [PATCH 014/193] fix z-index in copy/share ActionButtonGroup to avoid showing over dialog --- packages/dev/s2-docs/src/CodePlatter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/s2-docs/src/CodePlatter.tsx b/packages/dev/s2-docs/src/CodePlatter.tsx index d158ba35c1e..11f512c133f 100644 --- a/packages/dev/s2-docs/src/CodePlatter.tsx +++ b/packages/dev/s2-docs/src/CodePlatter.tsx @@ -67,7 +67,7 @@ export function CodePlatter({children, shareUrl, files, type, registryUrl}: Code return (
-
+
Date: Mon, 28 Jul 2025 10:18:17 -0700 Subject: [PATCH 015/193] update button docs --- .../dev/s2-docs/pages/react-aria/Button.mdx | 69 ++++++++++++++----- starters/docs/src/Button.tsx | 16 ++++- starters/docs/src/ProgressCircle.tsx | 33 +++++++++ 3 files changed, 100 insertions(+), 18 deletions(-) create mode 100644 starters/docs/src/ProgressCircle.tsx diff --git a/packages/dev/s2-docs/pages/react-aria/Button.mdx b/packages/dev/s2-docs/pages/react-aria/Button.mdx index 437f18df274..af5c6298d34 100644 --- a/packages/dev/s2-docs/pages/react-aria/Button.mdx +++ b/packages/dev/s2-docs/pages/react-aria/Button.mdx @@ -9,6 +9,7 @@ import {Button as TailwindButton} from 'tailwind-starter/Button'; import tailwindDocs from 'docs:tailwind-starter/Button'; import '../../tailwind/tailwind.css'; import typesDocs from 'docs:@react-types/shared/src/events.d.ts'; +import {InlineAlert, Heading, Content} from '@react-spectrum/s2' # Button @@ -19,7 +20,7 @@ import typesDocs from 'docs:@react-types/shared/src/events.d.ts'; component={VanillaButton} docs={docs.exports.Button} links={docs.links} - props={['isDisabled']} + props={['isPending', 'isDisabled']} initialProps={{children: 'Vanilla'}} type="vanilla" files={["starters/docs/src/Button.tsx", "starters/docs/src/Button.css", "starters/docs/src/theme.css"]} /> @@ -40,18 +41,6 @@ import typesDocs from 'docs:@react-types/shared/src/events.d.ts'; files={["packages/dev/s2-docs/pages/ButtonExample.tsx"]} /> */} -## Features - -On the surface, building a custom styled button seems simple. However, there are many -cross browser inconsistencies in interactions and accessibility features to consider. -`Button` handles all of these interactions for you, so you can focus on the styling. - -* **Styleable** – Hover, press, and keyboard focus states are provided for easy styling. These states only apply when interacting with an appropriate input device, unlike CSS pseudo classes. -* **Accessible** – Uses a native ` + ); +} +``` + + + Accessibility +

The `ProgressBar` must be in the accessibility tree as soon as the button becomes pending, even if it is not visible. To delay showing a spinner until a minimum amount of time passes, use `opacity: 0`. Do not use `visibility: hidden` or `display: none` as these remove the element from the accessibility tree.

+

To reserve space for the button's label while pending, either set it to `visibility: hidden` with a descriptive ProgressBar `aria-label` (e.g. "Saving"), or set it to `opacity: 0` to combine the button's label with the `aria-label` of the ProgressBar (e.g. "Save, pending").

+
+ +## Link buttons + +The `Button` component always represents a button semantically. To create a link that visually looks like a button, use the [Link](Link.html) component instead. You can reuse the same styles you apply to the `Button` component on the `Link`. + +```tsx render +"use client"; +import {Link} from 'react-aria-components'; + + + Adobe + +``` + ## Props diff --git a/starters/docs/src/Button.tsx b/starters/docs/src/Button.tsx index 3520c7223ad..e976a2b7c04 100644 --- a/starters/docs/src/Button.tsx +++ b/starters/docs/src/Button.tsx @@ -1,7 +1,19 @@ 'use client'; -import {Button as RACButton, ButtonProps} from 'react-aria-components'; +import {Button as RACButton, ButtonProps, composeRenderProps} from 'react-aria-components'; +import {ProgressCircle} from './ProgressCircle'; import './Button.css'; export function Button(props: ButtonProps) { - return ; + return ( + + {composeRenderProps(props.children, (children, {isPending}) => ( + <> + {!isPending && children} + {isPending && ( + + )} + + ))} + + ); } diff --git a/starters/docs/src/ProgressCircle.tsx b/starters/docs/src/ProgressCircle.tsx new file mode 100644 index 00000000000..cbe4f6a789b --- /dev/null +++ b/starters/docs/src/ProgressCircle.tsx @@ -0,0 +1,33 @@ +import {ProgressBar} from 'react-aria-components'; +import type {ProgressBarProps} from 'react-aria-components'; + +export function ProgressCircle(props: ProgressBarProps) { + return ( + + + + + + + + + ); +} From 1816b0b8c78838cbc78212b29b0f314b07d0ddbf Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 28 Jul 2025 10:19:55 -0700 Subject: [PATCH 016/193] Update async loading docs --- .../dev/s2-docs/pages/react-aria/GridList.mdx | 43 ++++++-- .../dev/s2-docs/pages/react-aria/ListBox.mdx | 43 +++++--- .../dev/s2-docs/pages/react-aria/Table.mdx | 99 +++++++++++++------ starters/docs/src/GridList.css | 8 ++ starters/docs/src/ListBox.css | 8 ++ starters/docs/src/Table.css | 5 + starters/docs/src/Table.tsx | 4 +- 7 files changed, 157 insertions(+), 53 deletions(-) diff --git a/packages/dev/s2-docs/pages/react-aria/GridList.mdx b/packages/dev/s2-docs/pages/react-aria/GridList.mdx index 418dae00037..c249ccdccbf 100644 --- a/packages/dev/s2-docs/pages/react-aria/GridList.mdx +++ b/packages/dev/s2-docs/pages/react-aria/GridList.mdx @@ -104,11 +104,13 @@ function Example() { ### Asynchronous loading -This example uses the useAsyncList hook to handle asynchronous loading of data from a server. +Use [renderEmptyState](#empty-state) to display a spinner during initial load. To enable infinite scrolling, render a `` at the end of the list. Use whatever data fetching library you prefer – this example uses `useAsyncList` from `react-stately`. ```tsx render "use client"; +import {Collection, GridListLoadMoreItem} from 'react-aria-components'; import {GridList, GridListItem} from 'vanilla-starter/GridList'; +import {ProgressCircle} from 'vanilla-starter/ProgressCircle'; import {useAsyncList} from '@react-stately/data'; interface Character { @@ -116,25 +118,42 @@ interface Character { } function AsyncLoadingExample() { - /*- begin highlight -*/ let list = useAsyncList({ - async load({signal, filterText}) { + async load({ signal, cursor }) { + if (cursor) { + cursor = cursor.replace(/^http:\/\//i, 'https://'); + } + let res = await fetch( - `https://pokeapi.co/api/v2/pokemon`, - {signal} + cursor || `https://swapi.py4e.com/api/people/?search=`, + { signal } ); let json = await res.json(); return { - items: json.results + items: json.results, + cursor: json.next }; } }); - /*- end highlight -*/ return ( - - {(item) => {item.name}} + ( + + )}> + + {(item) => {item.name}} + + {/*- begin highlight -*/} + + + + {/*- end highlight -*/} ); } @@ -271,3 +290,9 @@ function Example() { + +### GridListLoadMoreItem + + + +{/* */} diff --git a/packages/dev/s2-docs/pages/react-aria/ListBox.mdx b/packages/dev/s2-docs/pages/react-aria/ListBox.mdx index 1a653e9cc67..c42663ef643 100644 --- a/packages/dev/s2-docs/pages/react-aria/ListBox.mdx +++ b/packages/dev/s2-docs/pages/react-aria/ListBox.mdx @@ -161,38 +161,51 @@ import {ListBox, ListBoxItem, ListBoxSection, Header} from 'react-aria-component ### Asynchronous loading -This example uses the useAsyncList hook to handle asynchronous loading -of data from a server. +Use [renderEmptyState](#empty-state) to display a spinner during initial load. To enable infinite scrolling, render a `` at the end of the list or section. Use whatever data fetching library you prefer – this example uses `useAsyncList` from `react-stately`. ```tsx render "use client"; -import {ListBox, ListBoxItem} from 'react-aria-components'; -import {useAsyncList} from '@react-stately/data'; +import {ListBox, ListBoxItem, Collection, ListBoxLoadMoreItem} from 'react-aria-components'; +import {ProgressCircle} from 'vanilla-starter/ProgressCircle'; +import {useAsyncList} from 'react-stately'; interface Character { name: string } function AsyncLoadingExample() { - /*- begin highlight -*/ let list = useAsyncList({ - async load({signal, filterText}) { + async load({signal, cursor}) { let res = await fetch( - `https://pokeapi.co/api/v2/pokemon`, - {signal} + cursor || `https://pokeapi.co/api/v2/pokemon`, + { signal } ); let json = await res.json(); return { - items: json.results + items: json.results, + cursor: json.next }; } }); - /*- end highlight -*/ return ( - - {(item) => {item.name}} + ( + + )}> + + {(item) => {item.name}} + + {/*- begin highlight -*/} + + + + {/*- end highlight -*/} ); } @@ -395,3 +408,9 @@ function Example() { {/* */} + +### ListBoxLoadMoreItem + + + +{/* */} diff --git a/packages/dev/s2-docs/pages/react-aria/Table.mdx b/packages/dev/s2-docs/pages/react-aria/Table.mdx index 4855f4026a6..b1ca44cb569 100644 --- a/packages/dev/s2-docs/pages/react-aria/Table.mdx +++ b/packages/dev/s2-docs/pages/react-aria/Table.mdx @@ -201,12 +201,13 @@ on external state (e.g. `columns` in this example). ### Asynchronous loading -This example uses the `useAsyncList` hook to handle asynchronous loading of data from a server. +Use [renderEmptyState](#empty-state) to display a spinner during initial load. To enable infinite scrolling, render a `` at the end of the list. Use whatever data fetching library you prefer – this example uses `useAsyncList` from `react-stately`. ```tsx render "use client"; -import {Table, TableHeader, Column, Row} from 'vanilla-starter/Table'; -import {TableBody, Cell} from 'react-aria-components'; +import {Table, TableHeader, Column, Row, TableBody, Cell} from 'vanilla-starter/Table'; +import {Collection, TableLoadMoreItem} from 'react-aria-components'; +import {ProgressCircle} from 'vanilla-starter/ProgressCircle'; import {useAsyncList} from 'react-stately'; interface Character { @@ -217,39 +218,72 @@ interface Character { } function AsyncSortTable() { - ///- begin highlight -/// let list = useAsyncList({ - async load({ signal }) { - let res = await fetch(`https://swapi.py4e.com/api/people/?search`, { - signal - }); + async load({ signal, cursor }) { + if (cursor) { + cursor = cursor.replace(/^http:\/\//i, 'https://'); + } + + let res = await fetch( + cursor || 'https://swapi.py4e.com/api/people/?search=', + { signal } + ); let json = await res.json(); + return { - items: json.results + items: json.results, + cursor: json.next }; } }); - ///- end highlight -/// return ( - - - Name - Height - Mass - Birth Year - - - {(item) => ( - - {item.name} - {item.height} - {item.mass} - {item.birth_year} - - )} - -
+
+ + + Name + Height + Mass + Birth Year + + ( + + )}> + + {(item) => ( + + {item.name} + {item.height} + {item.mass} + {item.birth_year} + + )} + + {/*- begin highlight -*/} + + + + {/*- end highlight -*/} + +
+
); } ``` @@ -694,9 +728,14 @@ function ReorderableTable() { - ### ColumnResizer - \ No newline at end of file + + +### TableLoadMoreItem + + + +{/* */} diff --git a/starters/docs/src/GridList.css b/starters/docs/src/GridList.css index 65babe75fed..5aece571555 100644 --- a/starters/docs/src/GridList.css +++ b/starters/docs/src/GridList.css @@ -185,3 +185,11 @@ margin-bottom: -2px; } } + +.react-aria-GridListLoadingIndicator { + display: flex; + align-items: center; + justify-content: center; + height: 24px; + width: 100%; +} diff --git a/starters/docs/src/ListBox.css b/starters/docs/src/ListBox.css index 738c071e905..c7bcd4ebb4c 100644 --- a/starters/docs/src/ListBox.css +++ b/starters/docs/src/ListBox.css @@ -206,3 +206,11 @@ .react-aria-DropIndicator[data-drop-target] { outline: 1px solid var(--highlight-background); } + +.react-aria-ListBoxLoadingIndicator { + display: flex; + align-items: center; + justify-content: center; + height: 24px; + width: 100%; +} diff --git a/starters/docs/src/Table.css b/starters/docs/src/Table.css index 714d6837926..9d5d91c3fcd 100644 --- a/starters/docs/src/Table.css +++ b/starters/docs/src/Table.css @@ -269,3 +269,8 @@ object-fit: cover; display: block; } + +.react-aria-TableLoadingIndicator { + height: 24px; + position: relative; +} diff --git a/starters/docs/src/Table.tsx b/starters/docs/src/Table.tsx index d588b03ea77..7e55db4dcfa 100644 --- a/starters/docs/src/Table.tsx +++ b/starters/docs/src/Table.tsx @@ -46,13 +46,13 @@ export function Column( } export function TableHeader( - { columns, children }: TableHeaderProps + { columns, children, ...otherProps }: TableHeaderProps ) { let { selectionBehavior, selectionMode, allowsDragging } = useTableOptions(); return ( ( - + {/* Add extra columns for drag and drop and selection. */} {allowsDragging && } {selectionBehavior === 'toggle' && ( From b55f781b492aed8251e640ecddb7b7d1c8dbd9b0 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 28 Jul 2025 10:23:44 -0700 Subject: [PATCH 017/193] Make StateTable style match PropTable --- packages/dev/s2-docs/src/PropTable.tsx | 4 ++-- packages/dev/s2-docs/src/StateTable.tsx | 32 ++++++++++++++----------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/packages/dev/s2-docs/src/PropTable.tsx b/packages/dev/s2-docs/src/PropTable.tsx index 737bc14f937..b857afb9274 100644 --- a/packages/dev/s2-docs/src/PropTable.tsx +++ b/packages/dev/s2-docs/src/PropTable.tsx @@ -79,11 +79,11 @@ export function PropTable({component, links, showDescription}: PropTableProps) { interface GroupedPropTableProps { properties: TInterface['properties'], links: any, - propGroups: {[name: string]: (string | RegExp)[]}, + propGroups?: {[name: string]: (string | RegExp)[]}, defaultExpanded?: Set } -export function GroupedPropTable({properties, links, propGroups, defaultExpanded}: GroupedPropTableProps) { +export function GroupedPropTable({properties, links, propGroups = GROUPS, defaultExpanded = DEFAULT_EXPANDED}: GroupedPropTableProps) { setLinks(links); let [props, groups] = groupProps(properties, propGroups); diff --git a/packages/dev/s2-docs/src/StateTable.tsx b/packages/dev/s2-docs/src/StateTable.tsx index c7b8b972d14..0619b203bec 100644 --- a/packages/dev/s2-docs/src/StateTable.tsx +++ b/packages/dev/s2-docs/src/StateTable.tsx @@ -1,9 +1,10 @@ import {Code, styles as codeStyles} from './Code'; +import {Fragment} from 'react'; import {renderHTMLfromMarkdown, TInterface} from './types'; import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; import {Table, TableBody, TableCell, TableColumn, TableHeader, TableRow} from './Table'; -const codeStyle = style({font: {default: 'code-xs', lg: 'code-sm'}}); +const codeStyle = style({font: {default: 'code-xs', lg: 'code-sm'}, whiteSpace: 'nowrap'}); interface StateTableProps { properties: TInterface['properties'], @@ -25,23 +26,26 @@ export function StateTable({properties, showOptional, hideSelector, defaultClass Render Prop {showSelector && CSS Selector} - Description {props.map((prop, index) => ( - - - - {prop.name} - - - {showSelector && - CSS Selector: - {prop.type === 'property' && prop.selector ? {prop.selector} : } - } - {prop.description && renderHTMLfromMarkdown(prop.description, {forceInline: false})} - + + + + + {prop.name} + + + {showSelector && + CSS Selector: + {prop.type === 'property' && prop.selector ? {prop.selector} : } + } + + + {prop.description && renderHTMLfromMarkdown(prop.description, {forceInline: true})} + + ))} From c65cc1985969a08d1ee4486bfcdc65ecde92afcd Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 28 Jul 2025 10:23:55 -0700 Subject: [PATCH 018/193] Merge CSS bundles --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index fb4b86a89f0..c4bbdea2d42 100644 --- a/package.json +++ b/package.json @@ -280,7 +280,8 @@ { "name": "s2-styles", "assets": [ - "packages/@react-spectrum/s2/**" + "packages/@react-spectrum/s2/**", + "packages/dev/s2-docs/src/**" ], "types": [ "css" From 250373daac234b593495b6c550abc2876e17c601 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 28 Jul 2025 10:55:43 -0700 Subject: [PATCH 019/193] Fix combobox displaying both description and error message --- starters/docs/src/ComboBox.tsx | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/starters/docs/src/ComboBox.tsx b/starters/docs/src/ComboBox.tsx index e064a6c657a..c9ad4ea8221 100644 --- a/starters/docs/src/ComboBox.tsx +++ b/starters/docs/src/ComboBox.tsx @@ -28,18 +28,20 @@ export function ComboBox( return ( ( - -
- - -
- {description && {description}} - {errorMessage} - - - {children} - - + {({isInvalid}) => (<> + +
+ + +
+ {description && !isInvalid && {description}} + {errorMessage} + + + {children} + + + )}
) ); From e75b060ef7a4194c1c9611acff76ace1fd0d5cc0 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Mon, 28 Jul 2025 11:18:56 -0700 Subject: [PATCH 020/193] Avoid duplicate ids and fix mobile scrolling --- packages/dev/s2-docs/src/Layout.tsx | 10 +++++----- packages/dev/s2-docs/src/StateTable.tsx | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/dev/s2-docs/src/Layout.tsx b/packages/dev/s2-docs/src/Layout.tsx index f87c527fc69..a5dd1ddf1e2 100644 --- a/packages/dev/s2-docs/src/Layout.tsx +++ b/packages/dev/s2-docs/src/Layout.tsx @@ -1,5 +1,5 @@ import {MobileNav, MobileOnPageNav, Nav, OnPageNav, SideNav, SideNavItem, SideNavLink} from '../src/Nav'; -import type {PageProps} from '@parcel/rsc'; +import type {PageProps, TocNode} from '@parcel/rsc'; import React, {ReactElement} from 'react'; import '../src/client'; import './anatomy.css'; @@ -18,7 +18,7 @@ import {TypeLink} from './types'; import {VisualExample} from './VisualExample'; const components = { - h1: ({children, ...props}) =>

{children}

, + h1: ({children, ...props}) =>

{children}

, h2: H2, h3: H3, h4: H4, @@ -47,7 +47,7 @@ function anchorId(children) { export function Layout(props: PageProps & {children: ReactElement}) { let {pages, currentPage, children} = props; return ( - + @@ -166,9 +166,9 @@ function MobileToc({toc}) { ); } -function renderMobileToc(toc, seen = new Map()) { +function renderMobileToc(toc: TocNode[], seen = new Map()) { return toc.map((c) => { - let href = '#' + anchorId(c.title); + let href = c.level === 1 ? '#top' : '#' + anchorId(c.title); if (seen.has(href)) { seen.set(href, seen.get(href) + 1); href += '-' + seen.get(href); diff --git a/packages/dev/s2-docs/src/StateTable.tsx b/packages/dev/s2-docs/src/StateTable.tsx index 0619b203bec..eee6c3e962a 100644 --- a/packages/dev/s2-docs/src/StateTable.tsx +++ b/packages/dev/s2-docs/src/StateTable.tsx @@ -4,7 +4,7 @@ import {renderHTMLfromMarkdown, TInterface} from './types'; import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; import {Table, TableBody, TableCell, TableColumn, TableHeader, TableRow} from './Table'; -const codeStyle = style({font: {default: 'code-xs', lg: 'code-sm'}, whiteSpace: 'nowrap'}); +const codeStyle = style({font: {default: 'code-xs', lg: 'code-sm'}}); interface StateTableProps { properties: TInterface['properties'], From fab13b84f9b941b5a91305a7c0b78aa671f17ee7 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Mon, 28 Jul 2025 14:23:57 -0500 Subject: [PATCH 021/193] Add library to page titles --- packages/dev/s2-docs/src/Layout.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/dev/s2-docs/src/Layout.tsx b/packages/dev/s2-docs/src/Layout.tsx index f0a6ff0a3f9..3b557953948 100644 --- a/packages/dev/s2-docs/src/Layout.tsx +++ b/packages/dev/s2-docs/src/Layout.tsx @@ -1,5 +1,5 @@ import {MobileNav, MobileOnPageNav, Nav, OnPageNav, SideNav, SideNavItem, SideNavLink} from '../src/Nav'; -import type {PageProps} from '@parcel/rsc'; +import type {Page, PageProps} from '@parcel/rsc'; import React, {ReactElement} from 'react'; import '../src/client'; import './anatomy.css'; @@ -44,6 +44,17 @@ function anchorId(children) { return children.replace(/\s/g, '-').replace(/[^a-zA-Z0-9-_]/g, '').toLowerCase(); } +const getTitle = (currentPage: Page): string => { + let library: string | undefined; + if (currentPage.name.startsWith('react-aria/')) { + library = 'React Aria'; + } else if (currentPage.name.startsWith('s2/')) { + library = 'React Spectrum'; + } + const pageTitle = currentPage.exports?.title ?? currentPage.tableOfContents?.[0]?.title ?? currentPage.name; + return library ? `${pageTitle} - ${library}` : pageTitle; +}; + export function Layout(props: PageProps & {children: ReactElement}) { let {pages, currentPage, children} = props; return ( @@ -52,7 +63,7 @@ export function Layout(props: PageProps & {children: ReactElement}) { - {currentPage.exports?.title ?? currentPage.tableOfContents?.[0]?.title ?? currentPage.name} + {getTitle(currentPage)} Date: Mon, 28 Jul 2025 14:24:38 -0500 Subject: [PATCH 022/193] Revert "fix z-index in copy/share ActionButtonGroup to avoid showing over dialog" This reverts commit e5f538da18625aa8af49fec0e1d5155286e594a0. --- packages/dev/s2-docs/src/CodePlatter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/s2-docs/src/CodePlatter.tsx b/packages/dev/s2-docs/src/CodePlatter.tsx index 11f512c133f..d158ba35c1e 100644 --- a/packages/dev/s2-docs/src/CodePlatter.tsx +++ b/packages/dev/s2-docs/src/CodePlatter.tsx @@ -67,7 +67,7 @@ export function CodePlatter({children, shareUrl, files, type, registryUrl}: Code return (
-
+
Date: Mon, 28 Jul 2025 15:44:06 -0500 Subject: [PATCH 023/193] use ui-2xl font for search menu button and tabs --- packages/dev/s2-docs/src/Header.tsx | 2 +- packages/dev/s2-docs/src/SearchMenu.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dev/s2-docs/src/Header.tsx b/packages/dev/s2-docs/src/Header.tsx index 64223b14ebf..c9f66429d22 100644 --- a/packages/dev/s2-docs/src/Header.tsx +++ b/packages/dev/s2-docs/src/Header.tsx @@ -93,7 +93,7 @@ export default function Header(props: PageProps) {
{getButtonIcon(currentPage)}
- + {getButtonText(currentPage)}
diff --git a/packages/dev/s2-docs/src/SearchMenu.tsx b/packages/dev/s2-docs/src/SearchMenu.tsx index 7fb38fa19a5..ed323a6667b 100644 --- a/packages/dev/s2-docs/src/SearchMenu.tsx +++ b/packages/dev/s2-docs/src/SearchMenu.tsx @@ -356,7 +356,7 @@ export default function SearchMenu(props: SearchMenuProps) { {tab.icon}
- + {tab.label}
{tab.description}
From e4fd9cd3a2099c520e723c9919834681b138c1f0 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Mon, 28 Jul 2025 17:58:54 -0500 Subject: [PATCH 024/193] fix overscroll behavior --- packages/dev/s2-docs/src/Layout.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/dev/s2-docs/src/Layout.tsx b/packages/dev/s2-docs/src/Layout.tsx index ceaadeba1ec..da2e19b9991 100644 --- a/packages/dev/s2-docs/src/Layout.tsx +++ b/packages/dev/s2-docs/src/Layout.tsx @@ -85,7 +85,10 @@ export function Layout(props: PageProps & {children: ReactElement}) { default: 0, lg: 12 }, - overflow: 'hidden' + overscrollBehavior: { + default: 'auto', + lg: 'none' + } })}>
Date: Mon, 28 Jul 2025 17:51:48 -0700 Subject: [PATCH 025/193] Add Tree docs --- .../dev/s2-docs/pages/react-aria/GridList.mdx | 2 +- .../dev/s2-docs/pages/react-aria/Tree.mdx | 408 ++++++++++++++++++ packages/react-aria-components/docs/Tree.mdx | 4 +- packages/react-aria-components/src/Tree.tsx | 4 +- packages/react-aria-components/src/utils.tsx | 2 +- starters/tailwind/src/Tree.tsx | 58 ++- starters/tailwind/stories/Tree.stories.tsx | 35 +- 7 files changed, 449 insertions(+), 64 deletions(-) create mode 100644 packages/dev/s2-docs/pages/react-aria/Tree.mdx diff --git a/packages/dev/s2-docs/pages/react-aria/GridList.mdx b/packages/dev/s2-docs/pages/react-aria/GridList.mdx index c249ccdccbf..b71c39c76d5 100644 --- a/packages/dev/s2-docs/pages/react-aria/GridList.mdx +++ b/packages/dev/s2-docs/pages/react-aria/GridList.mdx @@ -161,7 +161,7 @@ function AsyncLoadingExample() { ### Links -Use the `href` prop on a `` to create a link. See the **client side routing guide** to learn how to integrate with your framework. Link interactions vary depending on the selection behavior. See the [selection guide](selection.html) for more details. +Use the `href` prop on a `` to create a link. See the **client side routing guide** to learn how to integrate with your framework. Link interactions vary depending on the selection behavior. See the [selection guide](selection.html) for more details. ```tsx render docs={docs.exports.ListBox} links={docs.links} props={['selectionBehavior']} initialProps={{'aria-label': 'Links', selectionMode: 'multiple'}} wide "use client"; diff --git a/packages/dev/s2-docs/pages/react-aria/Tree.mdx b/packages/dev/s2-docs/pages/react-aria/Tree.mdx new file mode 100644 index 00000000000..bd328e2df1f --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/Tree.mdx @@ -0,0 +1,408 @@ +import {Layout} from '../../src/Layout'; +export default Layout; + +import docs from 'docs:react-aria-components'; +import {Tree as VanillaTree, TreeItem} from 'vanilla-starter/Tree'; +import vanillaDocs from 'docs:vanilla-starter/Tree'; +import '../../tailwind/tailwind.css'; +import Anatomy from 'react-aria-components/docs/TreeAnatomy.svg'; + +# Tree + +{docs.exports.Tree.description} + + + ```tsx render docs={docs.exports.Tree} links={docs.links} props={['selectionMode']} initialProps={{selectionMode: 'multiple'}} type="vanilla" files={["starters/docs/src/Tree.tsx", "starters/docs/src/Tree.css", "starters/docs/src/theme.css"]} + "use client"; + import {Tree, TreeItem} from 'vanilla-starter/Tree'; + import {Button} from 'vanilla-starter/Button'; + + + + + + + + + + + + + ``` + + ```tsx render docs={docs.exports.Tree} links={docs.links} props={['selectionMode']} initialProps={{selectionMode: 'multiple'}} type="tailwind" files={["starters/tailwind/src/Tree.tsx"]} + "use client"; + import {Tree, TreeItem} from 'tailwind-starter/Tree'; + + + + + + + + + + + + + ``` + + + +## Anatomy + + + +A Tree consists of a container element, with rows of data inside. The items within a tree may contain focusable elements or plain text content. Each item may also contain a button to toggle the expandable state of that item. If the tree supports item selection, each item can optionally include a selection checkbox. + +```tsx +import {Tree, TreeItem, TreeItemContent, Button, Checkbox} from 'react-aria-components'; + + + + + - ) :
} - {children} -
- )} - - ); + + + {({ selectionMode, selectionBehavior, hasChildItems, isExpanded, isDisabled }) => ( +
+ {selectionMode === 'multiple' && selectionBehavior === 'toggle' && ( + + )} +
+ {hasChildItems ? ( + + ) :
} + {props.title} +
+ )} + + {props.children} + + ) } diff --git a/starters/tailwind/stories/Tree.stories.tsx b/starters/tailwind/stories/Tree.stories.tsx index 29e5f807647..9dbbf1d8920 100644 --- a/starters/tailwind/stories/Tree.stories.tsx +++ b/starters/tailwind/stories/Tree.stories.tsx @@ -1,5 +1,5 @@ import { Meta } from '@storybook/react'; -import { Tree, TreeItem, TreeItemContent } from '../src/Tree'; +import { Tree, TreeItem } from '../src/Tree'; import React from 'react'; const meta: Meta = { @@ -14,35 +14,14 @@ export default meta; export const Example = (args: any) => ( - - - Documents - - - - Project - - - - Weekly Report - - + + + - - - Photos - - - - Image 1 - - - - - Image 2 - - + + + ); From 6832d48f95514567c659a6ee46b0ce9228ebe535 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Tue, 29 Jul 2025 10:36:47 -0500 Subject: [PATCH 026/193] add generateMarkdownDocs and generateOGImages to makefile --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 684fd286e04..f5de6668ec7 100644 --- a/Makefile +++ b/Makefile @@ -143,6 +143,8 @@ s2-api-diff: node scripts/api-diff.js --skip-same --skip-style-props s2-docs: + node packages/dev/s2-docs/scripts/generateMarkdownDocs.mjs + node packages/dev/s2-docs/scripts/generateOGImages.mjs node scripts/extractStarter.mjs REGISTRY_URL=https://reactspectrum.blob.core.windows.net/reactspectrum/$$(git rev-parse HEAD)/s2-docs/registry node scripts/buildRegistry.mjs REGISTRY_URL=https://reactspectrum.blob.core.windows.net/reactspectrum/$$(git rev-parse HEAD)/s2-docs/registry yarn build:s2-docs --public-url /reactspectrum/$$(git rev-parse HEAD)/s2-docs/ From b4eee383a9f0182b3422763fea5694ddd0629381 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Tue, 29 Jul 2025 10:37:13 -0700 Subject: [PATCH 027/193] TagGroup docs --- .../@react-aria/autocomplete/docs/anatomy.svg | 2 +- .../@react-aria/gridlist/docs/anatomy.svg | 2 +- packages/@react-aria/tag/docs/anatomy.svg | 2 +- .../dev/s2-docs/pages/react-aria/GridList.mdx | 4 +- .../dev/s2-docs/pages/react-aria/TagGroup.mdx | 182 ++++++++++++++++++ 5 files changed, 187 insertions(+), 5 deletions(-) create mode 100644 packages/dev/s2-docs/pages/react-aria/TagGroup.mdx diff --git a/packages/@react-aria/autocomplete/docs/anatomy.svg b/packages/@react-aria/autocomplete/docs/anatomy.svg index 7bf0320612d..d15379993fa 100644 --- a/packages/@react-aria/autocomplete/docs/anatomy.svg +++ b/packages/@react-aria/autocomplete/docs/anatomy.svg @@ -1,4 +1,4 @@ - + diff --git a/packages/@react-aria/gridlist/docs/anatomy.svg b/packages/@react-aria/gridlist/docs/anatomy.svg index f55562d5809..4dc667b119a 100644 --- a/packages/@react-aria/gridlist/docs/anatomy.svg +++ b/packages/@react-aria/gridlist/docs/anatomy.svg @@ -1,4 +1,4 @@ - + List anatomy diagram Shows a list and list item component with labels pointing to its parts. diff --git a/packages/@react-aria/tag/docs/anatomy.svg b/packages/@react-aria/tag/docs/anatomy.svg index d4ef325ce48..6795f8880a9 100644 --- a/packages/@react-aria/tag/docs/anatomy.svg +++ b/packages/@react-aria/tag/docs/anatomy.svg @@ -1,4 +1,4 @@ - + diff --git a/packages/dev/s2-docs/pages/react-aria/GridList.mdx b/packages/dev/s2-docs/pages/react-aria/GridList.mdx index b71c39c76d5..f3cd4d35f47 100644 --- a/packages/dev/s2-docs/pages/react-aria/GridList.mdx +++ b/packages/dev/s2-docs/pages/react-aria/GridList.mdx @@ -163,7 +163,7 @@ function AsyncLoadingExample() { Use the `href` prop on a `` to create a link. See the **client side routing guide** to learn how to integrate with your framework. Link interactions vary depending on the selection behavior. See the [selection guide](selection.html) for more details. -```tsx render docs={docs.exports.ListBox} links={docs.links} props={['selectionBehavior']} initialProps={{'aria-label': 'Links', selectionMode: 'multiple'}} wide +```tsx render docs={docs.exports.GridList} links={docs.links} props={['selectionBehavior']} initialProps={{'aria-label': 'Links', selectionMode: 'multiple'}} wide "use client"; import {GridList, GridListItem} from 'vanilla-starter/GridList'; @@ -181,7 +181,7 @@ import {GridList, GridListItem} from 'vanilla-starter/GridList'; ```tsx render hideImports "use client"; -import {GridList, GridListItem} from 'vanilla-starter/GridList'; +import {GridList} from 'vanilla-starter/GridList'; {docs.exports.TagGroup.description} + + + ```tsx render docs={vanillaDocs.exports.TagGroup} links={docs.links} props={['label', 'selectionMode', 'description', 'errorMessage']} initialProps={{label: 'Categories', selectionMode: 'multiple'}} type="vanilla" files={["starters/docs/src/TagGroup.tsx", "starters/docs/src/TagGroup.css", "starters/docs/src/theme.css"]} + "use client"; + import {TagGroup, Tag} from 'vanilla-starter/TagGroup'; + + + News + Travel + Gaming + Shopping + + ``` + + ```tsx render docs={vanillaDocs.exports.TagGroup} links={docs.links} props={['label', 'selectionMode', 'description', 'errorMessage']} initialProps={{label: 'Categories', selectionMode: 'multiple'}} type="tailwind" files={["starters/tailwind/src/TagGroup.tsx"]} + "use client"; + import {TagGroup, Tag} from 'tailwind-starter/TagGroup'; + + + News + Travel + Gaming + Shopping + + ``` + + + +## Anatomy + + + +A tag group consists of label and a list of tags. Each tag should include a visual label, and may optionally include a remove button. If a visual label is not included in a tag group, then an `aria-label` or `aria-labelledby` prop must be passed to identify it to assistive technology. + +`TagGroup` also supports optional description and error message slots, which can be used +to provide more context about the tag group, and any validation messages. These are linked with the +tag group via the `aria-describedby` attribute. + +```tsx +import {TagGroup, TagList, Tag, Label, Button, Text} from 'react-aria-components'; + + + + + + + + + + + +``` + +## Content + +`TagGroup` follows the **Collection Components API**, accepting both static and dynamic collections. This example shows a dynamic collection, passing a list of objects to the `items` prop, and a function to render the children. Items can be removed via the `onRemove` event. + +```tsx render +"use client"; +import {TagGroup, Tag} from 'vanilla-starter/TagGroup'; +import {useListData} from 'react-stately'; + +function Example() { + let list = useListData({ + initialItems: [ + { id: 1, name: 'News' }, + { id: 2, name: 'Travel' }, + { id: 3, name: 'Gaming' }, + { id: 4, name: 'Shopping' } + ] + }); + + return ( + list.remove(...keys)}> + {/*- end highlight -*/} + {(item) => {item.name}} + + /*- end highlight -*/ + ); +} +``` + +### Links + +Use the `href` prop on a `` to create a link. See the **client side routing guide** to learn how to integrate with your framework. + +```tsx render +"use client"; +import {TagGroup, Tag} from 'vanilla-starter/TagGroup'; + + + {/*- begin highlight -*/} + Adobe + {/*- end highlight -*/} + Apple + Google + Microsoft + +``` + +### Empty state + +```tsx render hideImports +"use client"; +import {TagGroup} from 'vanilla-starter/TagGroup'; + + 'No categories.'}> + {[]} + +``` + +## Selection + +Use the `selectionMode` prop to enable single or multiple selection. The selected items can be controlled via the `selectedKeys` prop, matching the `id` prop of the items. Items can be disabled with the `isDisabled` prop. See the [selection guide](selection.html) for more details. + +```tsx render docs={docs.exports.TagGroup} links={docs.links} props={['selectionMode', 'selectionBehavior', 'disallowEmptySelection']} initialProps={{selectionMode: 'multiple'}} wide +"use client"; +import type {Selection} from 'react-aria-components'; +import {TagGroup, Tag} from 'vanilla-starter/TagGroup'; +import {useState} from 'react'; + +function Example(props) { + let [selected, setSelected] = useState(new Set()); + + return ( +
+ + Laundry + Fitness center + Parking + Swimming pool + Breakfast + +

Current selection: {selected === 'all' ? 'all' : [...selected].join(', ')}

+
+ ); +} +``` + +## API + +### TagGroup + + + +### TagList + + + + + +### Tag + + + + From 710198b224cc3fef6be12f3e7c1ed26f35735eb7 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Tue, 29 Jul 2025 11:24:54 -0700 Subject: [PATCH 028/193] RangeCalendar --- .../dev/s2-docs/pages/react-aria/Calendar.mdx | 15 +- .../pages/react-aria/MonthDropdown.tsx | 6 +- .../pages/react-aria/RangeCalendar.mdx | 372 ++++++++++++++++++ .../s2-docs/pages/react-aria/YearDropdown.tsx | 6 +- 4 files changed, 387 insertions(+), 12 deletions(-) create mode 100644 packages/dev/s2-docs/pages/react-aria/RangeCalendar.mdx diff --git a/packages/dev/s2-docs/pages/react-aria/Calendar.mdx b/packages/dev/s2-docs/pages/react-aria/Calendar.mdx index 34c9ab435e1..ad61bd54239 100644 --- a/packages/dev/s2-docs/pages/react-aria/Calendar.mdx +++ b/packages/dev/s2-docs/pages/react-aria/Calendar.mdx @@ -172,17 +172,15 @@ class Custom454 extends GregorianCalendar { ## Validation -Set the `isRequired`, `minValue`, or `maxValue` props to validate the value, or implement custom client or server-side validation. The `isDateUnavailable` callback prevents certain dates from being selected. See the Forms guide to learn more. +Use the `minValue` and `maxValue` props to set the valid date range. The `isDateUnavailable` callback prevents certain dates from being selected. For custom validation rules, set the `isInvalid` prop and the `errorMessage` slot. -```tsx render +```tsx render docs={vanillaDocs.exports.Calendar} links={docs.links} props={['isInvalid', 'errorMessage']} wide "use client"; import {isWeekend, today, getLocalTimeZone} from '@internationalized/date'; import {useLocale} from 'react-aria'; import {Calendar} from 'vanilla-starter/Calendar'; -import {Button} from 'vanilla-starter/Button'; -import {Form} from 'react-aria-components'; -function Example() { +function Example(props) { let {locale} = useLocale(); let now = today(getLocalTimeZone()); let disabledRanges = [ @@ -193,9 +191,10 @@ function Example() { return ( ( isWeekend(date, locale) || @@ -212,7 +211,7 @@ function Example() { Set the `visibleDuration` prop and render multiple `CalendarGrid` elements to display more than one month at a time. The `pageBehavior` prop controls whether pagination advances by a single month or multiple. The `firstDayOfWeek` prop overrides the locale-specified first day of the week. -```tsx render docs={docs.exports.Calendar} links={docs.links} props={['visibleDuration', 'pageBehavior', 'firstDayOfWeek']} initialProps={{visibleDuration: {months: 2}}} +```tsx render docs={docs.exports.Calendar} links={docs.links} props={['visibleDuration', 'pageBehavior', 'firstDayOfWeek']} initialProps={{visibleDuration: {months: 2}}} wide "use client"; import {Calendar, Heading, Button, CalendarGrid, CalendarCell} from 'react-aria-components'; import {useDateFormatter} from 'react-aria'; diff --git a/packages/dev/s2-docs/pages/react-aria/MonthDropdown.tsx b/packages/dev/s2-docs/pages/react-aria/MonthDropdown.tsx index d96aa0545b7..68b6279cc9f 100644 --- a/packages/dev/s2-docs/pages/react-aria/MonthDropdown.tsx +++ b/packages/dev/s2-docs/pages/react-aria/MonthDropdown.tsx @@ -1,5 +1,5 @@ import type {CalendarDate} from '@internationalized/date'; -import {CalendarStateContext} from 'react-aria-components'; +import {CalendarStateContext, RangeCalendarStateContext} from 'react-aria-components'; import {ReactElement, useContext} from 'react'; import {Select, SelectItem} from 'vanilla-starter/Select'; import {useDateFormatter} from 'react-aria'; @@ -11,7 +11,9 @@ interface MonthItem { } export function MonthDropdown(): ReactElement { - let state = useContext(CalendarStateContext)!; + let calendarState = useContext(CalendarStateContext); + let rangeCalendarState = useContext(RangeCalendarStateContext); + let state = calendarState || rangeCalendarState!; let formatter = useDateFormatter({ month: 'short', timeZone: state.timeZone diff --git a/packages/dev/s2-docs/pages/react-aria/RangeCalendar.mdx b/packages/dev/s2-docs/pages/react-aria/RangeCalendar.mdx new file mode 100644 index 00000000000..1ab2d94fb7b --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/RangeCalendar.mdx @@ -0,0 +1,372 @@ +import {Layout} from '../../src/Layout'; +export default Layout; + +import docs from 'docs:react-aria-components'; +import vanillaDocs from 'docs:vanilla-starter/RangeCalendar'; +import {RangeCalendar as VanillaRangeCalendar} from 'vanilla-starter/RangeCalendar'; +import {RangeCalendar as TailwindRangeCalendar} from 'tailwind-starter/RangeCalendar'; +import '../../tailwind/tailwind.css'; +import Anatomy from '@react-aria/calendar/docs/rangecalendar-anatomy.svg'; + +# RangeCalendar + +{docs.exports.RangeCalendar.description} + + + + + + +## Anatomy + + + +A range calendar consists of a grouping element containing one or more date grids (e.g. months), and a previous and next button for navigating through time. Each calendar grid consists of cells containing button elements that can be pressed and navigated to using the arrow keys to select a date range. Once a start date is selected, the user can navigate to another date using the keyboard or by hovering over it, and clicking it or pressing the Enter key commits the selected date range. + +`RangeCalendar` also supports an optional error message element, which can be used to provide more context about any validation errors. This is linked with the calendar via the `aria-describedby` attribute. + +```tsx +import {RangeCalendar, Heading, Button, CalendarGrid, CalendarGridHeader, CalendarHeaderCell, CalendarGridBody, CalendarCell, Text} from 'react-aria-components'; + + + + } +

{monthFormatter.format(state.visibleRange.start.add({months: i}).toDate(state.timeZone))}

+ {i === props.visibleDuration.months - 1 && + + } +
+ + {date => } + +
+ )) + )} + + ); +} +``` + +## Controlling the focused date + +Use the `focusedValue` or `defaultFocusedValue` prop to control which date is focused. This controls which month is visible. The `onFocusChange` event is called when a date is focused by the user. + +```tsx render +"use client"; +import {RangeCalendar} from 'vanilla-starter/RangeCalendar'; +import {Button} from 'vanilla-starter/Button'; +import {CalendarDate, today, getLocalTimeZone} from '@internationalized/date'; +import {useState} from 'react'; + +function Example() { + let defaultDate = new CalendarDate(2021, 7, 1); + let [focusedDate, setFocusedDate] = useState(defaultDate); + + return ( +
+ + +
+ ); +} +``` + +### Month and year pickers + +You can also control the focused date via `CalendarStateContext`. This example shows month and year dropdown components that work inside any ``. + +```tsx render files={['packages/dev/s2-docs/pages/react-aria/MonthDropdown.tsx', 'packages/dev/s2-docs/pages/react-aria/YearDropdown.tsx']} +"use client"; +import {RangeCalendar, CalendarGrid, CalendarCell} from 'react-aria-components'; +import {MonthDropdown} from './MonthDropdown'; +import {YearDropdown} from './YearDropdown'; +import {Button} from 'vanilla-starter/Button'; + + +
+ + {/*- begin highlight -*/} + + + {/*- end highlight -*/} + +
+ + {(date) => } + +
+``` + +## API + +### RangeCalendar + + + + + +### CalendarGrid + + + +{/* */} + +### CalendarGridHeader + + + +{/* */} + +### CalendarHeaderCell + + + +{/* */} + +### CalendarGridBody + + + +{/* */} + +### CalendarCell + + + + diff --git a/packages/dev/s2-docs/pages/react-aria/YearDropdown.tsx b/packages/dev/s2-docs/pages/react-aria/YearDropdown.tsx index 0e1aa6bb7c1..38a6df4df44 100644 --- a/packages/dev/s2-docs/pages/react-aria/YearDropdown.tsx +++ b/packages/dev/s2-docs/pages/react-aria/YearDropdown.tsx @@ -1,5 +1,5 @@ import type {CalendarDate} from '@internationalized/date'; -import {CalendarStateContext} from 'react-aria-components'; +import {CalendarStateContext, RangeCalendarStateContext} from 'react-aria-components'; import {ReactElement, useContext} from 'react'; import {Select, SelectItem} from 'vanilla-starter/Select'; import {useDateFormatter} from 'react-aria'; @@ -11,7 +11,9 @@ interface YearItem { } export function YearDropdown(): ReactElement { - let state = useContext(CalendarStateContext)!; + let calendarState = useContext(CalendarStateContext); + let rangeCalendarState = useContext(RangeCalendarStateContext); + let state = calendarState || rangeCalendarState!; let formatter = useDateFormatter({ year: 'numeric', timeZone: state.timeZone From 6f9e411db3687696cea38ad9eeb4fba43b7ecd7e Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Tue, 29 Jul 2025 17:09:42 -0500 Subject: [PATCH 029/193] add docs for ColorArea, ColorField, ColorSlider, ColorSwatch, ColorSwatchPicker, ColorWheel --- packages/dev/s2-docs/pages/s2/ColorArea.mdx | 125 +++++++++++++ packages/dev/s2-docs/pages/s2/ColorField.mdx | 120 ++++++++++++ packages/dev/s2-docs/pages/s2/ColorSlider.mdx | 157 ++++++++++++++++ packages/dev/s2-docs/pages/s2/ColorSwatch.mdx | 102 ++++++++++ .../s2-docs/pages/s2/ColorSwatchPicker.mdx | 132 +++++++++++++ packages/dev/s2-docs/pages/s2/ColorWheel.mdx | 176 ++++++++++++++++++ 6 files changed, 812 insertions(+) create mode 100644 packages/dev/s2-docs/pages/s2/ColorArea.mdx create mode 100644 packages/dev/s2-docs/pages/s2/ColorField.mdx create mode 100644 packages/dev/s2-docs/pages/s2/ColorSlider.mdx create mode 100644 packages/dev/s2-docs/pages/s2/ColorSwatch.mdx create mode 100644 packages/dev/s2-docs/pages/s2/ColorSwatchPicker.mdx create mode 100644 packages/dev/s2-docs/pages/s2/ColorWheel.mdx diff --git a/packages/dev/s2-docs/pages/s2/ColorArea.mdx b/packages/dev/s2-docs/pages/s2/ColorArea.mdx new file mode 100644 index 00000000000..7e3e0dfaa15 --- /dev/null +++ b/packages/dev/s2-docs/pages/s2/ColorArea.mdx @@ -0,0 +1,125 @@ +import {Layout} from '../../src/Layout'; +export default Layout; + +import {ColorArea} from '@react-spectrum/s2'; +import docs from 'docs:@react-spectrum/s2'; +import racDocs from 'docs:react-aria-components'; + +# ColorArea + +{docs.exports.ColorArea.description} + + + +## Value + +Use the `value` or `defaultValue` prop to set the color value, and the `xChannel` and `yChannel` props to specify which color channels to display. The value may be a string or object, parsed using the function. + +The `onChange` event is called as the user drags, and `onChangeEnd` is called when dragging ends. + +```tsx render +"use client"; +import {ColorArea} from '@react-spectrum/s2'; +import {useState} from 'react'; +import {parseColor} from '@react-stately/color'; + +function Example() { + let [currentValue, setCurrentValue] = useState(parseColor('hsl(50, 100%, 50%)')); + let [finalValue, setFinalValue] = useState(parseColor('hsl(50, 100%, 50%)')); + + return ( + <> + +

Current value: {currentValue.toString('hsl')}

+

Final value: {finalValue.toString('hsl')}

+ + ); +} +``` + +## Color spaces + +ColorArea supports the RGB, HSL, and HSB color spaces via the `colorSpace` prop. You can specify which color channels to display with the `xChannel` and `yChannel` props. + +```tsx render hideImports +"use client"; +import {ColorArea} from '@react-spectrum/s2'; + +
+
+

HSL

+ +
+
+

HSB

+ +
+
+

RGB

+ +
+
+``` + +## Events + +ColorArea supports user interactions via mouse, keyboard, and touch. You can handle color changes via the `onChange` and `onChangeEnd` props. + +```tsx render hideImports +"use client"; +import {ColorArea} from '@react-spectrum/s2'; +import {useState} from 'react'; + +function Example() { + let [events, setEvents] = useState([]); + + let addEvent = (type, value) => { + setEvents(prev => [...prev.slice(-2), `${type}: ${value}`]); + }; + + return ( + <> + addEvent('onChange', value.toString('hsl'))} + onChangeEnd={(value) => addEvent('onChangeEnd', value.toString('hsl'))} /> +
+ {events.map((event, i) => ( +
+ {event} +
+ ))} +
+ + ); +} +``` + +## Props + + diff --git a/packages/dev/s2-docs/pages/s2/ColorField.mdx b/packages/dev/s2-docs/pages/s2/ColorField.mdx new file mode 100644 index 00000000000..b00490aea5c --- /dev/null +++ b/packages/dev/s2-docs/pages/s2/ColorField.mdx @@ -0,0 +1,120 @@ +import {Layout} from '../../src/Layout'; +export default Layout; + +import {ColorField} from '@react-spectrum/s2'; +import docs from 'docs:@react-spectrum/s2'; + +# ColorField + +{docs.exports.ColorField.description} + + + +## Value + +A ColorField's value can be a hex color string or individual color channel values. Use the `value` or `defaultValue` prop to set the initial value, and the `onChange` event to handle changes. + +```tsx render +"use client"; +import {ColorField} from '@react-spectrum/s2'; +import {useState} from 'react'; +import {parseColor} from '@react-stately/color'; + +function Example() { + let [value, setValue] = useState(parseColor('#e73623')); + + return ( + <> + +

Current value: {value.toString('hsl')}

+ + ); +} +``` + +## Channel editing + +ColorField can be used to edit individual color channels by specifying the `channel` prop. This allows users to input numeric values for specific color components. + +```tsx render hideImports +"use client"; +import {ColorField} from '@react-spectrum/s2'; + +
+ + + +
+``` + +## Validation + +ColorField supports validation through the `isRequired` prop and custom validation functions. It integrates with forms and displays error states appropriately. + +```tsx render hideImports +"use client"; +import {ColorField, Button, Form} from '@react-spectrum/s2'; + +
+ + + +``` + +## Error state + +ColorField can display validation errors using the `isInvalid` and `errorMessage` props. + +```tsx render +"use client"; +import {ColorField} from '@react-spectrum/s2'; + + +``` + +## Help text + +You can provide additional context with the `description` prop. + +```tsx render +"use client"; +import {ColorField} from '@react-spectrum/s2'; + + +``` + +## Props + + diff --git a/packages/dev/s2-docs/pages/s2/ColorSlider.mdx b/packages/dev/s2-docs/pages/s2/ColorSlider.mdx new file mode 100644 index 00000000000..101711e7022 --- /dev/null +++ b/packages/dev/s2-docs/pages/s2/ColorSlider.mdx @@ -0,0 +1,157 @@ +import {Layout} from '../../src/Layout'; +export default Layout; + +import {ColorSlider} from '@react-spectrum/s2'; +import docs from 'docs:@react-spectrum/s2'; + +# ColorSlider + +{docs.exports.ColorSlider.description} + + + +## Value + +A ColorSlider's value can be controlled using the `value` or `defaultValue` prop. The `onChange` event is called as the user drags, and `onChangeEnd` is called when dragging ends. + +```tsx render +"use client"; +import {ColorSlider} from '@react-spectrum/s2'; +import {useState} from 'react'; +import {parseColor} from '@react-stately/color'; + +function Example() { + let [value, setValue] = useState(parseColor('hsl(180, 50%, 50%)')); + + return ( + <> + +

Current value: {value.toString('hsl')}

+ + ); +} +``` + +## Channels + +ColorSlider supports adjusting individual color channels using the `channel` prop. Different channels are available depending on the color space. + +```tsx render hideImports +"use client"; +import {ColorSlider} from '@react-spectrum/s2'; + +
+ + + + +
+``` + +## Orientation + +ColorSlider supports both horizontal and vertical orientations via the `orientation` prop. + +```tsx render hideImports +"use client"; +import {ColorSlider} from '@react-spectrum/s2'; + +
+ + +
+``` + +## Label + +ColorSlider displays the channel name as a label by default when no explicit label is provided. You can customize this with the `label` prop or hide it for vertical sliders by providing an `aria-label`. + +```tsx render hideImports +"use client"; +import {ColorSlider} from '@react-spectrum/s2'; + +
+ + + +
+``` + +## Events + +ColorSlider supports user interactions via mouse, keyboard, and touch. You can handle color changes via the `onChange` and `onChangeEnd` props. + +```tsx render hideImports +"use client"; +import {ColorSlider} from '@react-spectrum/s2'; +import {useState} from 'react'; + +function Example() { + let [events, setEvents] = useState([]); + + let addEvent = (type, value) => { + setEvents(prev => [...prev.slice(-2), `${type}: ${value}`]); + }; + + return ( + <> + addEvent('onChange', value.toString('hsl'))} + onChangeEnd={(value) => addEvent('onChangeEnd', value.toString('hsl'))} /> +
+ {events.map((event, i) => ( +
+ {event} +
+ ))} +
+ + ); +} +``` + +## Props + + diff --git a/packages/dev/s2-docs/pages/s2/ColorSwatch.mdx b/packages/dev/s2-docs/pages/s2/ColorSwatch.mdx new file mode 100644 index 00000000000..720816ca179 --- /dev/null +++ b/packages/dev/s2-docs/pages/s2/ColorSwatch.mdx @@ -0,0 +1,102 @@ +import {Layout} from '../../src/Layout'; +export default Layout; + +import {ColorSwatch} from '@react-spectrum/s2'; +import docs from 'docs:@react-spectrum/s2'; + +# ColorSwatch + +{docs.exports.ColorSwatch.description} + + + +## Value + +A ColorSwatch displays a color value passed via the `color` prop. The value can be a string or Color object. + +```tsx render +"use client"; +import {ColorSwatch} from '@react-spectrum/s2'; + +
+ + + + +
+``` + +## No value + +When no color is provided or the color is transparent, ColorSwatch displays a visual indicator (red slash) to represent the absence of a color. + +```tsx render +"use client"; +import {ColorSwatch} from '@react-spectrum/s2'; + +
+ + +
+``` + +## Custom sizing + +ColorSwatch can be resized using style overrides with the `styles` prop. + +```tsx render +"use client"; +import {ColorSwatch} from '@react-spectrum/s2'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +
+ + + +
+``` + +## Accessibility + +ColorSwatch automatically provides appropriate accessibility attributes. For interactive use cases or when additional context is needed, you can provide an `aria-label`. + +```tsx render +"use client"; +import {ColorSwatch} from '@react-spectrum/s2'; + + +``` + +## Transparent colors + +ColorSwatch displays transparent colors with a checkerboard pattern underneath to show the alpha channel effect. + +```tsx render hideImports +"use client"; +import {ColorSwatch} from '@react-spectrum/s2'; + +
+ + + + +
+``` + +## Props + + diff --git a/packages/dev/s2-docs/pages/s2/ColorSwatchPicker.mdx b/packages/dev/s2-docs/pages/s2/ColorSwatchPicker.mdx new file mode 100644 index 00000000000..25e0c90564f --- /dev/null +++ b/packages/dev/s2-docs/pages/s2/ColorSwatchPicker.mdx @@ -0,0 +1,132 @@ +import {Layout} from '../../src/Layout'; +export default Layout; + +import {ColorSwatchPicker, ColorSwatch} from '@react-spectrum/s2'; +import docs from 'docs:@react-spectrum/s2'; + +# ColorSwatchPicker + +{docs.exports.ColorSwatchPicker.description} + +```tsx render docs={docs.exports.ColorSwatchPicker} links={docs.links} props={['size', 'density', 'rounding']} initialProps={{defaultValue: '#f00'}} expanded type="s2" +import {ColorSwatchPicker, ColorSwatch} from '@react-spectrum/s2'; + + + + + + + + + +``` + +## Selection + +ColorSwatchPicker manages selection state for a collection of color swatches. Use the `value` or `defaultValue` prop to control which color is selected, and handle changes with the `onChange` event. + +```tsx render +"use client"; +import {ColorSwatchPicker, ColorSwatch} from '@react-spectrum/s2'; +import {useState} from 'react'; +import {parseColor} from '@react-stately/color'; + +function Example() { + let [selectedColor, setSelectedColor] = useState(parseColor('#e11d48')); + + return ( + <> + + + + + + + + + +

Selected: {selectedColor.toString('hsl')}

+ + ); +} +``` + +## Grid layout + +ColorSwatchPicker automatically wraps to multiple rows when there are many colors. You can constrain the width to control the layout. + +```tsx render +"use client"; +import {ColorSwatchPicker, ColorSwatch} from '@react-spectrum/s2'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + + + + + + + + + + + + + + + + + + +``` + +## Dynamic collections + +ColorSwatchPicker can be populated dynamically from data arrays, making it easy to work with programmatically generated color palettes. + +```tsx render hideImports +"use client"; +import {ColorSwatchPicker, ColorSwatch} from '@react-spectrum/s2'; + +function Example() { + let colors = [ + '#ff0000', '#ff8000', '#ffff00', '#80ff00', + '#00ff00', '#00ff80', '#00ffff', '#0080ff', + '#0000ff', '#8000ff', '#ff00ff', '#ff0080' + ]; + + return ( + + {colors.map(color => ( + + ))} + + ); +} +``` + +## Accessibility + +ColorSwatchPicker provides full keyboard navigation and screen reader support. Users can navigate between swatches using arrow keys and select with Enter or Space. + +```tsx render +"use client"; +import {ColorSwatchPicker, ColorSwatch} from '@react-spectrum/s2'; + + + + + + + + +``` + +## Props + + diff --git a/packages/dev/s2-docs/pages/s2/ColorWheel.mdx b/packages/dev/s2-docs/pages/s2/ColorWheel.mdx new file mode 100644 index 00000000000..becc5951d80 --- /dev/null +++ b/packages/dev/s2-docs/pages/s2/ColorWheel.mdx @@ -0,0 +1,176 @@ +import {Layout} from '../../src/Layout'; +export default Layout; + +import {ColorWheel} from '@react-spectrum/s2'; +import docs from 'docs:@react-spectrum/s2'; + +# ColorWheel + +{docs.exports.ColorWheel.description} + + + +## Value + +A ColorWheel's value can be controlled using the `value` or `defaultValue` prop. The `onChange` event is called as the user drags, and `onChangeEnd` is called when dragging ends. + +```tsx render +"use client"; +import {ColorWheel} from '@react-spectrum/s2'; +import {useState} from 'react'; +import {parseColor} from '@react-stately/color'; + +function Example() { + let [value, setValue] = useState(parseColor('hsl(0, 100%, 50%)')); + + return ( + <> + +

Current value: {value.toString('hsl')}

+ + ); +} +``` + +## Color spaces + +ColorWheel works with HSL and HSB color spaces to adjust the hue while maintaining saturation and lightness/brightness values. + +```tsx render hideImports +"use client"; +import {ColorWheel} from '@react-spectrum/s2'; + +
+
+ +
HSL
+
+
+ +
HSB
+
+
+``` + +## Events + +ColorWheel supports user interactions via mouse, keyboard, and touch. You can handle color changes via the `onChange` and `onChangeEnd` props. + +```tsx render hideImports +"use client"; +import {ColorWheel} from '@react-spectrum/s2'; +import {useState} from 'react'; + +function Example() { + let [events, setEvents] = useState([]); + + let addEvent = (type, value) => { + setEvents(prev => [...prev.slice(-2), `${type}: ${value}`]); + }; + + return ( + <> + addEvent('onChange', value.toString('hsl'))} + onChangeEnd={(value) => addEvent('onChangeEnd', value.toString('hsl'))} /> +
+ {events.map((event, i) => ( +
+ {event} +
+ ))} +
+ + ); +} +``` + +## Controlled hue + +ColorWheel is particularly useful for selecting hue values while keeping other color components constant. Here's an example that updates multiple color representations. + +```tsx render hideImports +"use client"; +import {ColorWheel, ColorSwatch} from '@react-spectrum/s2'; +import {useState} from 'react'; +import {parseColor} from '@react-stately/color'; + +function Example() { + let [color, setColor] = useState(parseColor('hsl(0, 80%, 60%)')); + + return ( +
+ +
+ +
+ {color.toString('hsl')} +
+
+
+ ); +} +``` + +## Accessibility + +ColorWheel provides keyboard navigation support. Users can adjust the hue using arrow keys, and use Page Up/Down for larger increments. + +```tsx render +"use client"; +import {ColorWheel} from '@react-spectrum/s2'; + + +``` + +## Integration + +ColorWheel works well alongside other color components to create complete color picking experiences. + +```tsx render hideImports +"use client"; +import {ColorWheel, ColorSlider, ColorSwatch} from '@react-spectrum/s2'; +import {useState} from 'react'; + +function Example() { + let [color, setColor] = useState('hsl(30, 80%, 60%)'); + + return ( +
+ +
+ + + +
+
+ ); +} +``` + +## Props + + From 8a1f9625571e2dc7fb886395e495f53246154d44 Mon Sep 17 00:00:00 2001 From: Daniel Lu Date: Tue, 29 Jul 2025 16:26:34 -0700 Subject: [PATCH 030/193] docs: add S2 tableview docs to new docs --- packages/@react-spectrum/s2/src/TableView.tsx | 1 + .../dev/s2-docs/pages/react-aria/Table.mdx | 12 +- packages/dev/s2-docs/pages/s2/TableView.mdx | 660 ++++++++++++++++++ 3 files changed, 667 insertions(+), 6 deletions(-) create mode 100644 packages/dev/s2-docs/pages/s2/TableView.mdx diff --git a/packages/@react-spectrum/s2/src/TableView.tsx b/packages/@react-spectrum/s2/src/TableView.tsx index 27c8cb292ac..a425c90713b 100644 --- a/packages/@react-spectrum/s2/src/TableView.tsx +++ b/packages/@react-spectrum/s2/src/TableView.tsx @@ -507,6 +507,7 @@ const columnStyles = style({ }); export interface ColumnProps extends Omit { + // TODO: this prop doesn't seem to work, is it supposed to be supported? /** Whether the column should render a divider between it and the next column. */ showDivider?: boolean, /** Whether the column allows resizing. */ diff --git a/packages/dev/s2-docs/pages/react-aria/Table.mdx b/packages/dev/s2-docs/pages/react-aria/Table.mdx index b1ca44cb569..803dc29a1b9 100644 --- a/packages/dev/s2-docs/pages/react-aria/Table.mdx +++ b/packages/dev/s2-docs/pages/react-aria/Table.mdx @@ -121,7 +121,7 @@ import {Table, TableHeader, TableBody, Column, Row, Cell, Button, Checkbox, Resi ## Content -`Table` follows the **Collection Components API**, accepting both static and dynamic collections. +`Table` follows the **Collection Components API**, accepting both static and dynamic collections. In this example, both the columns and the rows are provided to the table via a render function, enabling the user to hide and show columns and add additional rows. ```tsx render @@ -441,7 +441,7 @@ function SortableTable() { } return ( - {column => ( -
- File Name + {column.name}
@@ -625,7 +625,7 @@ function subscribe(fn) { ## Drag and drop -Table supports drag and drop interactions when the `dragAndDropHooks` prop is provided using the hook. Users can drop data on the table as a whole, on individual rows, insert new rows between existing ones, or reorder rows. React Aria supports drag and drop via mouse, touch, keyboard, and screen reader interactions. See the [drag and drop guide](dnd.html) to learn more. +`Table` supports drag and drop interactions when the `dragAndDropHooks` prop is provided using the hook. Users can drop data on the table as a whole, on individual rows, insert new rows between existing ones, or reorder rows. React Aria supports drag and drop via mouse, touch, keyboard, and screen reader interactions. See the [drag and drop guide](dnd.html) to learn more. ```tsx render "use client"; diff --git a/packages/dev/s2-docs/pages/s2/TableView.mdx b/packages/dev/s2-docs/pages/s2/TableView.mdx new file mode 100644 index 00000000000..72ae105010f --- /dev/null +++ b/packages/dev/s2-docs/pages/s2/TableView.mdx @@ -0,0 +1,660 @@ +import {Layout} from '../../src/Layout'; +export default Layout; + +import docs from 'docs:@react-spectrum/s2'; + +# TableView + +{docs.exports.TableView.description} + +```tsx render docs={docs.exports.TableView} links={docs.links} props={['selectionMode', 'density', 'disallowEmptySelection', 'escapeKeyBehavior', 'isQuiet', 'loadingState', 'overflowMode', 'shouldSelectOnPressUp']} initialProps={{'aria-label': 'Files', selectionMode: 'multiple'}} type="s2" +import {TableView, TableHeader, Column, TableBody, Row, Cell} from '@react-spectrum/s2'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + + + + Name + Type + Date Modified + + + + Games + File folder + 6/7/2020 + + + Program Files + File folder + 4/7/2021 + + + bootmgr + System file + 11/20/2010 + + + log.txt + Text Document + 1/18/2016 + + + +``` + +## Content + +`TableView` follows the **Collection Components API**, accepting both static and dynamic collections. +In this example, both the columns and the rows are provided to the table via a render function, enabling the user to hide and show columns and add additional rows. + +```tsx render +"use client"; +import {TableView, TableHeader, Column, TableBody, Row, Cell, CheckboxGroup, Checkbox, ActionButton} from '@react-spectrum/s2'; +import {useState} from 'react'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +///- begin collapse -/// +const columns = [ + {name: 'Name', id: 'name', isRowHeader: true}, + {name: 'Type', id: 'type'}, + {name: 'Date Modified', id: 'date'} +]; +///- end collapse -/// + +///- begin collapse -/// +const initialRows = [ + {id: 1, name: 'Games', date: '6/7/2020', type: 'File folder'}, + {id: 2, name: 'Program Files', date: '4/7/2021', type: 'File folder'}, + {id: 3, name: 'bootmgr', date: '11/20/2010', type: 'System file'}, + {id: 4, name: 'log.txt', date: '1/18/2016', type: 'Text Document'} +]; +///- end collapse -/// + +function FileTable() { + let [showColumns, setShowColumns] = useState(['name', 'type', 'date']); + let visibleColumns = columns.filter(column => showColumns.includes(column.id)); + + let [rows, setRows] = useState(initialRows); + let addRow = () => { + let date = new Date().toLocaleDateString(); + setRows(rows => [ + ...rows, + {id: rows.length + 1, name: 'file.txt', date, type: 'Text Document'} + ]); + }; + + return ( +
+ + Type + Date Modified + + + + {column => ( + + {column.name} + + )} + + {/*- begin highlight -*/} + + {item => ( + /*- end highlight -*/ + + {column => {item[column.id]}} + + )} + + + Add row +
+ ); +} +``` + +### Asynchronous loading + +To enable infinite scrolling, provide `loadingState` and `onLoadMore` to the `TableView`. Use whatever data fetching library you prefer – this example uses `useAsyncList` from `react-stately`. + +```tsx render +"use client"; +import {TableView, TableHeader, Column, TableBody, Row, Cell} from '@react-spectrum/s2'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; +import {useAsyncList} from 'react-stately'; + +interface Character { + name: string; + height: number; + mass: number; + birth_year: number; +} + +function AsyncSortTable() { + let list = useAsyncList({ + async load({ signal, cursor }) { + if (cursor) { + cursor = cursor.replace(/^http:\/\//i, 'https://'); + } + + let res = await fetch( + cursor || 'https://swapi.py4e.com/api/people/?search=', + { signal } + ); + let json = await res.json(); + + return { + items: json.results, + cursor: json.next + }; + } + }); + + return ( + + + Name + Height + Mass + Birth Year + + + {(item) => ( + + {item.name} + {item.height} + {item.mass} + {item.birth_year} + + )} + + + ); +} +``` + +### Links + +Use the `href` prop on a `` to create a link. See the **client side routing guide** to learn how to integrate with your framework. Link interactions vary depending on the selection behavior. See the [selection guide](selection.html) for more details. + +Will need to mention highlight mode later perhaps... + + +```tsx render docs={docs.exports.TableView} links={docs.links} props={['selectionMode']} initialProps={{'aria-label': 'Bookmarks', selectionMode: 'multiple'}} wide type="s2" hideImports +"use client"; +import {TableView, TableHeader, Column, TableBody, Row, Cell} from '@react-spectrum/s2'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + + + + Name + URL + Date added + + + {/*- begin highlight -*/} + + {/*- end highlight -*/} + Adobe + https://adobe.com/ + January 28, 2023 + + + Google + https://google.com/ + April 5, 2023 + + + New York Times + https://nytimes.com/ + July 12, 2023 + + + +``` + +### Empty state + +```tsx render hideImports +"use client"; +import {TableView, TableHeader, Column, TableBody} from '@react-spectrum/s2'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + + + + Name + Type + Date Modified + + {/*- begin highlight -*/} + 'No results found.'}> + {/*- end highlight -*/} + {[]} + + +``` + +### Column/Cell alignment and dividers + +Use the `align` prop on a Column and Cell to control the horizontal alignment of the content within. Similarly, use the `showDivider` prop to +render a visual divider between columns and cells for increased clarity. + +```tsx render hideImports +"use client"; +import {TableView, TableHeader, Column, TableBody, Row, Cell} from '@react-spectrum/s2'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +///- begin collapse -/// +const rows = [ + {id: 1, name: 'Charizard', type: 'Fire, Flying', level: 67}, + {id: 2, name: 'Blastoise', type: 'Water', level: 56}, + {id: 3, name: 'Venusaur', type: 'Grass, Poison', level: 83}, + {id: 4, name: 'Pikachu', type: 'Electric', level: 100} +]; +///- end collapse -/// + +const columns = [ + {id: 'name', name: 'Name', isRowHeader: true, showDivider: true}, + {id: 'type', name: 'Type', align: 'center', showDivider: true}, + {id: 'level', name: 'Level', align: 'end'} +]; + +function TableWithDividers() { + return ( + + + {/*- begin highlight -*/} + {(column) => ( + {column.name} + )} + {/*- end highlight -*/} + + + {item => ( + + {(column) => { + {/*- begin highlight -*/} + return {item[column.id]}; + {/*- end highlight -*/} + }} + + )} + + + ); +} +``` + +### Custom menus + +Custom column menus can be added via the `menuItems` props. This prop accepts the same contents that a `Menu` accepts, please see the docs for +more details. + +```tsx render +"use client"; +import {TableView, TableHeader, Column, TableBody, Row, Cell, MenuSection, MenuItem} from '@react-spectrum/s2'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +///- begin collapse -/// +const rows = [ + {id: 1, name: 'Charizard', type: 'Fire, Flying', level: 67}, + {id: 2, name: 'Blastoise', type: 'Water', level: 56}, + {id: 3, name: 'Venusaur', type: 'Grass, Poison', level: 83}, + {id: 4, name: 'Pikachu', type: 'Electric', level: 100} +]; +///- end collapse -/// + +///- begin collapse -/// +const columns = [ + {id: 'name', name: 'Name', isRowHeader: true}, + {id: 'type', name: 'Type'}, + {id: 'level', name: 'Level'} +]; +///- end collapse -/// + +function CustomMenusTable() { + return ( + + + {(column) => ( + + + Filter + + + Hide column + Manage columns + + + } + ///- end highlight -/// + isRowHeader={column?.isRowHeader} + > + {column.name} + + )} + + + {item => ( + + {(column) => { + return {item[column.id]}; + }} + + )} + + + ); +} +``` + + +## Selection and actions + +Use the `selectionMode` prop to enable single or multiple selection. The selected rows can be controlled via the `selectedKeys` prop, matching the `id` prop of the rows. Similarly, rows can be disabled via the `disabledKeys` prop. The `onAction` event handles item actions. See the [selection guide](selection.html) for more details. + +```tsx render docs={docs.exports.TableView} links={docs.links} props={['selectionMode', 'disallowEmptySelection']} initialProps={{selectionMode: 'multiple'}} wide hideImports +"use client"; +import type {Selection} from 'react-aria-components'; +import {TableView, TableHeader, Column, TableBody, Row, Cell} from '@react-spectrum/s2'; +import {useState} from 'react'; + +function Example(props) { + let [selected, setSelected] = useState(new Set()); + + return ( +
+ alert(`Clicked ${key}`)} + ///- end highlight -/// + > + + Name + Type + Level + + + + Charizard + Fire, Flying + 67 + + + Blastoise + Water + 56 + + + Venusaur + Grass, Poison + 83 + + + Pikachu + Electric + 100 + + + +

Current selection: {selected === 'all' ? 'all' : [...selected].join(', ')}

+
+ ); +} +``` + + +## Sorting + +Set the `allowsSorting` prop on a `` to make it sortable. When the column header is pressed, the table will call `onSortChange` with a including the sorted column and direction (ascending or descending). Use this to sort the data accordingly, and pass the `sortDescriptor` prop to the `TableView` to display the sorted column. + +```tsx render hideImports +"use client"; +import {TableView, TableHeader, Column, TableBody, Row, Cell} from '@react-spectrum/s2'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; +import {type SortDescriptor} from 'react-aria-components'; +import {useState} from 'react'; + +///- begin collapse -/// +const rows = [ + {id: 1, name: 'Charizard', type: 'Fire, Flying', level: 67}, + {id: 2, name: 'Blastoise', type: 'Water', level: 56}, + {id: 3, name: 'Venusaur', type: 'Grass, Poison', level: 83}, + {id: 4, name: 'Pikachu', type: 'Electric', level: 100} +]; +///- end collapse -/// + +function SortableTable() { + let [sortDescriptor, setSortDescriptor] = useState(null); + let sortedRows = rows; + if (sortDescriptor) { + sortedRows = rows.toSorted((a, b) => { + let first = a[sortDescriptor.column]; + let second = b[sortDescriptor.column]; + let cmp = first < second ? -1 : 1; + if (sortDescriptor.direction === 'descending') { + cmp = -cmp; + } + return cmp; + }); + } + + return ( + + + {/*- begin highlight -*/} + Name + Type + Level + {/*- end highlight -*/} + + + {item => ( + + {item.name} + {item.type} + {item.level} + + )} + + + ); +} +``` + + +## Column resizing + +Set the `allowsResizing` prop on a `` to make it resizable. Use the `defaultWidth`, `width`, `minWidth`, and `maxWidth` props on a `` to control resizing behavior. These accept pixels, percentages, or fractional values (the [fr](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout#the_fr_unit) unit). The default column width is `1fr`. + +```tsx render hideImports +"use client"; +import {TableView, TableHeader, Column, TableBody, Row, Cell} from '@react-spectrum/s2'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +///- begin collapse -/// +const rows = [ + {id: 1, name: '2022 Roadmap Proposal Revision 012822 Copy (2)', date: 'November 27, 2022 at 4:56PM', size: '214 KB'}, + {id: 2, name: 'Budget', date: 'January 27, 2021 at 1:56AM', size: '14 MB'}, + {id: 3, name: 'Welcome Email Template', date: 'July 24, 2022 at 2:48 PM', size: '20 KB'}, + {id: 4, name: 'Job Posting_8301', date: 'May 30, 2025', size: '139 KB'} +]; +///- end collapse -/// + + + + {/*- begin highlight -*/} + + File Name + + Size + + Date Modified + + {/*- end highlight -*/} + + + {item => ( + + {item.name} + {item.size} + {item.date} + + )} + + +``` + +### Resize events + +The `TableView`'s `onResize` event is called when a column resizer is moved by the user. The `onResizeEnd` event is called when the user finishes resizing. These receive a `Map` containing the widths of all columns in the `TableView`. This example persists the column widths in `localStorage`. + +```tsx render hideImports +"use client"; +import {TableView, TableHeader, Column, TableBody, Row, Cell} from '@react-spectrum/s2'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; +import {useSyncExternalStore} from 'react'; + +///- begin collapse -/// +const rows = [ + {id: 1, name: '2022 Roadmap Proposal Revision 012822 Copy (2)', date: 'November 27, 2022 at 4:56PM', size: '214 KB'}, + {id: 2, name: 'Budget', date: 'January 27, 2021 at 1:56AM', size: '14 MB'}, + {id: 3, name: 'Welcome Email Template', date: 'July 24, 2022 at 2:48 PM', size: '20 KB'}, + {id: 4, name: 'Job Posting_8301', date: 'May 30, 2025', size: '139 KB'} +]; +///- end collapse -/// + +///- begin collapse -/// +const columns = [ + {id: 'file', name: 'File Name'}, + {id: 'size', name: 'Size'}, + {id: 'date', name: 'Date'} +]; +///- end collapse -/// + +const initialWidths = new Map([ + ['file', '1fr'], + ['size', 80], + ['date', 100] +]); + +export default function ResizableTable() { + let columnWidths = useSyncExternalStore(subscribe, getColumnWidths, getInitialWidths); + + return ( + + + {column => ( + + {column.name} + + )} + + + {item => ( + + {item.name} + {item.size} + {item.date} + + )} + + + ); +} + +let parsedWidths; +function getColumnWidths() { + // Parse column widths from localStorage. + if (!parsedWidths) { + let data = localStorage.getItem('table-widths'); + if (data) { + parsedWidths = new Map(JSON.parse(data)); + } + } + return parsedWidths || initialWidths; +} + +function setColumnWidths(widths) { + // Store new widths in localStorage, and trigger subscriptions. + localStorage.setItem('table-widths', JSON.stringify(Array.from(widths))); + window.dispatchEvent(new Event('storage')); +} + +function getInitialWidths() { + return initialWidths; +} + +function subscribe(fn) { + let onStorage = () => { + // Invalidate cache. + parsedWidths = null; + fn(); + }; + + window.addEventListener('storage', onStorage); + return () => window.removeEventListener('storage', onStorage); +} +``` + +## API + +### TableView + + + +### TableHeader + + + +### Column + + + +### TableBody + + + +### Row + + + +### Cell + + From 57999e05483b0657413bbc28bfb7f2c294fb1ab4 Mon Sep 17 00:00:00 2001 From: Daniel Lu Date: Tue, 29 Jul 2025 16:34:43 -0700 Subject: [PATCH 031/193] fix alerts --- packages/dev/s2-docs/pages/s2/TableView.mdx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/dev/s2-docs/pages/s2/TableView.mdx b/packages/dev/s2-docs/pages/s2/TableView.mdx index 72ae105010f..034b57c0f36 100644 --- a/packages/dev/s2-docs/pages/s2/TableView.mdx +++ b/packages/dev/s2-docs/pages/s2/TableView.mdx @@ -183,9 +183,6 @@ function AsyncSortTable() { Use the `href` prop on a `` to create a link. See the **client side routing guide** to learn how to integrate with your framework. Link interactions vary depending on the selection behavior. See the [selection guide](selection.html) for more details. -Will need to mention highlight mode later perhaps... - - ```tsx render docs={docs.exports.TableView} links={docs.links} props={['selectionMode']} initialProps={{'aria-label': 'Bookmarks', selectionMode: 'multiple'}} wide type="s2" hideImports "use client"; import {TableView, TableHeader, Column, TableBody, Row, Cell} from '@react-spectrum/s2'; @@ -331,11 +328,11 @@ function CustomMenusTable() { menuItems={ <> - Filter + alert(`Filtering "${column.name}" column`)}>Filter - Hide column - Manage columns + alert(`Hiding "${column.name}" column`)}>Hide column + alert(`Managing the "${column.name}" column`)}>Manage columns } From 203dac45f660e1ff533275dce67df4ae639ecc98 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Tue, 29 Jul 2025 17:10:59 -0700 Subject: [PATCH 032/193] DateRangePicker --- .../@react-spectrum/s2/src/DatePicker.tsx | 2 +- .../s2/src/DateRangePicker.tsx | 2 +- .../@react-types/datepicker/src/index.d.ts | 23 +- .../s2-docs/pages/react-aria/DatePicker.mdx | 8 +- .../pages/react-aria/DateRangePicker.mdx | 201 ++++++++++++++++++ packages/dev/s2-docs/src/PropTable.tsx | 4 +- starters/docs/src/DatePicker.tsx | 7 +- starters/docs/src/DateRangePicker.css | 4 +- starters/docs/src/DateRangePicker.tsx | 4 +- 9 files changed, 229 insertions(+), 26 deletions(-) create mode 100644 packages/dev/s2-docs/pages/react-aria/DateRangePicker.mdx diff --git a/packages/@react-spectrum/s2/src/DatePicker.tsx b/packages/@react-spectrum/s2/src/DatePicker.tsx index 8ed4bd5fccd..03fc5d1a111 100644 --- a/packages/@react-spectrum/s2/src/DatePicker.tsx +++ b/packages/@react-spectrum/s2/src/DatePicker.tsx @@ -41,7 +41,7 @@ import {useSpectrumContextProps} from './useSpectrumContextProps'; export interface DatePickerProps extends Omit, 'children' | 'className' | 'style' | keyof GlobalDOMAttributes>, - Pick, 'createCalendar' | 'pageBehavior' | 'isDateUnavailable'>, + Pick, 'createCalendar' | 'pageBehavior' | 'firstDayOfWeek' | 'isDateUnavailable'>, StyleProps, SpectrumLabelableProps, HelpTextProps { diff --git a/packages/@react-spectrum/s2/src/DateRangePicker.tsx b/packages/@react-spectrum/s2/src/DateRangePicker.tsx index 6132469899a..03209c1106a 100644 --- a/packages/@react-spectrum/s2/src/DateRangePicker.tsx +++ b/packages/@react-spectrum/s2/src/DateRangePicker.tsx @@ -33,7 +33,7 @@ import {useSpectrumContextProps} from './useSpectrumContextProps'; export interface DateRangePickerProps extends Omit, 'children' | 'className' | 'style' | keyof GlobalDOMAttributes>, - Pick, 'createCalendar' | 'pageBehavior' | 'isDateUnavailable'>, + Pick, 'createCalendar' | 'pageBehavior' | 'firstDayOfWeek' | 'isDateUnavailable'>, StyleProps, SpectrumLabelableProps, HelpTextProps { diff --git a/packages/@react-types/datepicker/src/index.d.ts b/packages/@react-types/datepicker/src/index.d.ts index 652ca80f50b..340e05706e1 100644 --- a/packages/@react-types/datepicker/src/index.d.ts +++ b/packages/@react-types/datepicker/src/index.d.ts @@ -71,17 +71,7 @@ export interface AriaDateFieldProps extends DateFieldProps< autoComplete?: string } -interface DatePickerBase extends DateFieldBase, OverlayTriggerProps { - /** - * Controls the behavior of paging. Pagination either works by advancing the visible page by visibleDuration (default) or one unit of visibleDuration. - * @default visible - */ - pageBehavior?: PageBehavior, - /** - * The day that starts the week. - */ - firstDayOfWeek?: 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' -} +interface DatePickerBase extends DateFieldBase, OverlayTriggerProps {} export interface AriaDatePickerBaseProps extends DatePickerBase, AriaLabelingProps, InputDOMProps, DOMProps {} export interface DatePickerProps extends DatePickerBase, ValueBase | null> {} @@ -109,7 +99,7 @@ export interface DateRangePickerProps extends Omit extends Omit, 'validate'>, DateRangePickerProps {} +export interface AriaDateRangePickerProps extends Omit, 'validate' | 'name'>, DateRangePickerProps {} interface SpectrumDateFieldBase extends SpectrumLabelableProps, HelpTextProps, SpectrumFieldValidation>, StyleProps { /** @@ -125,6 +115,15 @@ interface SpectrumDateFieldBase extends SpectrumLabelablePr } interface SpectrumDatePickerBase extends SpectrumDateFieldBase, SpectrumLabelableProps, StyleProps { + /** + * Controls the behavior of paging. Pagination either works by advancing the visible page by visibleDuration (default) or one unit of visibleDuration. + * @default visible + */ + pageBehavior?: PageBehavior, + /** + * The day that starts the week. + */ + firstDayOfWeek?: 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat', /** * The maximum number of months to display at once in the calendar popover, if screen space permits. * @default 1 diff --git a/packages/dev/s2-docs/pages/react-aria/DatePicker.mdx b/packages/dev/s2-docs/pages/react-aria/DatePicker.mdx index b2c21f31262..47f411f4cbc 100644 --- a/packages/dev/s2-docs/pages/react-aria/DatePicker.mdx +++ b/packages/dev/s2-docs/pages/react-aria/DatePicker.mdx @@ -60,6 +60,8 @@ import {DatePicker, Label, Group, Popover, Dialog, Calendar, Button, DateInput, ``` +Most of this anatomy is shared with [DateRangePicker](DateRangePicker.html), so you can reuse many components between them. + ## Value Use the `value` or `defaultValue` prop to set the date value, using objects in the [@internationalized/date](../internationalized/date/) package. This library supports parsing date strings in multiple formats, manipulation across international calendar systems, time zones, etc. @@ -92,14 +94,14 @@ function Example() { The date format is automatically determined based on the user's locale. `DatePicker` supports several props to control how values are displayed. -```tsx render docs={docs.exports.DatePicker} links={docs.links} props={['granularity', 'firstDayOfWeek', 'hourCycle', 'hideTimeZone', 'shouldForceLeadingZeros']} initialProps={{label: 'Date'}} +```tsx render docs={docs.exports.DatePicker} links={docs.links} props={['granularity', 'hourCycle', 'hideTimeZone', 'shouldForceLeadingZeros']} initialProps={{label: 'Date'}} "use client"; import {parseZonedDateTime} from '@internationalized/date'; import {DatePicker} from 'vanilla-starter/DatePicker'; + /* PROPS */ + defaultValue={parseZonedDateTime('2025-02-03T08:45:00[America/Los_Angeles]')} /> ``` ### International calendars diff --git a/packages/dev/s2-docs/pages/react-aria/DateRangePicker.mdx b/packages/dev/s2-docs/pages/react-aria/DateRangePicker.mdx new file mode 100644 index 00000000000..6aed599b72e --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/DateRangePicker.mdx @@ -0,0 +1,201 @@ +import {Layout} from '../../src/Layout'; +export default Layout; + +import docs from 'docs:react-aria-components'; +import vanillaDocs from 'docs:vanilla-starter/DateRangePicker'; +import {DateRangePicker as VanillaDateRangePicker} from 'vanilla-starter/DateRangePicker'; +import {DateRangePicker as TailwindDateRangePicker} from 'tailwind-starter/DateRangePicker'; +import '../../tailwind/tailwind.css'; +import Anatomy from '@react-aria/datepicker/docs/daterangepicker-anatomy.svg'; + +# DateRangePicker + +{docs.exports.DateRangePicker.description} + + + + + + +## Anatomy + + + +A DateRangePicker consists of a label, and [Group](Group.html) containing two [DateField](DateField.html)s and a [Button](Button.html). Clicking the button opens a [Popover](Popover.html) containing a [RangeCalendar](RangeCalendar.html). The DateFields include segments representing each unit of a date and time (e.g. years, months, days, etc.), each of which is individually focusable and editable using the keyboard. + +`DateRangePicker` also supports optional description and `` elements. If there is no visible label, an `aria-label` or `aria-labelledby` must be provided to identify it to assistive technology. + +```tsx +import {DateRangePicker, Label, Group, Popover, Dialog, RangeCalendar, CalendarGrid, CalendarGridHeader, CalendarHeaderCell, CalendarGridBody, CalendarCell, Button, Heading, DateInput, DateSegment, Text, FieldError} from 'react-aria-components'; + + +