From 7898846dd91482f281e0a7b6c84f762b86774a6a Mon Sep 17 00:00:00 2001 From: Robert Snow Date: Tue, 4 Nov 2025 06:37:36 +1100 Subject: [PATCH 1/4] feat: Release notes for new docs (#9091) * feat: Release notes for new docs * divvy up the rest of S2 and remove from sidenav * add react aria components release notes * fix heading levels * update link * Fix all dates * fix code block display * fix rendering inside description * fix image and add actual github releases to relevant docs * fix code blocks and add archive link * fix missed page * remove RSP v3 references in the release notes to reduce noise for RAC users * remove more V3 PRs from the release notes for RAC * fix spacing * add metadata * fix examples page * add link to go to the releases list from any individual release * add aria label --- .../react-aria/assets/datepicker-rtl.png | Bin 0 -> 138460 bytes .../pages/react-aria/examples/index.mdx | 1 + .../pages/react-aria/releases/index.mdx | 20 ++ .../pages/react-aria/releases/v1-0-0.mdx | 199 +++++++++++ .../pages/react-aria/releases/v1-1-0.mdx | 154 +++++++++ .../pages/react-aria/releases/v1-10-0.mdx | 225 ++++++++++++ .../pages/react-aria/releases/v1-11-0.mdx | 241 +++++++++++++ .../pages/react-aria/releases/v1-12-0.mdx | 222 ++++++++++++ .../pages/react-aria/releases/v1-13-0.mdx | 218 ++++++++++++ .../pages/react-aria/releases/v1-2-0.mdx | 287 ++++++++++++++++ .../pages/react-aria/releases/v1-3-0.mdx | 282 +++++++++++++++ .../pages/react-aria/releases/v1-4-0.mdx | 236 +++++++++++++ .../pages/react-aria/releases/v1-5-0.mdx | 245 +++++++++++++ .../pages/react-aria/releases/v1-6-0.mdx | 219 ++++++++++++ .../pages/react-aria/releases/v1-7-0.mdx | 256 ++++++++++++++ .../pages/react-aria/releases/v1-8-0.mdx | 219 ++++++++++++ .../pages/react-aria/releases/v1-9-0.mdx | 200 +++++++++++ .../dev/s2-docs/pages/s2/release-notes.mdx | 324 ------------------ .../dev/s2-docs/pages/s2/releases/index.mdx | 20 ++ .../dev/s2-docs/pages/s2/releases/v0-1-0.mdx | 40 +++ .../dev/s2-docs/pages/s2/releases/v0-10-0.mdx | 48 +++ .../dev/s2-docs/pages/s2/releases/v0-11-0.mdx | 34 ++ .../dev/s2-docs/pages/s2/releases/v0-12-0.mdx | 46 +++ .../dev/s2-docs/pages/s2/releases/v0-2-0.mdx | 48 +++ .../dev/s2-docs/pages/s2/releases/v0-3-0.mdx | 46 +++ .../dev/s2-docs/pages/s2/releases/v0-4-0.mdx | 50 +++ .../dev/s2-docs/pages/s2/releases/v0-5-0.mdx | 71 ++++ .../dev/s2-docs/pages/s2/releases/v0-6-0.mdx | 40 +++ .../dev/s2-docs/pages/s2/releases/v0-7-0.mdx | 50 +++ .../dev/s2-docs/pages/s2/releases/v0-8-0.mdx | 39 +++ .../dev/s2-docs/pages/s2/releases/v0-9-0.mdx | 59 ++++ .../dev/s2-docs/pages/s2/releases/v0-9-1.mdx | 32 ++ packages/dev/s2-docs/src/Code.tsx | 22 +- packages/dev/s2-docs/src/CodeBlock.tsx | 2 +- packages/dev/s2-docs/src/GoUpOneLink.tsx | 70 ++++ packages/dev/s2-docs/src/Layout.tsx | 20 +- packages/dev/s2-docs/src/Nav.tsx | 5 +- packages/dev/s2-docs/src/ReleasesList.tsx | 34 ++ 38 files changed, 3990 insertions(+), 334 deletions(-) create mode 100644 packages/dev/s2-docs/pages/react-aria/assets/datepicker-rtl.png create mode 100644 packages/dev/s2-docs/pages/react-aria/releases/index.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/releases/v1-0-0.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/releases/v1-1-0.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/releases/v1-10-0.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/releases/v1-11-0.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/releases/v1-12-0.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/releases/v1-13-0.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/releases/v1-2-0.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/releases/v1-3-0.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/releases/v1-4-0.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/releases/v1-5-0.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/releases/v1-6-0.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/releases/v1-7-0.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/releases/v1-8-0.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/releases/v1-9-0.mdx delete mode 100644 packages/dev/s2-docs/pages/s2/release-notes.mdx create mode 100644 packages/dev/s2-docs/pages/s2/releases/index.mdx create mode 100644 packages/dev/s2-docs/pages/s2/releases/v0-1-0.mdx create mode 100644 packages/dev/s2-docs/pages/s2/releases/v0-10-0.mdx create mode 100644 packages/dev/s2-docs/pages/s2/releases/v0-11-0.mdx create mode 100644 packages/dev/s2-docs/pages/s2/releases/v0-12-0.mdx create mode 100644 packages/dev/s2-docs/pages/s2/releases/v0-2-0.mdx create mode 100644 packages/dev/s2-docs/pages/s2/releases/v0-3-0.mdx create mode 100644 packages/dev/s2-docs/pages/s2/releases/v0-4-0.mdx create mode 100644 packages/dev/s2-docs/pages/s2/releases/v0-5-0.mdx create mode 100644 packages/dev/s2-docs/pages/s2/releases/v0-6-0.mdx create mode 100644 packages/dev/s2-docs/pages/s2/releases/v0-7-0.mdx create mode 100644 packages/dev/s2-docs/pages/s2/releases/v0-8-0.mdx create mode 100644 packages/dev/s2-docs/pages/s2/releases/v0-9-0.mdx create mode 100644 packages/dev/s2-docs/pages/s2/releases/v0-9-1.mdx create mode 100644 packages/dev/s2-docs/src/GoUpOneLink.tsx create mode 100644 packages/dev/s2-docs/src/ReleasesList.tsx diff --git a/packages/dev/s2-docs/pages/react-aria/assets/datepicker-rtl.png b/packages/dev/s2-docs/pages/react-aria/assets/datepicker-rtl.png new file mode 100644 index 0000000000000000000000000000000000000000..a4447de1f807287a1210d30f274904b95306fa63 GIT binary patch literal 138460 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%0D$gA~ojr2}9X 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 literal 0 HcmV?d00001 diff --git a/packages/dev/s2-docs/pages/react-aria/examples/index.mdx b/packages/dev/s2-docs/pages/react-aria/examples/index.mdx index 9fe89b7dee7..96549d6fb18 100644 --- a/packages/dev/s2-docs/pages/react-aria/examples/index.mdx +++ b/packages/dev/s2-docs/pages/react-aria/examples/index.mdx @@ -3,6 +3,7 @@ export default Layout; export const section = 'Getting started'; export const hideFromSearch = true; +export const title = 'Examples'; # Examples diff --git a/packages/dev/s2-docs/pages/react-aria/releases/index.mdx b/packages/dev/s2-docs/pages/react-aria/releases/index.mdx new file mode 100644 index 00000000000..cb881be4e06 --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/releases/index.mdx @@ -0,0 +1,20 @@ +{/* Copyright 2020 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../../src/Layout'; +import {ReleasesList} from '../../../src/ReleasesList'; +export default Layout; + +export const section = 'Releases'; +export const tags = ['changelog', 'versions', 'updates']; +export const title = 'Releases'; + +# Releases + + page.name.includes('react-aria') && page.name.includes('releases') && !page.name.includes('index.html')) ?? []} /> diff --git a/packages/dev/s2-docs/pages/react-aria/releases/v1-0-0.mdx b/packages/dev/s2-docs/pages/react-aria/releases/v1-0-0.mdx new file mode 100644 index 00000000000..738e42604bd --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/releases/v1-0-0.mdx @@ -0,0 +1,199 @@ +{/* Copyright 2020 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../../src/Layout'; +export default Layout; + +import docs from 'docs:@react-spectrum/s2'; +import {Time} from '../../../src/ReleasesList'; + +export const section = ''; +export const tags = ['release', 'React Aria']; +export const date = 'December 20, 2023'; +export const title = 'v1.0.0'; +export const description = 'In this release, we\'re promoting React Aria Components from RC to GA 🎉, optimizing our included translation files, shipping a storybook based on all our React Aria Components with a starter theme, and fixing plenty of bugs.'; +export const isSubpage = true; + +# v1.0.0 +
-
+ } -
+
{children}
@@ -246,68 +248,16 @@ const Flash = createIcon(props => ( )); function ShadcnDialog({registryUrl}) { - let [packageManager, setPackageManager] = useState('npm'); - let command = packageManager; - if (packageManager === 'npx') { - command = 'npx'; - } else if (packageManager === 'pnpm') { - command = 'pnpm dlx'; - } - let componentName = registryUrl.match(/([^/]+)\.json$/)[1]; let preRef = useRef(null); - useEffect(() => { - let value = localStorage.getItem('packageManager'); - if (value) { - setPackageManager(value); - } - }, []); - - let onSelectionChange = value => { - setPackageManager(value); - localStorage.setItem('packageManager', value); - }; - return ( {({close}) => (<> Install with shadcn

Use the shadcn CLI to install {componentName} and its dependencies into your project.

-
- - npm - yarn - pnpm - -
- -
-                {command} shadcn@latest add {registryUrl}
-              
-
-
+
@@ -324,3 +274,66 @@ function ShadcnDialog({registryUrl}) {
); } + +const pulseAnimation = keyframes(` + 0% { + outline-width: 0px; + transform: scale(100%); + } + 50% { + outline-width: 8px; + transform: scale(104%); + } + 100% { + outline-width: 0px; + transform: scale(100%); + } +`); + + +const indicator = style({ + animation: pulseAnimation, + animationDuration: 2500, + animationIterationCount: 'infinite', + animationFillMode: 'forwards', + animationTimingFunction: 'in-out', + position: 'absolute', + inset: 0, + borderRadius: 'default', + borderWidth: 2, + borderColor: 'blue-800', + borderStyle: 'solid', + outlineColor: 'blue-800/20', + outlineWidth: 4, + outlineStyle: 'solid' +}); + +const toolbar = style({ + display: 'flex', + justifyContent: 'end', + padding: 4, + position: 'absolute', + top: 8, + insetEnd: 8, + backgroundColor: 'layer-2', + boxShadow: 'elevated', + borderRadius: 'default', + zIndex: 1 +}); + +function Toolbar({children, showCoachMark}) { + if (showCoachMark) { + children = ( + <> +
+ {children} + + ); + } + + return ( +
+ {children} +
+ ); +} diff --git a/packages/dev/s2-docs/src/Command.tsx b/packages/dev/s2-docs/src/Command.tsx index cac60837e11..22284c767f3 100644 --- a/packages/dev/s2-docs/src/Command.tsx +++ b/packages/dev/s2-docs/src/Command.tsx @@ -1,8 +1,9 @@ 'use client'; import {CopyButton} from './CopyButton'; +import {iconStyle, style} from '@react-spectrum/s2/style' with {type: 'macro'}; +import Prompt from '@react-spectrum/s2/icons/Prompt'; import React from 'react'; -import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; const container = style({ backgroundColor: 'layer-1', @@ -48,6 +49,7 @@ export function Command({command, label}: CommandProps) {
{label &&
{label}
}
+
{command}
diff --git a/packages/dev/s2-docs/src/ExampleSwitcher.tsx b/packages/dev/s2-docs/src/ExampleSwitcher.tsx index 2df3daebce0..b956dae9067 100644 --- a/packages/dev/s2-docs/src/ExampleSwitcher.tsx +++ b/packages/dev/s2-docs/src/ExampleSwitcher.tsx @@ -1,9 +1,10 @@ 'use client'; import {Content, ContextualHelp, Heading, Picker, PickerItem, SegmentedControl, SegmentedControlItem} from '@react-spectrum/s2'; -import {createContext, useEffect, useState} from 'react'; +import {createContext, useState} from 'react'; import {Key} from 'react-aria-components'; import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; +import {useLayoutEffect} from '@react-aria/utils'; const exampleStyle = style({ backgroundColor: 'layer-1', @@ -64,7 +65,7 @@ export function ExampleSwitcher({type = 'style', examples = DEFAULT_EXAMPLES, ch let [selected, setSelected] = useState(examples[0]); let [theme, setTheme] = useState('indigo'); - useEffect(() => { + useLayoutEffect(() => { if (!type) { return; } @@ -93,7 +94,7 @@ export function ExampleSwitcher({type = 'style', examples = DEFAULT_EXAMPLES, ch return () => controller.abort(); }, [type, examples]); - useEffect(() => { + useLayoutEffect(() => { document.documentElement.style.setProperty('--tint', `var(--${theme})`); }, [theme]); diff --git a/packages/dev/s2-docs/src/Headings.tsx b/packages/dev/s2-docs/src/Headings.tsx index 53852598ff1..92b1d0fc140 100644 --- a/packages/dev/s2-docs/src/Headings.tsx +++ b/packages/dev/s2-docs/src/Headings.tsx @@ -3,12 +3,24 @@ import {iconStyle, style} from '@react-spectrum/s2/style' with {type: 'macro'}; import {Link} from '@react-spectrum/s2'; import LinkIcon from '@react-spectrum/s2/icons/Link'; -import React from 'react'; +import React, {ReactNode} from 'react'; import {useFocusRing} from '@react-aria/focus'; import {useHover} from '@react-aria/interactions'; -function anchorId(children) { - return children.replace(/\s/g, '-').replace(/[^a-zA-Z0-9-_]/g, '').toLowerCase(); +function childrenToString(children: ReactNode) { + if (typeof children === 'string') { + return children; + } + + if (Array.isArray(children)) { + return children.reduce((p, c) => p + childrenToString(c), ''); + } + + return ''; +} + +function anchorId(children: ReactNode) { + return childrenToString(children).replace(/\s/g, '-').replace(/[^a-zA-Z0-9-_]/g, '').toLowerCase(); } function AnchorLink({anchorId, isHovered, level, headingText}) { diff --git a/packages/dev/s2-docs/src/InstallCommand.tsx b/packages/dev/s2-docs/src/InstallCommand.tsx index d930b548d59..68edd4340f2 100644 --- a/packages/dev/s2-docs/src/InstallCommand.tsx +++ b/packages/dev/s2-docs/src/InstallCommand.tsx @@ -1,10 +1,11 @@ 'use client'; import {CopyButton} from './CopyButton'; +import {iconStyle, style} from '@react-spectrum/s2/style' with {type: 'macro'}; import {Key} from 'react-aria-components'; +import Prompt from '@react-spectrum/s2/icons/Prompt'; import React, {useEffect, useMemo, useState} from 'react'; import {SegmentedControl, SegmentedControlItem} from '@react-spectrum/s2'; -import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; const container = style({ backgroundColor: 'layer-1', @@ -109,6 +110,7 @@ export function InstallCommand({pkg, flags, label, isCommand}: InstallCommandPro
{label &&
{label}
}
+
{command}
diff --git a/packages/dev/s2-docs/src/Nav.tsx b/packages/dev/s2-docs/src/Nav.tsx index a98b6715078..0a9d3acbe3d 100644 --- a/packages/dev/s2-docs/src/Nav.tsx +++ b/packages/dev/s2-docs/src/Nav.tsx @@ -179,7 +179,7 @@ function useCurrentSection() { let [selected, setSelected] = useState(''); useEffect(() => { - let elements = Array.from(document.querySelectorAll('article > :is(h2,h3,h4,h5)')); + let elements = Array.from(document.querySelectorAll('article :is(h2,h3,h4,h5)')); let visible = new Set(); let observer = new IntersectionObserver(entries => { for (let entry of entries) { diff --git a/packages/dev/s2-docs/src/ShadcnCommand.tsx b/packages/dev/s2-docs/src/ShadcnCommand.tsx new file mode 100644 index 00000000000..cbfb66f529c --- /dev/null +++ b/packages/dev/s2-docs/src/ShadcnCommand.tsx @@ -0,0 +1,67 @@ +'use client'; +import CopyButton from './CopyButton'; +import {iconStyle, style} from '@react-spectrum/s2/style' with {type: 'macro'}; +import {Key, SegmentedControl, SegmentedControlItem} from '@react-spectrum/s2'; +import Prompt from '@react-spectrum/s2/icons/Prompt'; +import {RefObject, useEffect, useState} from 'react'; + +export function ShadcnCommand({registryUrl, preRef}: {registryUrl: string, preRef?: RefObject}) { + let [packageManager, setPackageManager] = useState('npm'); + let command = packageManager; + if (packageManager === 'npm') { + command = 'npx'; + } else if (packageManager === 'pnpm') { + command = 'pnpm dlx'; + } + + useEffect(() => { + let value = localStorage.getItem('packageManager'); + if (value) { + setPackageManager(value); + } + }, []); + + let onSelectionChange = (value: Key) => { + setPackageManager(value); + localStorage.setItem('packageManager', String(value)); + }; + + let cmd = `${command} shadcn@latest add ${process.env.REGISTRY_URL || 'http://localhost:8081'}/${registryUrl}`; + + return ( +
+ + npm + yarn + pnpm + +
+ +
+          {cmd}
+        
+ +
+
+ ); +} diff --git a/packages/dev/s2-docs/src/StarterKits.tsx b/packages/dev/s2-docs/src/StarterKits.tsx new file mode 100644 index 00000000000..d05db85e817 --- /dev/null +++ b/packages/dev/s2-docs/src/StarterKits.tsx @@ -0,0 +1,60 @@ +import {Card, Content, Link, Text} from '@react-spectrum/s2'; +import Download from '@react-spectrum/s2/icons/Download'; +import {spawnSync} from 'child_process'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +const gitHash = process.env.GIT_HASH || spawnSync('git', ['rev-parse', '--short', 'HEAD']).stdout.toString().trim(); + +const preview = style({ + height: 'calc(100% + var(--card-padding-y) * 2)', + aspectRatio: 'square', + contain: 'strict', + marginStart: 'calc(var(--card-padding-x) * -1)', + marginEnd: '--card-padding-x', + marginY: 'calc(var(--card-padding-y) * -1)' +}); + +export function StarterKits() { + return ( +
+
+ +
+ + + + + + Vanilla CSS + Download ZIP + + +
+
+ Preview +
+
+ +
+ + + + + + + + + + + + Tailwind CSS + Download ZIP + + +
+
+ Preview +
+
+ ); +} diff --git a/packages/dev/s2-docs/src/Step.tsx b/packages/dev/s2-docs/src/Step.tsx new file mode 100644 index 00000000000..4902cd5b361 --- /dev/null +++ b/packages/dev/s2-docs/src/Step.tsx @@ -0,0 +1,65 @@ +import {raw} from '../../../@react-spectrum/s2/style/style-macro' with {type: 'macro'}; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +export function StepList({children}) { + return ( +
    + {children} +
+ ); +} + +export function Step({children}) { + return ( +
  • + {/*
    *)': 'none' + }, + position: 'absolute', + top: 'calc(1lh + 16px)', + left: 'calc(-1lh / 2 - 8px)', + bottom: -24, + width: 2, + borderRadius: 'full', + backgroundColor: 'gray-400' + })} /> */} + {children} +
  • + ); +} + +export function Counter() { + return ( + + ); +} diff --git a/packages/dev/s2-docs/src/VisualExample.tsx b/packages/dev/s2-docs/src/VisualExample.tsx index 6724d9e309f..5da3e6752f3 100644 --- a/packages/dev/s2-docs/src/VisualExample.tsx +++ b/packages/dev/s2-docs/src/VisualExample.tsx @@ -91,7 +91,8 @@ export interface VisualExampleProps { wide?: boolean, align?: 'center' | 'start' | 'end', acceptOrientation?: boolean, - propsObject?: string + propsObject?: string, + showCoachMark?: boolean } export interface PropControl extends Omit { @@ -105,7 +106,7 @@ export interface PropControl extends Omit { /** * Displays a component example with controls for changing the props. */ -export function VisualExample({component, docs, links, importSource, props, initialProps, controlOptions, files, code, wide, slots, align, acceptOrientation, type, propsObject}: VisualExampleProps) { +export function VisualExample({component, docs, links, importSource, props, initialProps, controlOptions, files, code, wide, slots, align, acceptOrientation, type, propsObject, showCoachMark}: VisualExampleProps) { let componentProps = docs.type === 'interface' ? docs : docs.props; if (componentProps?.type !== 'interface') { return null; @@ -160,7 +161,8 @@ export function VisualExample({component, docs, links, importSource, props, init code={code} files={files ? getFiles(files) : undefined} type={type} - registryUrl={type === 's2' || docs.type !== 'component' ? undefined : `${process.env.REGISTRY_URL || 'http://localhost:8081'}/${type}/${docs.name}.json`} /> + registryUrl={type === 's2' || docs.type !== 'component' ? undefined : `${type}/${docs.name}.json`} + showCoachMark={showCoachMark} /> ); // Render the corresponding client component to make the controls interactive. diff --git a/packages/dev/s2-docs/src/VisualExampleClient.tsx b/packages/dev/s2-docs/src/VisualExampleClient.tsx index faf7d43485a..c748e9545b9 100644 --- a/packages/dev/s2-docs/src/VisualExampleClient.tsx +++ b/packages/dev/s2-docs/src/VisualExampleClient.tsx @@ -148,10 +148,11 @@ interface CodeOutputProps { code?: ReactNode, files?: {[name: string]: string}, type?: 'vanilla' | 'tailwind' | 's2', - registryUrl?: string + registryUrl?: string, + showCoachMark?: boolean } -export function CodeOutput({code, files, type, registryUrl}: CodeOutputProps) { +export function CodeOutput({code, files, type, registryUrl, showCoachMark}: CodeOutputProps) { let {name, importSource, props, controls, propsObject} = useContext(Context); let searchParams = new URLSearchParams(); @@ -187,7 +188,7 @@ export function CodeOutput({code, files, type, registryUrl}: CodeOutputProps) { ); return ( - + {code} ); diff --git a/packages/dev/s2-docs/src/icons/Esbuild.tsx b/packages/dev/s2-docs/src/icons/Esbuild.tsx new file mode 100644 index 00000000000..2e386d17cbb --- /dev/null +++ b/packages/dev/s2-docs/src/icons/Esbuild.tsx @@ -0,0 +1,21 @@ +'use client'; + +import {createIcon} from '@react-spectrum/s2'; +import {useId} from 'react'; + +export default createIcon(props => { + let mask = useId(); + return ( + + + + + + + + ); +}); diff --git a/packages/dev/s2-docs/src/icons/Nextjs.tsx b/packages/dev/s2-docs/src/icons/Nextjs.tsx new file mode 100644 index 00000000000..32e8009b5fc --- /dev/null +++ b/packages/dev/s2-docs/src/icons/Nextjs.tsx @@ -0,0 +1,9 @@ +'use client'; + +import {createIcon} from '@react-spectrum/s2'; + +export default createIcon(props => ( + + + +)); diff --git a/packages/dev/s2-docs/src/icons/Parcel.tsx b/packages/dev/s2-docs/src/icons/Parcel.tsx new file mode 100644 index 00000000000..4da8655c7a8 --- /dev/null +++ b/packages/dev/s2-docs/src/icons/Parcel.tsx @@ -0,0 +1,9 @@ +'use client'; +import {createIcon} from '@react-spectrum/s2'; + +export default createIcon(props => ( + + + + +)); diff --git a/packages/dev/s2-docs/src/icons/ReactRouter.tsx b/packages/dev/s2-docs/src/icons/ReactRouter.tsx new file mode 100644 index 00000000000..33c905eb3e9 --- /dev/null +++ b/packages/dev/s2-docs/src/icons/ReactRouter.tsx @@ -0,0 +1,9 @@ +'use client'; + +import {createIcon} from '@react-spectrum/s2'; + +export default createIcon(props => ( + + + +)); diff --git a/packages/dev/s2-docs/src/icons/Rollup.tsx b/packages/dev/s2-docs/src/icons/Rollup.tsx new file mode 100644 index 00000000000..b1cbb225104 --- /dev/null +++ b/packages/dev/s2-docs/src/icons/Rollup.tsx @@ -0,0 +1,10 @@ +'use client'; + +import {createIcon} from '@react-spectrum/s2'; + +export default createIcon(props => ( + + + + +)); diff --git a/packages/dev/s2-docs/src/icons/Vite.tsx b/packages/dev/s2-docs/src/icons/Vite.tsx new file mode 100644 index 00000000000..a61e552b1ac --- /dev/null +++ b/packages/dev/s2-docs/src/icons/Vite.tsx @@ -0,0 +1,21 @@ +'use client'; +import {createIcon} from '@react-spectrum/s2'; +import {useId} from 'react'; + +export default createIcon(props => { + let path = useId(); + let mask = useId(); + return ( + + + + + + + + + + + + ); +}); diff --git a/packages/dev/s2-docs/src/icons/Webpack.tsx b/packages/dev/s2-docs/src/icons/Webpack.tsx new file mode 100644 index 00000000000..c78dc96d3d2 --- /dev/null +++ b/packages/dev/s2-docs/src/icons/Webpack.tsx @@ -0,0 +1,9 @@ +'use client'; +import {createIcon} from '@react-spectrum/s2'; + +export default createIcon(props => ( + + + + +)); diff --git a/packages/dev/s2-docs/src/routers.mdx b/packages/dev/s2-docs/src/routers.mdx new file mode 100644 index 00000000000..8f7d0bfea84 --- /dev/null +++ b/packages/dev/s2-docs/src/routers.mdx @@ -0,0 +1,73 @@ +import {Counter} from './Step'; + +Render a `RouterProvider` at the root of your app to enable React Aria links to use your client side router. This accepts two props: + +1. `navigate` – a function received from your router for performing a client side navigation programmatically. +2. `useHref` (optional) – converts a router-specific href to a native HTML href, e.g. prepending a base path. + + + +```tsx +// src/app.tsx +import {RouterProvider} from 'react-aria-components'; +import {BrowserRouter, useNavigate, useHref, type NavigateOptions} from 'react-router'; + +/*- begin highlight -*/ +// Configure the type of the `routerOptions` prop on all React Aria components. +declare module 'react-aria-components' { + interface RouterConfig { + routerOptions: NavigateOptions + } +} +/*- end highlight -*/ + +function App() { + let navigate = useNavigate(); + + return ( + /*- begin highlight -*/ + + {/*- end highlight -*/} + {/* Your app here... */} + + } /> + {/* ... */} + + + ); +} +``` + +```tsx +// src/routes/__root.tsx +import {RouterProvider} from 'react-aria-components'; +import {useRouter, type NavigateOptions, type ToOptions} from '@tanstack/react-router'; +import ReactDOM from 'react-dom/client'; + +/*- begin highlight -*/ +// Configure the type of the `href` and `routerOptions` props on all React Aria components. +declare module 'react-aria-components' { + interface RouterConfig { + href: ToOptions, + routerOptions: Omit + } +} +/*- end highlight -*/ + +export const Route = createRootRoute({ + component: () => { + let router = useRouter(); + return ( + /*- begin highlight -*/ + router.navigate({...href, ...opts})} + useHref={href => router.buildLocation(href).href}> + {/*- end highlight -*/} + {/* Your app here... */} + + ); + } +}); +``` + + diff --git a/packages/react-aria-components/src/index.ts b/packages/react-aria-components/src/index.ts index 3b70bc2e2b7..166d5b68f0b 100644 --- a/packages/react-aria-components/src/index.ts +++ b/packages/react-aria-components/src/index.ts @@ -82,7 +82,7 @@ export {useDrag, useDrop} from '@react-aria/dnd'; export {useDragAndDrop} from './useDragAndDrop'; export {DropIndicator, DropIndicatorContext, DragAndDropContext} from './DragAndDrop'; export {Virtualizer} from './Virtualizer'; -export {DIRECTORY_DRAG_TYPE, isDirectoryDropItem, isFileDropItem, isTextDropItem, SSRProvider, RouterProvider, I18nProvider, useLocale, useFilter, Pressable, Focusable, VisuallyHidden} from 'react-aria'; +export {DIRECTORY_DRAG_TYPE, isDirectoryDropItem, isFileDropItem, isTextDropItem, SSRProvider, RouterProvider, I18nProvider, isRTL, useLocale, useFilter, Pressable, Focusable, VisuallyHidden} from 'react-aria'; export {FormValidationContext, parseColor, getColorChannels, ToastQueue as UNSTABLE_ToastQueue} from 'react-stately'; export {ListLayout, GridLayout, WaterfallLayout} from '@react-stately/layout'; export {Layout, LayoutInfo, Size, Rect, Point} from '@react-stately/virtualizer'; From 65e644e3598a5beafc57ecf648a73693a081c036 Mon Sep 17 00:00:00 2001 From: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:56:02 -0800 Subject: [PATCH 3/4] docs: add s2 non-component hook docs (#9106) * docs: add non-component hook docs * add useClipboard * add more hook docs * clean up interaction hooks * clean up dnd hooks * cleanup focus hooks * cleanup internationalized hooks * add page description * fix types * fix lint * actually fix lint * remove console log * update css import * fix styles --------- Co-authored-by: Robert Snow --- .../s2-docs/pages/react-aria/Draggable.tsx | 20 + .../s2-docs/pages/react-aria/DropTarget.tsx | 51 +++ .../s2-docs/pages/react-aria/FocusRing.mdx | 48 ++ .../pages/react-aria/FocusRingExample.css | 14 + .../s2-docs/pages/react-aria/FocusScope.mdx | 130 ++++++ .../s2-docs/pages/react-aria/I18nProvider.mdx | 40 ++ .../pages/react-aria/MyToastRegion.tsx | 25 ++ .../pages/react-aria/PortalProvider.mdx | 171 +++++++ .../s2-docs/pages/react-aria/SSRProvider.mdx | 41 ++ .../pages/react-aria/VisuallyHidden.mdx | 67 +++ .../s2-docs/pages/react-aria/mergeProps.mdx | 70 +++ .../s2-docs/pages/react-aria/useClipboard.mdx | 417 ++++++++++++++++++ .../pages/react-aria/useClipboardExample.css | 29 ++ .../pages/react-aria/useClipboardGrid.css | 33 ++ .../s2-docs/pages/react-aria/useCollator.mdx | 74 ++++ .../pages/react-aria/useDateFormatter.mdx | 57 +++ .../dev/s2-docs/pages/react-aria/useDrag.mdx | 307 +++++++++++++ .../pages/react-aria/useDragExample.css | 25 ++ .../dev/s2-docs/pages/react-aria/useDrop.mdx | 360 +++++++++++++++ .../dev/s2-docs/pages/react-aria/useField.mdx | 68 +++ .../s2-docs/pages/react-aria/useFilter.mdx | 81 ++++ .../dev/s2-docs/pages/react-aria/useFocus.mdx | 85 ++++ .../s2-docs/pages/react-aria/useFocusRing.mdx | 79 ++++ .../pages/react-aria/useFocusVisible.mdx | 79 ++++ .../pages/react-aria/useFocusWithin.mdx | 102 +++++ .../dev/s2-docs/pages/react-aria/useHover.mdx | 114 +++++ .../dev/s2-docs/pages/react-aria/useId.mdx | 37 ++ .../dev/s2-docs/pages/react-aria/useIsSSR.mdx | 46 ++ .../s2-docs/pages/react-aria/useKeyboard.mdx | 83 ++++ .../dev/s2-docs/pages/react-aria/useLabel.mdx | 58 +++ .../s2-docs/pages/react-aria/useLandmark.mdx | 110 +++++ .../s2-docs/pages/react-aria/useLocale.mdx | 53 +++ .../s2-docs/pages/react-aria/useLongPress.mdx | 114 +++++ .../dev/s2-docs/pages/react-aria/useMove.mdx | 152 +++++++ .../pages/react-aria/useNumberFormatter.mdx | 61 +++ .../s2-docs/pages/react-aria/useObjectRef.mdx | 57 +++ .../dev/s2-docs/pages/react-aria/usePress.mdx | 114 +++++ packages/dev/s2-docs/src/FunctionAPI.tsx | 33 ++ 38 files changed, 3505 insertions(+) create mode 100644 packages/dev/s2-docs/pages/react-aria/Draggable.tsx create mode 100644 packages/dev/s2-docs/pages/react-aria/DropTarget.tsx create mode 100644 packages/dev/s2-docs/pages/react-aria/FocusRing.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/FocusRingExample.css create mode 100644 packages/dev/s2-docs/pages/react-aria/FocusScope.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/I18nProvider.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/MyToastRegion.tsx create mode 100644 packages/dev/s2-docs/pages/react-aria/PortalProvider.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/SSRProvider.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/VisuallyHidden.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/mergeProps.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useClipboard.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useClipboardExample.css create mode 100644 packages/dev/s2-docs/pages/react-aria/useClipboardGrid.css create mode 100644 packages/dev/s2-docs/pages/react-aria/useCollator.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useDateFormatter.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useDrag.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useDragExample.css create mode 100644 packages/dev/s2-docs/pages/react-aria/useDrop.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useField.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useFilter.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useFocus.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useFocusRing.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useFocusVisible.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useFocusWithin.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useHover.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useId.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useIsSSR.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useKeyboard.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useLabel.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useLandmark.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useLocale.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useLongPress.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useMove.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useNumberFormatter.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/useObjectRef.mdx create mode 100644 packages/dev/s2-docs/pages/react-aria/usePress.mdx create mode 100644 packages/dev/s2-docs/src/FunctionAPI.tsx diff --git a/packages/dev/s2-docs/pages/react-aria/Draggable.tsx b/packages/dev/s2-docs/pages/react-aria/Draggable.tsx new file mode 100644 index 00000000000..07a4cf27a3a --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/Draggable.tsx @@ -0,0 +1,20 @@ +"use client"; +import React from 'react'; +import {useDrag} from '@react-aria/dnd'; + +export function Draggable() { + let {dragProps, isDragging} = useDrag({ + getItems() { + return [{ + 'text/plain': 'hello world', + 'my-app-custom-type': JSON.stringify({message: 'hello world'}) + }]; + } + }); + + return ( +
    + Drag me +
    + ); +} \ No newline at end of file diff --git a/packages/dev/s2-docs/pages/react-aria/DropTarget.tsx b/packages/dev/s2-docs/pages/react-aria/DropTarget.tsx new file mode 100644 index 00000000000..2f4cb2e69ea --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/DropTarget.tsx @@ -0,0 +1,51 @@ +"use client"; + +import React, {JSX} from 'react'; +import type {TextDropItem} from '@react-aria/dnd'; +import {useDrop} from '@react-aria/dnd'; + +interface DroppedItem { + message: string; + style?: 'bold' | 'italic'; +} + +export function DropTarget() { + let [dropped, setDropped] = React.useState(null); + let ref = React.useRef(null); + let {dropProps, isDropTarget} = useDrop({ + ref, + async onDrop(e) { + let items = await Promise.all( + (e.items as TextDropItem[]) + .filter(item => item.kind === 'text' && (item.types.has('text/plain') || item.types.has('my-app-custom-type'))) + .map(async item => { + if (item.types.has('my-app-custom-type')) { + return JSON.parse(await item.getText('my-app-custom-type')); + } else { + return {message: await item.getText('text/plain')}; + } + }) + ); + setDropped(items); + } + }); + + let message: JSX.Element[] = [
    {`Drop here`}
    ]; + if (dropped) { + message = dropped.map((d, index) => { + let m = d.message; + if (d.style === 'bold') { + message = [{m}]; + } else if (d.style === 'italic') { + message = [{m}]; + } + return
    {message}
    ; + }); + } + + return ( +
    + {message} +
    + ); +} \ No newline at end of file diff --git a/packages/dev/s2-docs/pages/react-aria/FocusRing.mdx b/packages/dev/s2-docs/pages/react-aria/FocusRing.mdx new file mode 100644 index 00000000000..52d8a4aac79 --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/FocusRing.mdx @@ -0,0 +1,48 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import {GroupedPropTable} from '../../src/PropTable'; +import {FunctionAPI} from '../../src/FunctionAPI'; +import docs from 'docs:@react-aria/focus'; + +export const section = 'Focus'; + +# FocusRing + +{docs.exports.FocusRing.description} + +## Introduction + +`FocusRing` is a utility component that can be used to apply a CSS class when an element has keyboard focus. +This helps keyboard users determine which element on a page or in an application has keyboard focus as they +navigate around. Focus rings are only visible when interacting with a keyboard so as not to distract mouse +and touch screen users. When we are unable to detect if the user is using a mouse or touch screen, such as +switching in from a different tab, we show the focus ring. + +If CSS classes are not being used for styling, see [useFocusRing](useFocusRing.html) for a hooks version. + +## Props + + + +## Example + +This example shows how to use `` to apply a CSS class when keyboard focus is on a button. + +```tsx render files={["packages/dev/s2-docs/pages/react-aria/FocusRingExample.css"]} +'use client'; +import {FocusRing} from '@react-aria/focus'; +import './FocusRingExample.css'; + + + + +``` diff --git a/packages/dev/s2-docs/pages/react-aria/FocusRingExample.css b/packages/dev/s2-docs/pages/react-aria/FocusRingExample.css new file mode 100644 index 00000000000..3d335cd4e5b --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/FocusRingExample.css @@ -0,0 +1,14 @@ +.button { + -webkit-appearance: none; + appearance: none; + background: green; + border: none; + color: white; + font-size: 14px; + padding: 4px 8px; +} + +.button.focus-ring { + outline: 2px solid dodgerblue; + outline-offset: 2px; +} \ No newline at end of file diff --git a/packages/dev/s2-docs/pages/react-aria/FocusScope.mdx b/packages/dev/s2-docs/pages/react-aria/FocusScope.mdx new file mode 100644 index 00000000000..bf41919a10d --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/FocusScope.mdx @@ -0,0 +1,130 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import {GroupedPropTable} from '../../src/PropTable'; +import {FunctionAPI} from '../../src/FunctionAPI'; +import docs from 'docs:@react-aria/focus'; + +export const section = 'Focus'; + +# FocusScope + +{docs.exports.FocusScope.description} + +## Introduction + +`FocusScope` is a utility component that can be used to manage focus for its descendants. +When the `contain` prop is set, focus is contained within the scope. This is useful when +implementing overlays like modal dialogs, which should not allow focus to escape them while open. +In addition, the `restoreFocus` prop can be used to restore focus back to the previously focused +element when the focus scope unmounts, for example, back to a button which opened a dialog. +A FocusScope can also optionally auto focus the first focusable element within it on mount +when the `autoFocus` prop is set. + +The hook can also be used +in combination with a FocusScope to programmatically move focus within the scope. For example, +arrow key navigation could be implemented by handling keyboard events and using a focus manager +to move focus to the next and previous elements. + +## Props + + + +## FocusManager Interface + +To get a focus manager, call the hook +from a component within the FocusScope. A focus manager supports the following methods: + + + +## Example + +A basic example of a focus scope that contains focus within it is below. Clicking the "Open" +button mounts a FocusScope, which auto focuses the first input inside it. Once open, you can +press the Tab key to move within the scope, but focus is contained inside. Clicking the "Close" +button unmounts the focus scope, which restores focus back to the button. + +{/* Not implemented yet */} +{/* For a full example of building a modal dialog, see [useDialog](useDialog.html). */} + +```tsx render +'use client'; +import React from 'react'; +import {FocusScope} from '@react-aria/focus'; + +function Example() { + let [isOpen, setOpen] = React.useState(false); + return ( + <> + + {isOpen && + + + + + + + + } + + ); +} +``` + +## useFocusManager Example + +This example shows how to use `useFocusManager` to programmatically move focus within a +`FocusScope`. It implements a basic toolbar component, which allows using the left and +right arrow keys to move focus to the previous and next buttons. The `wrap` option is +used to make focus wrap around when it reaches the first or last button. + +```tsx render +'use client'; +import {FocusScope} from '@react-aria/focus'; +import {useFocusManager} from '@react-aria/focus'; + +function Toolbar(props) { + return ( +
    + + {props.children} + +
    + ); +} + +function ToolbarButton(props) { + let focusManager = useFocusManager(); + let onKeyDown = (e) => { + switch (e.key) { + case 'ArrowRight': + focusManager.focusNext({wrap: true}); + break; + case 'ArrowLeft': + focusManager.focusPrevious({wrap: true}); + break; + } + }; + + return ( + + ); +} + + + Cut + Copy + Paste + +``` diff --git a/packages/dev/s2-docs/pages/react-aria/I18nProvider.mdx b/packages/dev/s2-docs/pages/react-aria/I18nProvider.mdx new file mode 100644 index 00000000000..9bdc658f95d --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/I18nProvider.mdx @@ -0,0 +1,40 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import {FunctionAPI} from '../../src/FunctionAPI'; +import docs from 'docs:@react-aria/i18n'; + +export const section = 'Internationalization'; +export const description = 'Implementing collections in React Aria'; + + +# I18nProvider + +## Introduction + +`I18nProvider` allows you to override the default locale as determined by the browser/system setting +with a locale defined by your application (e.g. application setting). This should be done by wrapping +your entire application in the provider, which will be cause all child elements to receive the new locale +information via [useLocale](useLocale.html). + +## Props + + + +## Example + +```tsx +import {I18nProvider} from '@react-aria/i18n'; + + + + +``` diff --git a/packages/dev/s2-docs/pages/react-aria/MyToastRegion.tsx b/packages/dev/s2-docs/pages/react-aria/MyToastRegion.tsx new file mode 100644 index 00000000000..3e5e733fe74 --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/MyToastRegion.tsx @@ -0,0 +1,25 @@ +'use client'; +import React from 'react'; +import {UNSTABLE_ToastRegion as ToastRegion, UNSTABLE_Toast as Toast, UNSTABLE_ToastQueue as ToastQueue, UNSTABLE_ToastContent as ToastContent, Button, Text} from 'react-aria-components'; + +// Define the type for your toast content. +interface MyToastContent { + title: string, + description?: string +} + +export function MyToastRegion({queue}: {queue: ToastQueue}) { + return ( + + {({toast}) => ( + + + {toast.content.title} + {toast.content.description} + + + + )} + + ); +} \ No newline at end of file diff --git a/packages/dev/s2-docs/pages/react-aria/PortalProvider.mdx b/packages/dev/s2-docs/pages/react-aria/PortalProvider.mdx new file mode 100644 index 00000000000..c161d7d7b79 --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/PortalProvider.mdx @@ -0,0 +1,171 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import docs from 'docs:@react-aria/overlays'; +import {FunctionAPI} from '../../src/FunctionAPI'; + +export const section = 'Server Side Rendering'; + +# PortalProvider + +{docs.exports.UNSAFE_PortalProvider.description} + +## Introduction + +`UNSAFE_PortalProvider` is a utility wrapper component that can be used to set where components like +Modals, Popovers, Toasts, and Tooltips will portal their overlay element to. This is typically used when +your app is already portalling other elements to a location other than the `document.body` and thus requires +your React Aria components to send their overlays to the same container. + +Please note that `UNSAFE_PortalProvider` is considered `UNSAFE` because it is an escape hatch, and there are +many places that an application could portal to. Not all of them will work, either with styling, accessibility, +or for a variety of other reasons. Typically, it is best to portal to the root of the entire application, e.g. the `body` element, +outside of any possible overflow or stacking contexts. We envision `UNSAFE_PortalProvider` being used to group all of the portalled +elements into a single container at the root of the app or to control the order of children of the `body` element, but you may have use cases +that need to do otherwise. + +## Props + + + +## Example + +The example below shows how you can use `UNSAFE_PortalProvider` to portal your Toasts to an arbitrary container. Note that +the Toast in this example is taken directly from the [React Aria Components Toast documentation](Toast.html#example), please visit that page for +a detailed explanation of its implementation. + +```tsx render files={["packages/dev/s2-docs/pages/react-aria/MyToastRegion.tsx"]} +'use client'; +import React from 'react'; +import {Button} from 'vanilla-starter/Button'; +import {MyToastRegion} from './MyToastRegion.tsx' +import {UNSAFE_PortalProvider} from '@react-aria/overlays'; +import {UNSTABLE_ToastRegion as ToastRegion, UNSTABLE_Toast as Toast, UNSTABLE_ToastQueue as ToastQueue, UNSTABLE_ToastContent as ToastContent, Button, Text} from 'react-aria-components'; + +// Define the type for your toast content. +interface MyToastContent { + title: string, + description?: string +} + +// Create a global ToastQueue. +const queue = new ToastQueue(); + +// See the above Toast docs link for the ToastRegion implementation +function App() { + let container = React.useRef(null); + return ( + <> + container.current}> + + + +
    + Toasts are portalled here! +
    + + ); +} + + +``` + +```css render hidden +.react-aria-ToastRegion { + position: unset; + bottom: 16px; + right: 16px; + display: flex; + flex-direction: column-reverse; + gap: 8px; + border-radius: 8px; + outline: none; + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: 2px; + } +} + +.react-aria-Toast { + display: flex; + align-items: center; + gap: 16px; + background: var(--highlight-background); + color: white; + padding: 12px 16px; + border-radius: 8px; + outline: none; + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: 2px; + } + + .react-aria-ToastContent { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-width: 0px; + + [slot=title] { + font-weight: bold; + } + } + + .react-aria-Button[slot=close] { + flex: 0 0 auto; + background: none; + border: none; + appearance: none; + border-radius: 50%; + height: 32px; + width: 32px; + font-size: 16px; + border: 1px solid var(--highlight-foreground); + color: white; + padding: 0; + outline: none; + + &[data-focus-visible] { + box-shadow: 0 0 0 2px var(--highlight-background), 0 0 0 4px var(--highlight-foreground); + } + + &[data-pressed] { + background: var(--highlight-pressed); + } + } +} + +``` + +## Contexts + +The `getContainer` set by the nearest PortalProvider can be accessed by calling `useUNSAFE_PortalContext`. This can be +used by custom overlay components to ensure that they are also being consistently portalled throughout your app. + + + +```tsx +import {useUNSAFE_PortalContext} from '@react-aria/overlays'; + +function MyOverlay(props) { + let {children} = props; + let {getContainer} = useUNSAFE_PortalContext(); + return ReactDOM.createPortal(children, getContainer()); +} +``` diff --git a/packages/dev/s2-docs/pages/react-aria/SSRProvider.mdx b/packages/dev/s2-docs/pages/react-aria/SSRProvider.mdx new file mode 100644 index 00000000000..37f23f1bbbe --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/SSRProvider.mdx @@ -0,0 +1,41 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import docs from 'docs:@react-aria/ssr'; + +export const section = 'Server Side Rendering'; +export const description = 'Implementing collections in React Aria'; + +# SSRProvider + +## Introduction + +If you're using React 16 or 17, `SSRProvider` should be used as a wrapper for the entire application during server side rendering. +It works together with the [useId](useId.html) hook to ensure that auto generated ids are consistent +between the client and server by resetting the id internal counter on each request. +See the [server side rendering](ssr.html) docs for more information. + +**Note**: if you're using React 18 or newer, `SSRProvider` is not necessary and can be removed from your app. React Aria uses the +[React.useId](https://react.dev/reference/react/useId) hook internally when using React 18, which ensures ids are consistent. + +## Props + + + +## Example + +```tsx +import {SSRProvider} from '@react-aria/ssr'; + + + + +``` diff --git a/packages/dev/s2-docs/pages/react-aria/VisuallyHidden.mdx b/packages/dev/s2-docs/pages/react-aria/VisuallyHidden.mdx new file mode 100644 index 00000000000..14fb52ee5fe --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/VisuallyHidden.mdx @@ -0,0 +1,67 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import docs from 'docs:@react-aria/visually-hidden'; +import {FunctionAPI} from '../../src/FunctionAPI'; + +export const section = 'Utilities'; + +# VisuallyHidden + +{docs.exports.VisuallyHidden.description} + +## Introduction + +`VisuallyHidden` is a utility component that can be used to hide its children visually, +while keeping them visible to screen readers and other assistive technology. This would +typically be used when you want to take advantage of the behavior and semantics of a +native element like a checkbox or radio button, but replace it with a custom styled +element visually. + +## Props + + + +{/* not implemented yet */} +{/* ## Example + +See [useRadioGroup](useRadioGroup.html#styling) and [useCheckbox](useCheckbox.html#styling) +for examples of using `VisuallyHidden` to hide native form elements visually. */} + +## Hook + +In order to allow additional rendering flexibility, the `useVisuallyHidden` hook can be +used in custom components instead of the `VisuallyHidden` component. It supports the same +options as the component, and returns props to spread onto the element that should be hidden. + + + +```tsx +import {useVisuallyHidden} from '@react-aria/visually-hidden'; + +let {visuallyHiddenProps} = useVisuallyHidden(); + +
    I am hidden
    +``` + +## Gotchas + +VisuallyHidden is positioned absolutely, so it needs a positioned ancestor. Otherwise, it will be positioned relative to the nearest positioned ancestor, which may be the body, causing undesired scroll bars to appear. + +```tsx + +``` + diff --git a/packages/dev/s2-docs/pages/react-aria/mergeProps.mdx b/packages/dev/s2-docs/pages/react-aria/mergeProps.mdx new file mode 100644 index 00000000000..fee98fa10a5 --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/mergeProps.mdx @@ -0,0 +1,70 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import docs from 'docs:@react-aria/utils'; +import {FunctionAPI} from '../../src/FunctionAPI'; + +export const section = 'Utilities'; + +# mergeProps + +{docs.exports.mergeProps.description} + +## API + + + +## Introduction + +`mergeProps` is a utility function that combines multiple props objects together in a smart way. +This can be useful when you need to combine the results of multiple react-aria hooks onto a single +element. For example, both hooks may return the same event handlers, class names, or ids, and only +one of these can be applied. `mergeProps` handles combining these props together so that multiple +event handlers will be chained, multiple classes will be combined, and ids will be deduplicated. +For all other props, the last prop object overrides all previous ones. + +## Example + +```tsx +import {mergeProps} from '@react-aria/utils'; + +let a = { + className: 'foo', + onKeyDown(e) { + if (e.key === 'Enter') { + console.log('enter') + } + } +}; + +let b = { + className: 'bar', + onKeyDown(e) { + if (e.key === ' ') { + console.log('space') + } + } +}; + +let merged = mergeProps(a, b); +``` + +The result of the above example will be equivalent to this: + +```tsx +let merged = { + className: 'foo bar', + onKeyDown(e) { + a.onKeyDown(e); + b.onKeyDown(e); + } +}; +``` diff --git a/packages/dev/s2-docs/pages/react-aria/useClipboard.mdx b/packages/dev/s2-docs/pages/react-aria/useClipboard.mdx new file mode 100644 index 00000000000..e044de4e0b1 --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/useClipboard.mdx @@ -0,0 +1,417 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import {FunctionAPI} from '../../src/FunctionAPI'; +import docs from 'docs:@react-aria/dnd'; +import sharedDocs from 'docs:@react-types/shared/src/dnd.d.ts'; +export const section = 'Drag and Drop'; + +# useClipboard + +{docs.exports.useClipboard.description} + +## API + + + +## Introduction + +Copy and paste is a common way to transfer data between locations, either within or between apps. Browsers support copy and paste of selected text content by default, but rich objects with custom data can also be copied and pasted using the [clipboard events](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent) API. For example, an app could support copying and pasting a selected card representing a rich object to a new location, or allow a user to paste files from their device to upload them. This can provide a keyboard accessible alternative to drag and drop. + +The hook provides a simple way to implement copy and paste for a focusable element. When focused, users can press keyboard shortcuts like ⌘C and ⌘V, or even use the browser's "Copy" and "Paste" menu commands, to trigger clipboard events. Multiple items can be copied and pasted at once, each represented in one or more different data formats. Because it uses native browser APIs under the hood, copy and paste uses the operating system clipboard, which means it works between applications (e.g. Finder, Windows Explorer, a native email app, etc.) in addition to within the app. + +## Example + +This example shows a simple focusable element which supports copying a string when focused, and another element which supports pasting plain text. + +```tsx render files={["packages/dev/s2-docs/pages/react-aria/useClipboardExample.css"]} +'use client'; +import React from 'react'; +import type {TextDropItem} from '@react-aria/dnd'; +import {useClipboard} from '@react-aria/dnd'; +import './useClipboardExample.css'; +import 'vanilla-starter/theme.css'; + +function Copyable() { + let {clipboardProps} = useClipboard({ + getItems() { + return [{ + 'text/plain': 'Hello world' + }]; + } + }); + + return ( +
    + Hello world + ⌘C +
    + ); +} + +function Pasteable() { + let [pasted, setPasted] = React.useState(null); + let {clipboardProps} = useClipboard({ + async onPaste(items) { + let pasted = await Promise.all( + items + .filter((item) => + item.kind === 'text' && item.types.has('text/plain') + ) + .map((item: TextDropItem) => item.getText('text/plain')) + ); + setPasted(pasted.join('\n')); + } + }); + + return ( +
    + {pasted || 'Paste here'} + ⌘V +
    + ); +} + +
    + + +
    +``` + +## Copy data + +Data to copy can be provided in multiple formats at once. This allows the destination where the user pastes to choose the data that it understands. For example, you could serialize a complex object as JSON in a custom format for use within your own application, and also provide plain text and/or rich HTML fallbacks that can be used when a user pastes in an external application (e.g. an email message). + +This can be done by returning multiple keys for an item from the `getItems` function. Types can either be a standard [mime type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) for interoperability with external applications, or a custom string for use within your own app. + +In addition to providing items in multiple formats, you can also return multiple drag items from `getItems` to transfer multiple objects in a single copy and paste operation. + +This example copies two items, each of which contains representations as plain text, HTML, and a custom app-specific data format. Pasting on the target will use the custom data format to render formatted items. If you paste in an external application supporting rich text, the HTML representation will be used. Dropping in a text editor will use the plain text format. + +```tsx render +'use client'; +import React from 'react'; +import {useClipboard} from '@react-aria/dnd'; + +function Copyable() { + let {clipboardProps} = useClipboard({ + getItems() { + return [{ + 'text/plain': 'hello world', + 'text/html': 'hello world', + 'my-app-custom-type': JSON.stringify({ + message: 'hello world', + style: 'bold' + }) + }, { + 'text/plain': 'foo bar', + 'text/html': 'foo bar', + 'my-app-custom-type': JSON.stringify({ + message: 'foo bar', + style: 'italic' + }) + }]; + } + }); + + return ( +
    +
    +
    hello world
    +
    foo bar
    +
    + ⌘C +
    + ); +} + +///- begin collapse -/// +function Pasteable() { + let [pasted, setPasted] = React.useState(null); + let {clipboardProps} = useClipboard({ + async onPaste(items) { + let pasted = await Promise.all( + items + .filter(item => item.kind === 'text' && (item.types.has('text/plain') || item.types.has('my-app-custom-type'))) + .map(async (item: TextDropItem) => { + if (item.types.has('my-app-custom-type')) { + return JSON.parse(await item.getText('my-app-custom-type')); + } else { + return {message: await item.getText('text/plain')}; + } + }) + ); + setPasted(pasted); + } + }); + + let message = ['Paste here']; + if (pasted) { + message = pasted.map(d => { + let message = d.message; + if (d.style === 'bold') { + message = {message}; + } else if (d.style === 'italic') { + message = {message}; + } + return
    {message}
    ; + }); + } + + return ( +
    +
    {message || 'Paste here'}
    + ⌘V +
    + ); +} + +
    + + +
    +///- end collapse -/// +``` + +## Paste data + +`useClipboard` allows users to paste one or more items, each of which contains data to be pasted. There are three kinds of items: + +* `text` – represents data inline as a string in one or more formats +* `file` – references a file on the user's device +* `directory` – references the contents of a directory + +### Text + +A represents textual data in one or more different formats. These may be either standard [mime types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) or custom app-specific formats. Representing data in multiple formats allows drop targets both within and outside an application to choose data in a format that they understand. For example, a complex object may be serialized in a custom format for use within an application, with fallbacks in plain text and/or rich HTML that can be used when a user drops data from an external application. + +The example below works with the above `Copyable` example using a custom app-specific data format to transfer rich data. If no such data is available, it falls back to pasting plain text data. + +```tsx render +'use client'; +import React from 'react'; +import {useClipboard} from '@react-aria/dnd'; + +///- begin collapse -/// +function Copyable() { + let {clipboardProps} = useClipboard({ + getItems() { + return [{ + 'text/plain': 'hello world', + 'text/html': 'hello world', + 'my-app-custom-type': JSON.stringify({ + message: 'hello world', + style: 'bold' + }) + }, { + 'text/plain': 'foo bar', + 'text/html': 'foo bar', + 'my-app-custom-type': JSON.stringify({ + message: 'foo bar', + style: 'italic' + }) + }]; + } + }); + + return ( +
    +
    +
    hello world
    +
    foo bar
    +
    + ⌘C +
    + ); +} + +///- end collapse -/// +function Pasteable() { + let [pasted, setPasted] = React.useState(null); + let {clipboardProps} = useClipboard({ + async onPaste(items) { + let pasted = await Promise.all( + items + .filter(item => item.kind === 'text' && (item.types.has('text/plain') || item.types.has('my-app-custom-type'))) + .map(async (item: TextDropItem) => { + if (item.types.has('my-app-custom-type')) { + return JSON.parse(await item.getText('my-app-custom-type')); + } else { + return {message: await item.getText('text/plain')}; + } + }) + ); + setPasted(pasted); + } + }); + + let message = ['Paste here']; + if (pasted) { + message = pasted.map(d => { + let message = d.message; + if (d.style === 'bold') { + message = {message}; + } else if (d.style === 'italic') { + message = {message}; + } + return
    {message}
    ; + }); + } + + return ( +
    +
    {message || 'Paste here'}
    + ⌘V +
    + ); +} + +///- begin collapse -/// +
    + + +
    +///- end collapse -/// +``` +### Files +A references a file on the user's device. It includes the name and mime type of the file, and methods to read the contents as plain text, or retrieve a native [File](https://developer.mozilla.org/en-US/docs/Web/API/File) object which can be attached to form data for uploading. +This example accepts JPEG and PNG image files, and renders them by creating a local [object URL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL). +```tsx render +'use client'; +import React from 'react'; +import {useClipboard} from '@react-aria/dnd'; +import type {FileDropItem} from '@react-aria/dnd'; + +function Pasteable() { + let [file, setFile] = React.useState(null); + let {clipboardProps} = useClipboard({ + async onPaste(items) { + let item = items.find(item => item.kind === 'file' && (item.type === 'image/jpeg' || item.type === 'image/png')) as FileDropItem; + if (item) { + setFile(URL.createObjectURL(await item.getFile())); + } + } + }); + return ( +
    + {file ? : 'Paste image here'} +
    + ); +} +``` +### Directories +A references the contents of a directory on the user's device. It includes the name of the directory, as well as a method to iterate through the files and folders within the directory. The contents of any folders within the directory can be accessed recursively. +The `getEntries` method returns an [async iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) object, which can be used in a `for await...of` loop. This provides each item in the directory as either a or , and you can access the contents of each file as discussed above. +This example renders the file names within a dropped directory in a grid. +```tsx render files={["packages/dev/s2-docs/pages/react-aria/useClipboardGrid.css"]} +'use client'; +import React from 'react'; +import {useClipboard} from '@react-aria/dnd' +import type {DirectoryDropItem} from '@react-aria/dnd'; +import File from '@react-spectrum/s2/icons/File'; +import Folder from '@react-spectrum/s2/icons/Folder'; +import './useClipboardGrid.css'; + +function Pasteable() { + let [files, setFiles] = React.useState(null); + let {clipboardProps} = useClipboard({ + async onPaste(items) { + // Find the first dropped item that is a directory. + let dir = items.find(item => item.kind === 'directory') as DirectoryDropItem; + if (dir) { + // Read entries in directory and update state with relevant info. + let files = []; + for await (let entry of dir.getEntries()) { + files.push({ + name: entry.name, + kind: entry.kind + }); + } + setFiles(files); + } + } + }); + let contents = <>Paste directory here; + if (files) { + contents = ( +
      + {files.map(f => ( +
    • + {f.kind === 'directory' ? : } + {f.name} +
    • + ))} +
    + ); + } + return ( +
    + {contents} +
    + ); +} +``` +## Disabling copy and paste +If you need to temporarily disable copying and pasting, you can pass the `isDisabled` option to `useClipboard`. This will prevent copying and pasting on the element until it is re-enabled. +```tsx render +'use client'; +import React from 'react'; +import type {TextDropItem} from '@react-aria/dnd'; +import {useClipboard} from '@react-aria/dnd'; + +function Copyable() { + let {clipboardProps} = useClipboard({ + getItems() { + return [{ + 'text/plain': 'Hello world' + }]; + }, + /*- begin highlight -*/ + isDisabled: true + /*- end highlight -*/ + }); + return ( +
    + Hello world + ⌘C +
    + ); +} +function Pasteable() { + let [pasted, setPasted] = React.useState(null); + let {clipboardProps} = useClipboard({ + async onPaste(items) { + let pasted = await Promise.all( + items + .filter((item) => + item.kind === 'text' && item.types.has('text/plain') + ) + .map((item: TextDropItem) => item.getText('text/plain')) + ); + setPasted(pasted.join('\n')); + }, + /*- begin highlight -*/ + isDisabled: true + /*- end highlight -*/ + }); + return ( +
    + {pasted || 'Paste here'} + ⌘V +
    + ); +} + +
    + + +
    +``` \ No newline at end of file diff --git a/packages/dev/s2-docs/pages/react-aria/useClipboardExample.css b/packages/dev/s2-docs/pages/react-aria/useClipboardExample.css new file mode 100644 index 00000000000..e4412a91ecf --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/useClipboardExample.css @@ -0,0 +1,29 @@ +[role=textbox] { + display: inline-flex; + align-items: center; + justify-content: center; + background: var(--background-color); + border: 1px solid var(--border-color); + padding: 10px; + margin-right: 20px; + border-radius: 8px; + color: var(--color-white); +} + +[role=textbox]:focus { + outline: none; + border-color: var(--focus-ring-color); + box-shadow: 0 0 0 1px var(--focus-ring-color); +} + +[role=textbox] kbd { + display: inline-block; + margin-left: 10px; + padding: 0 4px; + background: var(--background-color); + border: 1px solid var(--border-color); + border-radius: 4px; + font-size: small; + letter-spacing: .2em; + color: var(--color-white); +} \ No newline at end of file diff --git a/packages/dev/s2-docs/pages/react-aria/useClipboardGrid.css b/packages/dev/s2-docs/pages/react-aria/useClipboardGrid.css new file mode 100644 index 00000000000..9d8a2e594ab --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/useClipboardGrid.css @@ -0,0 +1,33 @@ +.grid { + display: block; + width: auto; + height: auto; + min-height: 80px; +} + +.grid ul { + display: grid; + grid-template-columns: repeat(auto-fit, 100px); + list-style: none; + margin: 0; + padding: 0; + gap: 20px; +} + +.grid li { + display: flex; + align-items: center; + gap: 8px; +} + +.grid li svg { + flex: 0 0 auto; +} + +.grid li span { + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + diff --git a/packages/dev/s2-docs/pages/react-aria/useCollator.mdx b/packages/dev/s2-docs/pages/react-aria/useCollator.mdx new file mode 100644 index 00000000000..45d63d6cfd1 --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/useCollator.mdx @@ -0,0 +1,74 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import {FunctionAPI} from '../../src/FunctionAPI'; +import docs from 'docs:@react-aria/i18n'; + +export const section = 'Internationalization'; +export const description = 'Implementing collections in React Aria'; + + +# useCollator + +## Introduction + +`useCollator` wraps a builtin browser [Intl.Collator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator) +object to provide a React Hook that integrates with the i18n system in React Aria. It handles string comparison according to the current locale, +updating when the locale changes, and caching of collators for performance. See the +[Intl.Collator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator) docs for +information. + +## API + + + +## Example + +This example includes two textfields and compares the values of the two fields using a collator according to the current locale. + +```tsx render +'use client'; +import React from 'react'; +import {useCollator} from '@react-aria/i18n'; + +function Example() { + let [first, setFirst] = React.useState(''); + let [second, setSecond] = React.useState(''); + + let collator = useCollator(); + let result = collator.compare(first, second); + + return ( + <> +
    + + setFirst(e.target.value)} /> + + setSecond(e.target.value)} /> +
    +

    + {result === 0 + ? 'The strings are the same' + : result < 0 + ? 'First comes before second' + : 'Second comes before first' + } +

    + + ); +} +``` diff --git a/packages/dev/s2-docs/pages/react-aria/useDateFormatter.mdx b/packages/dev/s2-docs/pages/react-aria/useDateFormatter.mdx new file mode 100644 index 00000000000..2f77d300aed --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/useDateFormatter.mdx @@ -0,0 +1,57 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import {FunctionAPI} from '../../src/FunctionAPI'; +import docs from 'docs:@react-aria/i18n'; + +export const section = 'Internationalization'; +export const description = 'Implementing collections in React Aria'; + +# useDateFormatter + +## Introduction + +`useDateFormatter` wraps a builtin browser [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat) +object to provide a React Hook that integrates with the i18n system in React Aria. It handles formatting dates for the current locale, +updating when the locale changes, and caching of date formatters for performance. See the +[Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat) docs for +information on formatting options. + +## API + + + +## Example + +This example displays the current date for two locales: USA, and Russia. Two instances of the `CurrentDate` component are rendered, +using the [I18nProvider](I18nProvider.html) to specify the locale to display. + +```tsx render +'use client'; +import {I18nProvider, useDateFormatter} from '@react-aria/i18n'; + +function CurrentDate() { + let formatter = useDateFormatter(); + + return ( +

    {formatter.format(new Date())}

    + ); +} + +<> + + + + + + + +``` diff --git a/packages/dev/s2-docs/pages/react-aria/useDrag.mdx b/packages/dev/s2-docs/pages/react-aria/useDrag.mdx new file mode 100644 index 00000000000..7f028799fa3 --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/useDrag.mdx @@ -0,0 +1,307 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import {GroupedPropTable} from '../../src/PropTable'; +import {FunctionAPI} from '../../src/FunctionAPI'; +import docs from 'docs:@react-aria/dnd'; +import sharedDocs from 'docs:@react-types/shared/src/dnd.d.ts'; +export const section = 'Drag and Drop'; + +# useDrag + +{docs.exports.useDrag.description} + +## API + + + +## Introduction + +Drag and drop is a common UI interaction that allows users to transfer data between two locations by directly moving a visual representation on screen. It is a flexible, efficient, and intuitive way for users to perform a variety of tasks, and is widely supported across both desktop and mobile operating systems. + +React Aria supports traditional mouse and touch based drag and drop, but also implements keyboard and screen reader friendly interactions. Users can press Enter on a draggable element to enter drag and drop mode. Then, they can press Tab to navigate between drop targets, and Enter to drop or Escape to cancel. Touch screen reader users can also drag by double tapping to activate drag and drop mode, swiping between drop targets, and double tapping again to drop. + +See the [drag and drop introduction](dnd.html) to learn more. + +## Example + +This example shows how to make a simple draggable element that provides data as plain text. In order to support keyboard and screen reader drag interactions, the element must be focusable and have an ARIA role (in this case, `button`). While it is being dragged, it is displayed with a dimmed appearance by applying an additional CSS class. + +```tsx render files={["packages/dev/s2-docs/pages/react-aria/DropTarget.tsx", "packages/dev/s2-docs/pages/react-aria/useDragExample.css"]} +"use client"; +import {useDrag} from '@react-aria/dnd'; +import {DropTarget} from './DropTarget.tsx'; +import './useDragExample.css'; + +function Draggable() { + let {dragProps, isDragging} = useDrag({ + getItems() { + return [{ + 'text/plain': 'hello world' + }]; + } + }); + + return ( +
    + Drag me +
    + ); +} + +
    + + +
    +``` +## Drag data +Data for a draggable element can be provided in multiple formats at once. This allows drop targets to choose data in a format that they understand. For example, you could serialize a complex object as JSON in a custom format for use within your own application, and also provide plain text and/or rich HTML fallbacks that can be used when a user drops data in an external application (e.g. an email message). +This can be done by returning multiple keys for an item from the `getItems` function. Types can either be a standard [mime type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) for interoperability with external applications, or a custom string for use within your own app. +In addition to providing items in multiple formats, you can also return multiple drag items from `getItems` to transfer multiple objects in a single drag operation. +This example drags two items, each of which contains representations as plain text, HTML, and a custom app-specific data format. Dropping on the drop targets in this page will use the custom data format to render formatted items. If you drop in an external application supporting rich text, the HTML representation will be used. Dropping in a text editor will use the plain text format. + +```tsx render +"use client"; +import {useDrag} from '@react-aria/dnd'; +import {DropTarget} from './DropTarget.tsx'; + +function Draggable() { + let {dragProps, isDragging} = useDrag({ + getItems() { + return [{ + 'text/plain': 'hello world', + 'text/html': 'hello world', + 'my-app-custom-type': JSON.stringify({ + message: 'hello world', + style: 'bold' + }) + }, { + 'text/plain': 'foo bar', + 'text/html': 'foo bar', + 'my-app-custom-type': JSON.stringify({ + message: 'foo bar', + style: 'italic' + }) + }]; + } + }); + ///- begin collapse -/// + return ( +
    + Drag me +
    + ); + ///- end collapse -/// +} +///- begin collapse -/// +
    + + +
    +///- end collapse -/// +``` + +## Drag previews +By default, the drag preview shown under the user's pointer or finger is a copy of the original element that started the drag. A custom preview can be rendered using the `` component. This accepts a function as a child which receives the dragged data that was returned by `getItems`, and returns a rendered preview for those items. The `DragPreview` is linked with `useDrag` via a ref, passed to the `preview` property. The `DragPreview` should be placed in the component hierarchy appropriately, so that it receives any React context or inherited styles that it needs to render correctly. +This example renders a custom drag preview which shows the text of the first drag item. + +```tsx render +"use client"; +import React from 'react'; +import {useDrag} from '@react-aria/dnd'; +import {DropTarget} from './DropTarget.tsx'; +import {DragPreview} from '@react-aria/dnd'; + +function Draggable() { + let preview = React.useRef(null); + let {dragProps, isDragging} = useDrag({ + preview, + getItems() { + return [{ + 'text/plain': 'hello world' + }]; + } + }); + return ( + <> +
    + Drag me +
    + {/*- begin highlight -*/} + + {items =>
    {items[0]['text/plain']}
    } +
    + {/*- end highlight -*/} + + ); +} +
    + + +
    +``` + +## Drop operations +A is an indication of what will happen when dragged data is dropped on a particular drop target. These are: +* `move` – indicates that the dragged data will be moved from its source location to the target location. +* `copy` – indicates that the dragged data will be copied to the target destination. +* `link` – indicates that there will be a relationship established between the source and target locations. +* `cancel` – indicates that the drag and drop operation will be canceled, resulting in no changes made to the source or target. +Many operating systems display these in the form of a cursor change, e.g. a plus sign to indicate a copy operation. The user may also be able to use a modifier key to choose which drop operation to perform, such as Option or Alt to switch from move to copy. +The `onDragEnd` event allows the drag source to respond when a drag that it initiated ends, either because it was dropped or because it was canceled by the user. The `dropOperation` property of the event object indicates the operation that was performed. For example, when data is moved, the UI could be updated to reflect this change by removing the original dragged element. +This example removes the draggable element from the UI when a move operation is completed. Try holding the Option or Alt keys to change the operation to copy, and see how the behavior changes. +```tsx render +"use client" +import React from 'react'; +import {useDrag} from '@react-aria/dnd'; +import {DropTarget} from './DropTarget.tsx'; + +function Draggable() { + let [moved, setMoved] = React.useState(false); + let {dragProps, isDragging} = useDrag({ + getItems() { + return [{ + 'text/plain': 'hello world' + }]; + }, + /*- begin highlight -*/ + onDragEnd(e) { + if (e.dropOperation === 'move') { + setMoved(true); + } + } + /*- end highlight -*/ + }); + if (moved) { + return null; + } + // ... + ///- begin collapse -/// + return ( +
    + Drag me +
    + ); + ///- end collapse -/// +} +///- begin collapse -/// +
    + + +
    +///- end collapse -/// +``` + +The drag source can also control which drop operations are allowed for the data. For example, if moving data is not allowed, and only copying is supported, the `getAllowedDropOperations` function could be implemented to indicate this. When you drag the element below, the cursor now shows the copy affordance by default, and pressing a modifier to switch drop operations results in the drop being canceled. + +```tsx render +"use client"; +import {useDrag} from '@react-aria/dnd'; +import {DropTarget} from './DropTarget.tsx'; + +function Draggable() { + let {dragProps, isDragging} = useDrag({ + getItems() { + return [{ + 'text/plain': 'hello world' + }]; + }, + /*- begin highlight -*/ + getAllowedDropOperations() { + return ['copy']; + } + /*- end highlight -*/ + }); + // ... + ///- begin collapse -/// + return ( +
    + Drag me +
    + ); + ///- end collapse -/// +} +///- begin collapse -/// +
    + + +
    +///- end collapse -/// +``` + +## Drag button + +In cases where a draggable element has other interactions that conflict with accessible drag and drop (e.g. Enter key), or if the element is not focusable, an explicit drag affordance can be added. This acts as a button that keyboard and screen reader users can use to activate drag and drop. +When the `hasDragButton` option is enabled, the keyboard interactions are moved from the returned `dragProps` to the `dragButtonProps` so that they can be applied to a separate element, while the mouse and touch dragging interactions remain in `dragProps`. + +```tsx render +"use client"; +import React from 'react'; +import {useDrag} from '@react-aria/dnd'; +import {useButton} from '@react-aria/button'; +import {DropTarget} from './DropTarget.tsx'; + +function Draggable() { + let {dragProps, dragButtonProps, isDragging} = useDrag({ + /*- begin highlight -*/ + hasDragButton: true, + /*- end highlight -*/ + getItems() { + return [{ + 'text/plain': 'hello world' + }]; + } + }); + /*- begin highlight -*/ + let ref = React.useRef(null); + let {buttonProps} = useButton({...dragButtonProps, elementType: 'div'}, ref); + /*- end highlight -*/ + return ( +
    + {/*- begin highlight -*/} + + {/*- end highlight -*/} + Some text + +
    + ); +} + +
    + + +
    +``` +## Disabling dragging +If you need to temporarily disable dragging, you can pass the `isDisabled` option to `useDrag`. This will prevent dragging an element until it is re-enabled. + +```tsx render +"use client"; +import {useDrag} from '@react-aria/dnd'; +function Draggable() { + let {dragProps, isDragging} = useDrag({ + getItems() { + return [{ + 'text/plain': 'hello world' + }]; + }, + /*- begin highlight -*/ + isDisabled: true + /*- end highlight -*/ + }); + return ( +
    + Drag me +
    + ); +} + +``` \ No newline at end of file diff --git a/packages/dev/s2-docs/pages/react-aria/useDragExample.css b/packages/dev/s2-docs/pages/react-aria/useDragExample.css new file mode 100644 index 00000000000..9a546076f26 --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/useDragExample.css @@ -0,0 +1,25 @@ +.draggable { + display: inline-block; + vertical-align: top; + border: 1px solid gray; + padding: 10px; +} + +.draggable.dragging { + opacity: 0.5; +} + +.droppable { + width: 100px; + height: 80px; + border-radius: 6px; + display: inline-block; + padding: 20px; + margin-left: 20px; + border: 2px dotted gray; + white-space: pre-wrap; +} + +.droppable.target { + border: 2px solid var(--blue); +} \ No newline at end of file diff --git a/packages/dev/s2-docs/pages/react-aria/useDrop.mdx b/packages/dev/s2-docs/pages/react-aria/useDrop.mdx new file mode 100644 index 00000000000..1f7e1c93c53 --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/useDrop.mdx @@ -0,0 +1,360 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import {InterfaceType} from '../../src/types'; +import {FunctionAPI} from '../../src/FunctionAPI'; +import docs from 'docs:@react-aria/dnd'; +import sharedDocs from 'docs:@react-types/shared/src/dnd.d.ts'; +import typesDocs from 'docs:@react-types/shared/src/events.d.ts'; +export const section = 'Drag and Drop'; + +# useDrop + +{docs.exports.useDrop.description} + +## API + + + +## Introduction + +Drag and drop is a common UI interaction that allows users to transfer data between two locations by directly moving a visual representation on screen. It is a flexible, efficient, and intuitive way for users to perform a variety of tasks, and is widely supported across both desktop and mobile operating systems. + +React Aria supports traditional mouse and touch based drag and drop, but also implements keyboard and screen reader friendly interactions. Users can press Enter on a draggable element to enter drag and drop mode. Then, they can press Tab to navigate between drop targets, and Enter to drop or Escape to cancel. Touch screen reader users can also drag by double tapping to activate drag and drop mode, swiping between drop targets, and double tapping again to drop. + +See the [drag and drop introduction](dnd.html) to learn more. + +## Example + +This example shows how to make a simple drop target that accepts plain text data. In order to support keyboard and screen reader drag interactions, the element must be focusable and have an ARIA role (in this case, `button`). While a drag is hovered over it, a blue outline is rendered by applying an additional CSS class. + +```tsx render files={["packages/dev/s2-docs/pages/react-aria/Draggable.tsx", "packages/dev/s2-docs/pages/react-aria/useDragExample.css"]} +'use client'; +import React from 'react'; +import type {TextDropItem} from '@react-aria/dnd'; +import {useDrop} from '@react-aria/dnd'; +import {Draggable} from './Draggable.tsx'; +import './useDragExample.css'; + +function DropTarget() { + let [dropped, setDropped] = React.useState(null); + let ref = React.useRef(null); + let {dropProps, isDropTarget} = useDrop({ + ref, + async onDrop(e) { + let items = await Promise.all( + e.items + .filter(item => item.kind === 'text' && item.types.has('text/plain')) + .map((item: TextDropItem) => item.getText('text/plain')) + ); + setDropped(items.join('\n')); + } + }); + + return ( +
    + {dropped || 'Drop here'} +
    + ); +} + +
    + + +
    +``` +## Drop data +`useDrop` allows users to drop one or more **drag items**, each of which contains data to be transferred from the drag source to drop target. There are three kinds of drag items: +* `text` – represents data inline as a string in one or more formats +* `file` – references a file on the user's device +* `directory` – references the contents of a directory +### Text +A represents textual data in one or more different formats. These may be either standard [mime types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) or custom app-specific formats. Representing data in multiple formats allows drop targets both within and outside an application to choose data in a format that they understand. For example, a complex object may be serialized in a custom format for use within an application, with fallbacks in plain text and/or rich HTML that can be used when a user drops data from an external application. +The example below finds the first available item that includes a custom app-specific type. The same draggable component as used in the above example is used here, but rather than displaying the plain text representation, the custom format is used instead. + +```tsx render +'use client'; +import React from 'react'; +import {useDrop} from '@react-aria/dnd'; +import {Draggable} from './Draggable.tsx'; + +function DropTarget() { + let [dropped, setDropped] = React.useState(null); + let ref = React.useRef(null); + let {dropProps, isDropTarget} = useDrop({ + ref, + /*- begin highlight -*/ + async onDrop(e) { + let item = e.items.find(item => item.kind === 'text' && item.types.has('my-app-custom-type')) as TextDropItem; + if (item) { + setDropped(await item.getText('my-app-custom-type')); + } + } + /*- end highlight -*/ + }); + // ... + ///- begin collapse -/// + return ( +
    + {dropped || 'Drop here'} +
    + ); + ///- end collapse -/// +} +///- begin collapse -/// +
    + + +
    +///- end collapse -/// +``` +### Files +A references a file on the user's device. It includes the name and mime type of the file, and methods to read the contents as plain text, or retrieve a native [File](https://developer.mozilla.org/en-US/docs/Web/API/File) object which can be attached to form data for uploading. +This example accepts JPEG and PNG image files, and renders them by creating a local [object URL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL). + +```tsx render +'use client'; +import React from 'react'; +import type {FileDropItem} from '@react-aria/dnd'; +import {useDrop} from '@react-aria/dnd'; +function DropTarget() { + let [file, setFile] = React.useState(null); + let ref = React.useRef(null); + let {dropProps, isDropTarget} = useDrop({ + ref, + /*- begin highlight -*/ + async onDrop(e) { + let item = e.items.find(item => item.kind === 'file' && (item.type === 'image/jpeg' || item.type === 'image/png')) as FileDropItem; + if (item) { + setFile(URL.createObjectURL(await item.getFile())); + } + } + /*- end highlight -*/ + }); + return ( +
    + {file ? : 'Drop image here'} +
    + ); +} +``` +### Directories +A references the contents of a directory on the user's device. It includes the name of the directory, as well as a method to iterate through the files and folders within the directory. The contents of any folders within the directory can be accessed recursively. +The `getEntries` method returns an [async iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) object, which can be used in a `for await...of` loop. This provides each item in the directory as either a or , and you can access the contents of each file as discussed above. + +This example renders the file names within a dropped directory in a grid. + +```tsx render files={["packages/dev/s2-docs/pages/react-aria/useClipboardGrid.css"]} +'use client'; +import React from 'react'; +import type {DirectoryDropItem} from '@react-aria/dnd'; +import File from '@react-spectrum/s2/icons/File'; +import Folder from '@react-spectrum/s2/icons/Folder'; +import {useDrop} from '@react-aria/dnd'; +import './useClipboardGrid.css'; + +function DropTarget() { + let [files, setFiles] = React.useState(null); + let ref = React.useRef(null); + let {dropProps, isDropTarget} = useDrop({ + ref, + /*- begin highlight -*/ + async onDrop(e) { + // Find the first dropped item that is a directory. + let dir = e.items.find(item => item.kind === 'directory') as DirectoryDropItem; + if (dir) { + // Read entries in directory and update state with relevant info. + let files = []; + for await (let entry of dir.getEntries()) { + files.push({ + name: entry.name, + kind: entry.kind + }); + } + setFiles(files); + } + } + /*- end highlight -*/ + }); + let contents = <>Drop directory here; + if (files) { + contents = ( +
      + {files.map(f => ( +
    • + {f.kind === 'directory' ? : } + {f.name} +
    • + ))} +
    + ); + } + return ( +
    + {contents} +
    + ); +} +``` + +## Drop operations + +A is an indication of what will happen when dragged data is dropped on a particular drop target. These are: +* `move` – indicates that the dragged data will be moved from its source location to the target location. +* `copy` – indicates that the dragged data will be copied to the target destination. +* `link` – indicates that there will be a relationship established between the source and target locations. +* `cancel` – indicates that the drag and drop operation will be canceled, resulting in no changes made to the source or target. +Many operating systems display these in the form of a cursor change, e.g. a plus sign to indicate a copy operation. The user may also be able to use a modifier key to choose which drop operation to perform, such as Option or Alt to switch from move to copy. +The drag source can specify which drop operations are allowed for the dragged data (see the [useDrag docs](useDrag.html) for how to customize this). By default, the first allowed operation is allowed by drop targets, meaning that the drop target accepts data of any type and operation. + +### getDropOperation + +The `getDropOperation` function passed to `useDrop` can be used to provide appropriate feedback to the user when a drag hovers over the drop target. If a drop target only supports data of specific types (e.g. images, videos, text, etc.), then it should implement `getDropOperation` and return `'cancel'` for types that aren't supported. This will prevent visual feedback indicating that the drop target accepts the dragged data when this is not true. +When the data is supported, either return one of the drop operations in `allowedOperation` or a specific drop operation if only that drop operation is supported. If the returned operation is not in `allowedOperations`, then the drop target will act as if `'cancel'` was returned. + +In the below example, the drop target only supports dropping PNG images. If a PNG is dragged over the target, it will be highlighted and the operating system displays a copy cursor. If another type is dragged over the target, then there is no visual feedback, indicating that a drop is not accepted there. If the user holds a modifier key such as Control while dragging over the drop target in order to change the drop operation, then the drop target does not accept the drop. + +```tsx render +'use client'; +import React from 'react'; +import {useDrop} from '@react-aria/dnd'; +function DropTarget() { + let [file, setFile] = React.useState(null); + let ref = React.useRef(null); + let {dropProps, isDropTarget} = useDrop({ + ref, + /*- begin highlight -*/ + getDropOperation(types, allowedOperations) { + return types.has('image/png') ? 'copy' : 'cancel'; + }, + /*- end highlight -*/ + async onDrop(e) { + let item = e.items.find(item => item.kind === 'file' && item.type === 'image/png') as FileDropItem; + if (item) { + setFile(URL.createObjectURL(await item.getFile())); + } + } + }); + // ... +///- begin collapse -/// + return ( +
    + {file ? : 'Drop image here'} +
    + ); +///- end collapse -/// +} +``` +### onDrop +The `onDrop` event also includes the `dropOperation`. This can be used to perform different actions accordingly, for example, when communicating with a backend API. +```tsx +function DropTarget(props) { + let ref = React.useRef(null); + let {dropProps, isDropTarget} = useDrop({ + ref, + async onDrop(e) { + let item = e.items.find(item => item.kind === 'text' && item.types.has('my-app-file')) as TextDropItem; + if (!item) { + return; + } + let data = JSON.parse(await item.getText('my-app-file')); + /*- begin highlight -*/ + switch (e.dropOperation) { + case 'move': + MyAppFileService.move(data.filePath, props.filePath); + break; + case 'copy': + MyAppFileService.copy(data.filePath, props.filePath); + break; + case 'link': + MyAppFileService.link(data.filePath, props.filePath); + break; + } + /*- end highlight -*/ + } + }); + // ... +} +``` +## Events + +Drop targets receive a number of events during a drag session. These are: + + +This example logs all events that occur within the drop target: + +```tsx render +'use client'; +import React from 'react'; +import {useDrop} from '@react-aria/dnd'; +import {Draggable} from './Draggable.tsx'; + +function DropTarget() { + let [events, setEvents] = React.useState([]); + let onEvent = e => setEvents(events => [JSON.stringify(e), ...events]); + let ref = React.useRef(null); + let {dropProps, isDropTarget} = useDrop({ + ref, + onDropEnter: onEvent, + onDropMove: onEvent, + onDropExit: onEvent, + onDrop: onEvent + }); + return ( +
      + {events.map((e, i) =>
    • {e}
    • )} +
    + ); +} +
    + + +
    +``` +## Disabling dropping + +If you need to temporarily disable dropping, you can pass the `isDisabled` option to `useDrop`. This will prevent the drop target from accepting any drops until it is re-enabled. + +```tsx render +'use client'; +import React from 'react'; +import type {TextDropItem} from '@react-aria/dnd'; +import {useDrop} from '@react-aria/dnd'; +import {Draggable} from './Draggable.tsx'; + +function DropTarget() { + let [dropped, setDropped] = React.useState(null); + let ref = React.useRef(null); + let {dropProps, isDropTarget} = useDrop({ + ref, + async onDrop(e) { + let items = await Promise.all( + e.items + .filter(item => item.kind === 'text' && item.types.has('text/plain')) + .map((item: TextDropItem) => item.getText('text/plain')) + ); + setDropped(items.join('\n')); + }, + /*- begin highlight -*/ + isDisabled: true + /*- end highlight -*/ + }); + return ( +
    + {dropped || 'Drop here'} +
    + ); +} +
    + + +
    +``` \ No newline at end of file diff --git a/packages/dev/s2-docs/pages/react-aria/useField.mdx b/packages/dev/s2-docs/pages/react-aria/useField.mdx new file mode 100644 index 00000000000..41125ea5648 --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/useField.mdx @@ -0,0 +1,68 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import docs from 'docs:@react-aria/label'; +import {FunctionAPI} from '../../src/FunctionAPI'; + +export const section = 'Utilities'; + +# useField + +{docs.exports.useField.description} + +## API + + + +## Example + +The `useField` hook associates a form control with a label, and an optional description and/or error message. This is useful for providing context about how users should fill in a field, or a validation message. `useField` takes care of creating ids for each element and associating them with the correct ARIA attributes (`aria-labelledby` and `aria-describedby`). + +By default, `useField` assumes that the label is a native HTML `
    + +

    Navigation

    +
    + + + +

    Region

    +

    Example region with no focusable children.

    +
    +
    +``` diff --git a/packages/dev/s2-docs/pages/react-aria/useLocale.mdx b/packages/dev/s2-docs/pages/react-aria/useLocale.mdx new file mode 100644 index 00000000000..978bc50bd40 --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/useLocale.mdx @@ -0,0 +1,53 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import {FunctionAPI} from '../../src/FunctionAPI'; +import docs from 'docs:@react-aria/i18n'; + +export const section = 'Internationalization'; +export const description = 'Implementing collections in React Aria'; + +# useLocale + +## Introduction + +`useLocale` allows components to access the current locale and interface layout direction. +By default, this is automatically detected based on the browser or system language, but it can +be overridden by using the [I18nProvider](I18nProvider.html) at the root of your app. + +`useLocale` should be used in the root of your app to define the +[lang](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) +and [dir](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir) attributes +so that the browser knows which language and direction the user interface should be rendered in. + +## API + + + +## Interface + + + +## Example + +```tsx +import {useLocale} from '@react-aria/i18n'; + +function YourApp() { + let {locale, direction} = useLocale(); + + return ( +
    + {/* your app here */} +
    + ); +} +``` diff --git a/packages/dev/s2-docs/pages/react-aria/useLongPress.mdx b/packages/dev/s2-docs/pages/react-aria/useLongPress.mdx new file mode 100644 index 00000000000..9d94a0bce6f --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/useLongPress.mdx @@ -0,0 +1,114 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import {FunctionAPI} from '../../src/FunctionAPI'; +import docs from 'docs:@react-aria/interactions'; +import typesDocs from 'docs:@react-types/shared/src/events.d.ts'; +import {InterfaceType} from '../../src/types'; +export const section = 'Interactions'; + +# useLongPress + +{docs.exports.useLongPress.description} + +## API + + + +## Features + +`useLongPress` handles long press interactions across both mouse and touch devices. A long press is triggered when a user presses +and holds their pointer over a target for a minimum period of time. If the user moves their pointer off of the target before the +time threshold, the interaction is canceled. Once a long press event is triggered, other pointer interactions that may be active +such as `usePress` and `useMove` will be canceled so that only the long press is activated. + +* Handles mouse and touch events +* Uses pointer events where available, with fallbacks to mouse and touch events +* Ignores emulated mouse events in mobile browsers +* Prevents text selection on touch devices while long pressing +* Prevents browser and OS context menus from appearing while long pressing +* Customizable time threshold for long press +* Supports an accessibility description to indicate to assistive technology users that a long press action is available + +## Usage + +`useLongPress` returns props that you should spread onto the target element: + + + +`useLongPress` supports the following event handlers and options: + + + +Each of these handlers is fired with a `LongPressEvent`, which exposes information about the target and the +type of event that triggered the interaction. + + + +## Example + +This example shows a button that has both a normal press action using [usePress](usePress.html), as well as a long +press action using `useLongPress`. Pressing the button will set the mode to "Normal speed", and long pressing it will +set the mode to "Hyper speed". All of the emitted events are also logged below. Note that when long pressing the button, +only a long press is emitted, and no normal press is emitted on pointer up. + +**Note**: this example does not have a keyboard accessible way to trigger the long press action. Because the method of triggering +this action will differ depending on the component, it is outside the scope of `useLongPress`. Make sure to implement a keyboard +friendly alternative to all long press interactions if you are using this hook directly. + +```tsx render +"use client"; +import React from 'react'; +import {mergeProps} from '@react-aria/utils'; +import {useLongPress, usePress} from '@react-aria/interactions'; + +function Example() { + let [events, setEvents] = React.useState([]); + let [mode, setMode] = React.useState('Activate'); + let {longPressProps} = useLongPress({ + accessibilityDescription: 'Long press to activate hyper speed', + onLongPressStart: e => setEvents( + events => [`long press start with ${e.pointerType}`, ...events] + ), + onLongPressEnd: e => setEvents( + events => [`long press end with ${e.pointerType}`, ...events] + ), + onLongPress: e => { + setMode('Hyper speed'); + setEvents( + events => [`long press with ${e.pointerType}`, ...events] + ); + } + }); + + let {pressProps} = usePress({ + onPress: e => { + setMode('Normal speed'); + setEvents( + events => [`press with ${e.pointerType}`, ...events] + ); + } + }); + + return ( + <> + +
      + {events.map((e, i) =>
    • {e}
    • )} +
    + + ); +} +``` diff --git a/packages/dev/s2-docs/pages/react-aria/useMove.mdx b/packages/dev/s2-docs/pages/react-aria/useMove.mdx new file mode 100644 index 00000000000..91ef600ba40 --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/useMove.mdx @@ -0,0 +1,152 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import {FunctionAPI} from '../../src/FunctionAPI'; +import docs from 'docs:@react-aria/interactions'; +import typesDocs from 'docs:@react-types/shared/src/events.d.ts'; +import {InterfaceType} from '../../src/types'; +export const section = 'Interactions'; + +# useMove + +{docs.exports.useMove.description} + +## API + + + +## Features + +`useMove` handles move interactions across mouse, touch, and keyboard. A move interaction starts when a +user moves after pressing down with a mouse or their finger on the target, and ends when they lift their pointer. Move +events are fired as the pointer moves around, and specify the distance that the pointer traveled since the last +event. In addition, after a user focuses the target element, move events are fired when the user presses the arrow keys. + +* Handles mouse and touch events +* Handles arrow key presses +* Uses pointer events where available, with fallbacks to mouse and touch events +* Ignores emulated mouse events in mobile browsers +* Handles disabling text selection on mobile while the press interaction is active +* Normalizes many cross browser inconsistencies + +## Usage + +`useMove` returns props that you should spread onto the target element: + + + +`useMove` supports the following event handlers: + + + +Each of these handlers is fired with a `MoveEvent`, which exposes information about the target and the +type of event that triggered the interaction. + + + +## Example + +This example shows a ball that can be moved by dragging with a mouse or touch, or by tabbing to it and using +the arrow keys on your keyboard. The movement is clamped so that the ball cannot be dragged outside a box. +In addition, all of the move events are logged below so that you can inspect what is going on. + +```tsx render +"use client"; +import React from 'react'; +import {useMove} from '@react-aria/interactions'; + +function Example() { + const CONTAINER_SIZE = 200; + const BALL_SIZE = 30; + + let [events, setEvents] = React.useState([]); + let [color, setColor] = React.useState('black'); + let [position, setPosition] = React.useState({ + x: 0, + y: 0 + }); + + let clamp = pos => Math.min(Math.max(pos, 0), CONTAINER_SIZE - BALL_SIZE); + {/*- begin highlight -*/} + let {moveProps} = useMove({ + onMoveStart(e) { + setColor('red'); + setEvents(events => [`move start with pointerType = ${e.pointerType}`, ...events]); + }, + onMove(e) { + setPosition(({x, y}) => { + // Normally, we want to allow the user to continue + // dragging outside the box such that they need to + // drag back over the ball again before it moves. + // This is handled below by clamping during render. + // If using the keyboard, however, we need to clamp + // here so that dragging outside the container and + // then using the arrow keys works as expected. + if (e.pointerType === 'keyboard') { + x = clamp(x); + y = clamp(y); + } + + x += e.deltaX; + y += e.deltaY; + return {x, y}; + }); + + setEvents(events => [`move with pointerType = ${e.pointerType}, deltaX = ${e.deltaX}, deltaY = ${e.deltaY}`, ...events]); + }, + onMoveEnd(e) { + setPosition(({x, y}) => { + // Clamp position on mouse up + x = clamp(x); + y = clamp(y); + return {x, y}; + }); + setColor('black'); + setEvents(events => [`move end with pointerType = ${e.pointerType}`, ...events]); + } + }); + {/*- end highlight -*/} + + return ( + <> +
    +
    +
    +
      + {events.map((e, i) =>
    • {e}
    • )} +
    + + ); +} +``` diff --git a/packages/dev/s2-docs/pages/react-aria/useNumberFormatter.mdx b/packages/dev/s2-docs/pages/react-aria/useNumberFormatter.mdx new file mode 100644 index 00000000000..35d39fbb6e5 --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/useNumberFormatter.mdx @@ -0,0 +1,61 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import {FunctionAPI} from '../../src/FunctionAPI'; +import docs from 'docs:@react-aria/i18n'; + +export const section = 'Internationalization'; +export const description = 'Implementing collections in React Aria'; + +# useNumberFormatter + +## Introduction + +`useNumberFormatter` wraps a builtin browser [Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) +object to provide a React Hook that integrates with the i18n system in React Aria. It handles formatting numbers for the current locale, +updating when the locale changes, and caching of number formatters for performance. See the +[Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) docs for +information on formatting options. + +## API + + + +## Example + +This example displays a currency value for two locales: USA, and Germany. Two instances of the `Currency` component are rendered, +using the [I18nProvider](I18nProvider.html) to specify the locale to display. + +```tsx render +'use client'; +import {I18nProvider, useNumberFormatter} from '@react-aria/i18n'; + +function Currency({value, currency}) { + let formatter = useNumberFormatter({ + style: 'currency', + currency, + minimumFractionDigits: 0 + }); + + return ( +

    {formatter.format(value)}

    + ); +} + +<> + + + + + + + +``` diff --git a/packages/dev/s2-docs/pages/react-aria/useObjectRef.mdx b/packages/dev/s2-docs/pages/react-aria/useObjectRef.mdx new file mode 100644 index 00000000000..fc2d0392b4c --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/useObjectRef.mdx @@ -0,0 +1,57 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import docs from 'docs:@react-aria/utils'; +import {FunctionAPI} from '../../src/FunctionAPI'; + +export const section = 'Utilities'; + +# useObjectRef + +{docs.exports.useObjectRef.description} + +## API + + + +## Introduction + +`useObjectRef` is a utility function that will give an object ref back regardless of if a +callback ref or object ref was passed in. This is useful for passing refs to React Aria hooks. + +## Example + +```tsx render +'use client'; +import React from 'react'; +import {useObjectRef} from '@react-aria/utils'; +import {useButton} from '@react-aria/button'; +import {AriaButtonProps} from '@react-types/button'; + +let Button = React.forwardRef((props: AriaButtonProps, ref: React.ForwardedRef) => { + let objRef = useObjectRef(ref); + let {buttonProps} = useButton(props, objRef); + let {children} = props; + + return ( + + ); +}); + +function MyButton(props) { + let ref = React.useRef(null); + return ; +} + +Test +``` diff --git a/packages/dev/s2-docs/pages/react-aria/usePress.mdx b/packages/dev/s2-docs/pages/react-aria/usePress.mdx new file mode 100644 index 00000000000..a4d768b2b31 --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/usePress.mdx @@ -0,0 +1,114 @@ +{/* Copyright 2025 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {Layout} from '../../src/Layout'; +export default Layout; +import {FunctionAPI} from '../../src/FunctionAPI'; +import docs from 'docs:@react-aria/interactions'; +import typesDocs from 'docs:@react-types/shared/src/events.d.ts'; +import {InterfaceType} from '../../src/types'; +export const section = 'Interactions'; + +# usePress + +{docs.exports.usePress.description} + +## API + + + +## Features + +`usePress` handles press interactions across mouse, touch, keyboard, and screen readers. A press interaction starts when a +user presses down with a mouse or their finger on the target, and ends when they move the pointer off the target. It may +start again if the pointer re-enters the target. `usePress` returns the current press state, which can be used to adjust +the visual appearance of the target. If the pointer is released over the target, then an `onPress` event is fired. + +* Handles mouse and touch events +* Handles Enter or Space key presses +* Handles screen reader virtual clicks +* Uses pointer events where available, with fallbacks to mouse and touch events +* Normalizes focus behavior on mouse and touch interactions across browsers +* Handles disabling text selection on mobile while the press interaction is active +* Handles canceling press interactions on scroll +* Normalizes many cross browser inconsistencies + +{/* TODO: uncomment once blog posts are transferred over */} +{/* Read our [blog post](/blog/building-a-button-part-1.html) about the complexities of press event handling to learn more. */} + +## Usage + +`usePress` returns props that you should spread onto the target element, along with the current press state: + + + +`usePress` supports the following event handlers: + + + +Each of these handlers is fired with a `PressEvent`, which exposes information about the target and the +type of event that triggered the interaction. + + + +## Example + +This example shows a simple target that handles press events with `usePress` and logs them to a list below. +It also uses the `isPressed` state to adjust the background color when the target is pressed. +Press down on the target and drag your pointer off and over to see when the events are fired, and try focusing +the target with a keyboard and pressing the Enter or Space keys to trigger events as well. + +{/* Not implemented yet */} +{/* **NOTE: for more advanced button functionality, see [useButton](useButton.html).** */} + +```tsx render +"use client"; +import React from 'react'; +import {usePress} from '@react-aria/interactions'; + +function Example() { + let [events, setEvents] = React.useState([]); + let {pressProps, isPressed} = usePress({ + onPressStart: e => setEvents( + events => [...events, `press start with ${e.pointerType}`] + ), + onPressEnd: e => setEvents( + events => [...events, `press end with ${e.pointerType}`] + ), + onPress: e => setEvents( + events => [...events, `press with ${e.pointerType}`] + ) + }); + + return ( + <> +
    + Press me! +
    +
      + {events.map((e, i) =>
    • {e}
    • )} +
    + + ); +} +``` diff --git a/packages/dev/s2-docs/src/FunctionAPI.tsx b/packages/dev/s2-docs/src/FunctionAPI.tsx new file mode 100644 index 00000000000..e6a7e272364 --- /dev/null +++ b/packages/dev/s2-docs/src/FunctionAPI.tsx @@ -0,0 +1,33 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {styles as codeStyles} from './Code'; +import {Indent, JoinList, setLinks, Type, TypeParameters} from './types'; +import React from 'react'; + +export function FunctionAPI({function: func, links}) { + let {name, parameters, return: returnType, typeParameters} = func; + if (links) { + setLinks(links); + } + return ( + + {name} + + + + + {': '} + + + ); +} From 31b8bb5049e5b71ab8d3dceb3519a8299df4b167 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Mon, 3 Nov 2025 14:15:00 -0600 Subject: [PATCH 4/4] docs: add icons search to search menu (#9114) * docs: add icons search to search menu * add divider and separate tag group for Icons tag * don't show All tag if search starts from Icons tag * use medium divider * use custom divider * fix double scrollbars * fix cursor --- packages/@react-spectrum/s2/src/Divider.tsx | 2 +- packages/dev/s2-docs/pages/s2/Icons.mdx | 2 +- .../src/{IconCards.tsx => IconSearchView.tsx} | 142 ++++++++-- packages/dev/s2-docs/src/SearchMenu.tsx | 243 ++++++++++++------ 4 files changed, 285 insertions(+), 104 deletions(-) rename packages/dev/s2-docs/src/{IconCards.tsx => IconSearchView.tsx} (52%) diff --git a/packages/@react-spectrum/s2/src/Divider.tsx b/packages/@react-spectrum/s2/src/Divider.tsx index d41e3a0580e..ce33d027e63 100644 --- a/packages/@react-spectrum/s2/src/Divider.tsx +++ b/packages/@react-spectrum/s2/src/Divider.tsx @@ -29,7 +29,7 @@ interface DividerSpectrumProps { */ size?: 'S' | 'M' | 'L', /** - * How thick the Divider should be. + * The orientation of the Divider. * @default 'horizontal' */ orientation?: 'horizontal' | 'vertical', diff --git a/packages/dev/s2-docs/pages/s2/Icons.mdx b/packages/dev/s2-docs/pages/s2/Icons.mdx index 468eff7f74c..3c1d22a6f70 100644 --- a/packages/dev/s2-docs/pages/s2/Icons.mdx +++ b/packages/dev/s2-docs/pages/s2/Icons.mdx @@ -1,7 +1,7 @@ import {Layout} from '../../src/Layout'; import {InstallCommand} from '../../src/InstallCommand'; import {Command} from '../../src/Command'; -import {IconCards} from '../../src/IconCards'; +import {IconCards} from '../../src/IconSearchView'; import {IconColors} from '../../src/IconColors'; import {IconSizes} from '../../src/IconSizes'; import {InlineAlert, Heading, Content} from '@react-spectrum/s2'; diff --git a/packages/dev/s2-docs/src/IconCards.tsx b/packages/dev/s2-docs/src/IconSearchView.tsx similarity index 52% rename from packages/dev/s2-docs/src/IconCards.tsx rename to packages/dev/s2-docs/src/IconSearchView.tsx index 69b94bc79a9..fae26461399 100644 --- a/packages/dev/s2-docs/src/IconCards.tsx +++ b/packages/dev/s2-docs/src/IconSearchView.tsx @@ -2,16 +2,17 @@ 'use client'; import {Autocomplete, GridLayout, ListBox, ListBoxItem, Size, useFilter, Virtualizer} from 'react-aria-components'; -import {Content, Heading, IllustratedMessage, pressScale, SearchField} from '@react-spectrum/s2'; +import Close from '@react-spectrum/s2/icons/Close'; +import {Content, Heading, IllustratedMessage, pressScale, SearchField, Skeleton, Text} from '@react-spectrum/s2'; import {focusRing, iconStyle, style} from '@react-spectrum/s2/style' with {type: 'macro'}; import {iconAliases} from './iconAliases.js'; // @ts-ignore import icons from '/packages/@react-spectrum/s2/s2wf-icons/*.svg'; // eslint-disable-next-line monorepo/no-internal-import import NoSearchResults from '@react-spectrum/s2/illustrations/linear/NoSearchResults'; -import React, {useCallback, useRef} from 'react'; +import React, {useCallback, useMemo, useRef} from 'react'; -const iconList = Object.keys(icons).map(name => ({id: name.replace(/^S2_Icon_(.*?)(Size\d+)?_2.*/, '$1'), icon: icons[name].default})); +export const iconList = Object.keys(icons).map(name => ({id: name.replace(/^S2_Icon_(.*?)(Size\d+)?_2.*/, '$1'), icon: icons[name].default})); const itemStyle = style({ ...focusRing(), @@ -37,7 +38,8 @@ const itemStyle = style({ gap: 4, alignItems: 'center', justifyContent: 'center', - paddingX: 4 + paddingX: 4, + cursor: 'default' }); let handleCopyImport = (id: string) => { @@ -48,6 +50,119 @@ let handleCopyImport = (id: string) => { }); }; +interface IconSearchViewProps { + filteredItems: typeof iconList +} + +export function IconSearchView({filteredItems}: IconSearchViewProps) { + return ( + + handleCopyImport(item.toString())} + items={filteredItems} + layout="grid" + className={style({width: '100%', scrollPaddingY: 4})} + renderEmptyState={() => ( + + + No results + Try a different search term. + + )}> + {item => } + + + ); +} + +function IconItem({item}) { + let Icon = item.icon; + let ref = useRef(null); + return ( + + +
    + {item.id} +
    +
    + ); +} + +export function SkeletonIconItem({item}: {item: {id: string}}) { + const PlaceholderIcon = Close; + const ref = useRef(null); + + const itemStyle = style({ + ...focusRing(), + size: 'full', + backgroundColor: { + default: 'gray-50', + isHovered: 'gray-100', + isFocused: 'gray-100', + isSelected: 'neutral' + }, + '--iconPrimary': { + type: 'color', + value: { + default: 'neutral', + isSelected: 'gray-25' + } + }, + font: 'ui-sm', + borderRadius: 'default', + transition: 'default', + display: 'flex', + flexDirection: 'column', + gap: 4, + alignItems: 'center', + justifyContent: 'center', + paddingX: 4 + }); + + return ( + + +
    + Name +
    +
    + ); +} + +export function IconSearchSkeleton() { + const mockItems = useMemo(() => Array.from({length: 140}, (_, i) => ({id: `skeleton-${i}`})), []); + + return ( + + + + {(item) => } + + + + ); +} + export function IconCards() { let {contains} = useFilter({sensitivity: 'base'}); let filter = useCallback((textValue, inputValue) => { @@ -84,22 +199,3 @@ export function IconCards() { ); } - -function IconItem({item}) { - let Icon = item.icon; - let ref = useRef(null); - return ( - - -
    - {item.id} -
    -
    - ); -} diff --git a/packages/dev/s2-docs/src/SearchMenu.tsx b/packages/dev/s2-docs/src/SearchMenu.tsx index 55b60679962..6e735004f03 100644 --- a/packages/dev/s2-docs/src/SearchMenu.tsx +++ b/packages/dev/s2-docs/src/SearchMenu.tsx @@ -1,21 +1,41 @@ 'use client'; import {ActionButton, Content, Heading, IllustratedMessage, SearchField, Tag, TagGroup} from '@react-spectrum/s2'; -import {Autocomplete, Dialog, Key, OverlayTriggerStateContext, Provider} from 'react-aria-components'; +import {Autocomplete, Dialog, Key, OverlayTriggerStateContext, Provider, Separator as RACSeparator, useFilter} from 'react-aria-components'; import Close from '@react-spectrum/s2/icons/Close'; import {ComponentCardView} from './ComponentCardView'; import {getLibraryFromPage, getLibraryFromUrl} from './library'; +import {iconAliases} from './iconAliases.js'; +import {iconList, IconSearchSkeleton} from './IconSearchView'; import {type Library, TAB_DEFS} from './constants'; // eslint-disable-next-line monorepo/no-internal-import import NoSearchResults from '@react-spectrum/s2/illustrations/linear/NoSearchResults'; // @ts-ignore import {Page} from '@parcel/rsc'; -import React, {CSSProperties, useEffect, useMemo, useRef, useState} from 'react'; +import React, {CSSProperties, lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {SelectableCollectionContext} from '../../../react-aria-components/src/RSPContexts'; import {style} from '@react-spectrum/s2/style' with { type: 'macro' }; import {Tab, TabList, TabPanel, Tabs} from './Tabs'; import {TextFieldRef} from '@react-types/textfield'; +export const divider = style({ + marginY: 8, + marginStart: -8, + marginEnd: 0, + alignSelf: 'stretch', + backgroundColor: { + default: 'gray-400', + forcedColors: 'ButtonBorder' + }, + borderStyle: 'none', + borderRadius: 'full', + flexGrow: 0, + flexShrink: 0, + width: '[3px]' +}); + +const IconSearchView = lazy(() => import('./IconSearchView').then(({IconSearchView}) => ({default: IconSearchView}))); + interface SearchMenuProps { pages: Page[], currentPage: Page, @@ -110,24 +130,54 @@ export function SearchMenu(props: SearchMenuProps) { }); }, [transformedComponents]); + const sectionTags = useMemo(() => sections, [sections]); + const iconTag = useMemo(() => { + if (selectedLibrary === 'react-spectrum') { + return [{id: 'icons', name: 'Icons'}]; + } + return []; + }, [selectedLibrary]); + const [selectedSectionId, setSelectedSectionId] = useState(() => currentPage.exports?.section?.toLowerCase() || 'components'); const prevSearchWasEmptyRef = useRef(true); + // Icon filter function + const {contains} = useFilter({sensitivity: 'base'}); + const iconFilter = useCallback((textValue, inputValue) => { + // check if we're typing part of a category alias + for (const alias of Object.keys(iconAliases)) { + if (contains(alias, inputValue) && iconAliases[alias].includes(textValue)) { + return true; + } + } + // also compare for substrings in the icon's actual name + return textValue != null && contains(textValue, inputValue); + }, [contains]); + + const filteredIcons = useMemo(() => { + if (!searchValue.trim()) { + return iconList; + } + return iconList.filter(item => iconFilter(item.id, searchValue)); + }, [searchValue, iconFilter]); + // Ensure selected section is valid for the current library - const baseIds = sections.map(s => s.id); - const sectionIds = searchValue.trim().length > 0 ? ['all', ...baseIds] : baseIds; + const baseSectionIds = sectionTags.map(s => s.id); + const baseIconIds = iconTag.map(t => t.id); + const allBaseIds = [...baseSectionIds, ...baseIconIds]; + const sectionIds = searchValue.trim().length > 0 && selectedSectionId !== 'icons' ? ['all', ...allBaseIds] : allBaseIds; if (!selectedSectionId || !sectionIds.includes(selectedSectionId)) { setSelectedSectionId(sectionIds[0] || 'components'); } - // When search starts, auto-select the All tag. + // When search starts, auto-select the All tag (unless Icons is selected). useEffect(() => { const isEmpty = searchValue.trim().length === 0; - if (prevSearchWasEmptyRef.current && !isEmpty) { + if (prevSearchWasEmptyRef.current && !isEmpty && selectedSectionId !== 'icons') { setSelectedSectionId('all'); } prevSearchWasEmptyRef.current = isEmpty; - }, [searchValue]); + }, [searchValue, selectedSectionId]); let filteredComponents = useMemo(() => { if (!searchValue) { @@ -179,12 +229,12 @@ export function SearchMenu(props: SearchMenuProps) { }, [sections, searchValue]); const tags = useMemo(() => { - if (searchValue.trim().length > 0) { - // When searching, prepend an All tag - return [{id: 'all', name: 'All'}, ...sections]; + if (searchValue.trim().length > 0 && selectedSectionId !== 'icons') { + // When searching, prepend an All tag (unless Icons is selected) + return [{id: 'all', name: 'All'}, ...sectionTags]; } - return sections; - }, [searchValue, sections]); + return sectionTags; + }, [searchValue, sectionTags, selectedSectionId]); const handleTabSelectionChange = React.useCallback((key: Key) => { if (searchValue) { @@ -208,6 +258,13 @@ export function SearchMenu(props: SearchMenuProps) { } }, []); + const handleIconSelectionChange = React.useCallback((keys: Iterable) => { + const firstKey = Array.from(keys)[0] as string; + if (firstKey) { + setSelectedSectionId(firstKey); + } + }, []); + const selectedItems = useMemo(() => { let items: typeof transformedComponents = []; if (searchValue.trim().length > 0 && selectedSectionId === 'all') { @@ -269,74 +326,102 @@ export function SearchMenu(props: SearchMenuProps) { ))} - {orderedTabs.map((tab, i) => ( - - -
    - -
    + {orderedTabs.map((tab, i) => { + const tabIconTag = tab.id === 'react-spectrum' ? [{id: 'icons', name: 'Icons'}] : []; + return ( + + +
    + +
    - - -
    - {sections.length > 0 && ( -
    - - - {(tag) => ( - - {tag.name} - - )} - - -
    - )} - { - setSearchValue(''); - onClose(); - }} - items={selectedItems.map(item => ({ - id: item.id, - name: item.name, - href: item.href ?? `/${tab.id}/${item.name}.html`, - description: item.description - }))} - ariaLabel={selectedSectionName} - renderEmptyState={() => ( - - - No results - {searchValue.trim().length > 0 ? ( - - No results found for {searchValue} in {tab.label}. - - ) : ( - - No results found in {tab.label}. - - )} - - )} /> -
    -
    -
    - ))} + + +
    + {(tags.length > 0 || tabIconTag.length > 0) && ( +
    + +
    + {tags.length > 0 && ( + + {(tag) => ( + + {tag.name} + + )} + + )} + {tabIconTag.length > 0 && tags.length > 0 && ( + + )} + {tabIconTag.length > 0 && ( + + {(tag) => ( + + {tag.name} + + )} + + )} +
    +
    +
    + )} + {selectedSectionId === 'icons' ? ( + }> + + + ) : ( + { + setSearchValue(''); + onClose(); + }} + items={selectedItems.map(item => ({ + id: item.id, + name: item.name, + href: item.href ?? `/${tab.id}/${item.name}.html`, + description: item.description + }))} + ariaLabel={selectedSectionName} + renderEmptyState={() => ( + + + No results + {searchValue.trim().length > 0 ? ( + + No results found for {searchValue} in {tab.label}. + + ) : ( + + No results found in {tab.label}. + + )} + + )} /> + )} +
    +
    +
    + ); + })} );