diff --git a/Makefile b/Makefile index b4b7b4df8fb..1213c817b71 100644 --- a/Makefile +++ b/Makefile @@ -117,7 +117,6 @@ check-examples: yarn tsc --project dist/docs-examples/tsconfig.json starter: - node scripts/extractStarter.mjs cd starters/docs && yarn --no-immutable && yarn tsc starter-zip: starter diff --git a/packages/react-aria-components/docs/Button.mdx b/packages/react-aria-components/docs/Button.mdx index ad3319d735d..0bb6ce7bbb0 100644 --- a/packages/react-aria-components/docs/Button.mdx +++ b/packages/react-aria-components/docs/Button.mdx @@ -67,6 +67,10 @@ import {Button} from 'react-aria-components'; outline: none; padding: 6px 10px; text-decoration: none; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; &[data-pressed] { box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1); diff --git a/packages/react-aria-components/docs/Calendar.mdx b/packages/react-aria-components/docs/Calendar.mdx index 0ea8371c7ca..6fa2e2ce566 100644 --- a/packages/react-aria-components/docs/Calendar.mdx +++ b/packages/react-aria-components/docs/Calendar.mdx @@ -43,12 +43,13 @@ type: component ```tsx example import {Calendar, Heading, Button, CalendarGrid, CalendarCell} from 'react-aria-components'; +import {ChevronLeft, ChevronRight} from 'lucide-react';
- + - +
{date => } @@ -218,9 +219,9 @@ function MyCalendar({errorMessage, ...props}: MyCalendarPro return (
- + - +
{date => } @@ -376,9 +377,9 @@ Multiple `CalendarGrid` elements can be rendered to show multiple months at once ```tsx example
- + - +
@@ -401,9 +402,9 @@ The `pageBehavior` prop allows you to control how the calendar navigates between ```tsx example
- + - +
@@ -735,9 +736,9 @@ import {CalendarGridHeader, CalendarHeaderCell, CalendarGridBody} from 'react-ar
- + - +
@@ -920,9 +921,9 @@ function CalendarValue() {
- + - +
{date => } @@ -966,9 +967,9 @@ function WeekCalendarGrid(props: CalendarGridProps) {
- + - +
``` diff --git a/packages/react-aria-components/docs/ColorSlider.mdx b/packages/react-aria-components/docs/ColorSlider.mdx index d069ebe9b0f..7b3b1b01fb2 100644 --- a/packages/react-aria-components/docs/ColorSlider.mdx +++ b/packages/react-aria-components/docs/ColorSlider.mdx @@ -70,10 +70,14 @@ import {ColorSlider, ColorThumb, Label, SliderOutput, SliderTrack} from 'react-a .react-aria-Label { grid-area: label; + color: var(--text-color); } .react-aria-SliderOutput { grid-area: output; + width: 4ch; + text-align: end; + color: var(--text-color); } .react-aria-SliderTrack { @@ -190,7 +194,7 @@ export function MyColorSlider({label, ...props}: MyColorSliderProps) { - ({ background: `${defaultStyle.background}, repeating-conic-gradient(#CCC 0% 25%, white 0% 50%) 50% / 16px 16px` diff --git a/packages/react-aria-components/docs/ComboBox.mdx b/packages/react-aria-components/docs/ComboBox.mdx index d7df35f902a..379b8fb66f1 100644 --- a/packages/react-aria-components/docs/ComboBox.mdx +++ b/packages/react-aria-components/docs/ComboBox.mdx @@ -51,12 +51,13 @@ type: component ```tsx example import {ComboBox, Label, Input, Button, Popover, ListBox, ListBoxItem} from 'react-aria-components'; +import {ChevronDown} from 'lucide-react';
- +
@@ -338,7 +339,7 @@ function MyComboBox({label, description, errorMessage, childre
- +
{description && {description}} {errorMessage} @@ -1062,7 +1063,7 @@ The `description` slot can be used to associate additional help text with a Comb
- +
{/*- begin highlight -*/} Please select an animal. @@ -1409,6 +1410,7 @@ This example shows a `ComboBoxClearButton` component that can be placed within a ```tsx example import {ComboBoxStateContext} from 'react-aria-components'; +import {X} from 'lucide-react'; function ComboBoxClearButton() { /*- begin highlight -*/ @@ -1421,7 +1423,7 @@ function ComboBoxClearButton() { className="clear-button" aria-label="Clear" onPress={() => state?.setSelectedKey(null)}> - ✕ + ); } @@ -1433,7 +1435,7 @@ function ComboBoxClearButton() { {/*- begin highlight -*/} {/*- end highlight -*/} - +
@@ -1463,6 +1465,9 @@ function ComboBoxClearButton() { border: none; padding: 0; outline: none; + display: flex; + align-items: center; + justify-content: center; &[data-pressed] { background: dimgray; diff --git a/packages/react-aria-components/docs/DatePicker.mdx b/packages/react-aria-components/docs/DatePicker.mdx index 746739f11a1..f58b67e3bda 100644 --- a/packages/react-aria-components/docs/DatePicker.mdx +++ b/packages/react-aria-components/docs/DatePicker.mdx @@ -49,6 +49,7 @@ type: component ```tsx example import {DatePicker, Label, Group, Popover, Dialog, Calendar, CalendarGrid, CalendarCell, Button, Heading, DateInput, DateSegment} from 'react-aria-components'; +import {ChevronDown, ChevronLeft, ChevronRight} from 'lucide-react'; @@ -56,15 +57,15 @@ import {DatePicker, Label, Group, Popover, Dialog, Calendar, CalendarGrid, Calen {segment => } - +
- + - +
{date => } @@ -197,7 +198,7 @@ Note that most of this anatomy is shared with [DateRangePicker](DateRangePicker. ### Internationalization -To ensure the proper date and time format in RTL locales, `DateInput` must have `display` set to either `inline`, `inline-block`, or `block`. +To ensure the proper date and time format in RTL locales, `DateInput` must have `display` set to either `inline`, `inline-block`, or `block`. ### Concepts @@ -305,7 +306,7 @@ function MyDatePicker({label, description, errorMessage, fi {segment => } - + {description && {description}} {errorMessage} @@ -313,9 +314,9 @@ function MyDatePicker({label, description, errorMessage, fi
- + - +
{date => } @@ -507,7 +508,7 @@ import {Form, FieldError} from 'react-aria-components'; {segment => } - + {/*- begin highlight -*/} @@ -516,9 +517,9 @@ import {Form, FieldError} from 'react-aria-components';
- + - +
{date => } @@ -646,7 +647,7 @@ The `description` slot can be used to associate additional help text with a date {segment => } - + {/*- begin highlight -*/} Please select a weekday between 9 AM and 5 PM. @@ -655,9 +656,9 @@ The `description` slot can be used to associate additional help text with a date
- + - +
{date => } @@ -1148,15 +1149,15 @@ function DatePickerClearButton() { {/*- begin highlight -*/} {/*- end highlight -*/} - +
- + - +
{date => } diff --git a/packages/react-aria-components/docs/DateRangePicker.mdx b/packages/react-aria-components/docs/DateRangePicker.mdx index daad983725b..086debace01 100644 --- a/packages/react-aria-components/docs/DateRangePicker.mdx +++ b/packages/react-aria-components/docs/DateRangePicker.mdx @@ -48,6 +48,7 @@ type: component ```tsx example import {DateRangePicker, Label, Group, Popover, Dialog, RangeCalendar, CalendarGrid, CalendarCell, Button, Heading, DateInput, DateSegment} from 'react-aria-components'; +import {ChevronDown, ChevronLeft, ChevronRight} from 'lucide-react'; @@ -59,15 +60,15 @@ import {DateRangePicker, Label, Group, Popover, Dialog, RangeCalendar, CalendarG {segment => } - +
- + - +
{date => } @@ -234,7 +235,7 @@ Note that most of this anatomy is shared with [DatePicker](DatePicker.html), so ### Internationalization -To ensure the proper date and time format in RTL locales, `DateInput` must have `display` set to either `inline`, `inline-block`, or `block`. +To ensure the proper date and time format in RTL locales, `DateInput` must have `display` set to either `inline`, `inline-block`, or `block`. ### Concepts @@ -342,7 +343,7 @@ function MyDateRangePicker({label, description, errorMessag {segment => } - + {description && {description}} {errorMessage} @@ -350,9 +351,9 @@ function MyDateRangePicker({label, description, errorMessag
- + - +
{date => } @@ -567,7 +568,7 @@ import {Form, FieldError} from 'react-aria-components'; {segment => } - + {/*- begin highlight -*/} @@ -576,9 +577,9 @@ import {Form, FieldError} from 'react-aria-components';
- + - +
{date => } @@ -732,7 +733,7 @@ The `description` slot can be used to associate additional help text with a date {segment => } - + {/*- begin highlight -*/} Please your vacation dates. @@ -741,9 +742,9 @@ The `description` slot can be used to associate additional help text with a date
- + - +
{date => } @@ -1241,15 +1242,15 @@ function DateRangePickerClearButton() { {/*- begin highlight -*/} {/*- end highlight -*/} - +
- + - +
{date => } diff --git a/packages/react-aria-components/docs/Disclosure.mdx b/packages/react-aria-components/docs/Disclosure.mdx index 19de620ceb5..bf9cd439f0d 100644 --- a/packages/react-aria-components/docs/Disclosure.mdx +++ b/packages/react-aria-components/docs/Disclosure.mdx @@ -40,13 +40,12 @@ type: component ```tsx example import {Disclosure, Button, DisclosurePanel, Heading} from 'react-aria-components'; +import {ChevronRight} from 'lucide-react'; @@ -79,8 +78,6 @@ import {Disclosure, Button, DisclosurePanel, Heading} from 'react-aria-component svg { rotate: 0deg; transition: rotate 200ms; - width: 12px; - height: 12px; fill: none; stroke: currentColor; stroke-width: 3px; @@ -102,7 +99,7 @@ import {Disclosure, Button, DisclosurePanel, Heading} from 'react-aria-component ## Features -Disclosures can be built with the [<details>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details) and [<summary>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary) HTML elements, but this can be difficult to style, especially when adding adjacent interactive elements, like buttons, to the disclosure's heading. `Disclosure` helps achieve accessible disclosures implemented with the correct ARIA pattern while making custom styling easy. +Disclosures can be built with the [<details>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details) and [<summary>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary) HTML elements, but this can be difficult to style, especially when adding adjacent interactive elements, like buttons, to the disclosure's heading. `Disclosure` helps achieve accessible disclosures implemented with the correct ARIA pattern while making custom styling easy. * **Flexible** - Structured such that it can be used standalone or combined with other disclosures to form a `DisclosureGroup` * **Keyboard Interaction** - When focused, a disclosure's visibility can be toggled with either the Enter or Space key, and the appropriate ARIA attributes are automatically applied. @@ -147,9 +144,7 @@ function MyDisclosure({title, children, ...props}: MyDisclosureProps) { @@ -194,7 +189,7 @@ function ControlledExpanded() { ## Disabled -A Disclosure can be disabled using the `isDisabled` prop. +A Disclosure can be disabled using the `isDisabled` prop. ```tsx example @@ -211,9 +206,7 @@ In some use cases, you may want to add an interactive element, like a button, ad
diff --git a/packages/react-aria-components/docs/DisclosureGroup.mdx b/packages/react-aria-components/docs/DisclosureGroup.mdx index d530ce2c672..e5e8e2c5a37 100644 --- a/packages/react-aria-components/docs/DisclosureGroup.mdx +++ b/packages/react-aria-components/docs/DisclosureGroup.mdx @@ -42,14 +42,13 @@ type: component ```tsx example import {DisclosureGroup, Disclosure, Button, DisclosurePanel, Heading} from 'react-aria-components'; +import {ChevronRight} from 'lucide-react'; @@ -60,9 +59,7 @@ import {DisclosureGroup, Disclosure, Button, DisclosurePanel, Heading} from 'rea @@ -81,7 +78,7 @@ import {DisclosureGroup, Disclosure, Button, DisclosurePanel, Heading} from 'rea ## Features -Disclosure groups can be built by combining multiple disclosures built in HTML with the [<details>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details) and [<summary>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary) HTML elements, but this can be difficult to style, especially when adding adjacent interactive elements, like buttons, to the disclosure's heading. `DisclosureGroup` helps achieve accessible disclosures implemented with the correct ARIA pattern while making custom styling easy. +Disclosure groups can be built by combining multiple disclosures built in HTML with the [<details>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details) and [<summary>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary) HTML elements, but this can be difficult to style, especially when adding adjacent interactive elements, like buttons, to the disclosure's heading. `DisclosureGroup` helps achieve accessible disclosures implemented with the correct ARIA pattern while making custom styling easy. * **Accessible** - Disclosure group is exposed to assistive technology via ARIA. Uses hidden="until-found" in supported browsers, enabling find-in-page search support and improved search engine visibility for collapsed content. * **Styleable** - Disclosures include builtin states for styling such as keyboard focus, disabled, and expanded states. @@ -137,9 +134,7 @@ function MyDisclosure({title, children, ...props}: MyDisclosureProps) { @@ -188,7 +183,7 @@ function ControlledExpanded(){ ``` ### Multiple expanded -By default, only one disclosure will be open at a time. To allow multiple disclosures to expand, use the `allowsMultipleExpanded` prop. +By default, only one disclosure will be open at a time. To allow multiple disclosures to expand, use the `allowsMultipleExpanded` prop. ```tsx example @@ -203,7 +198,7 @@ By default, only one disclosure will be open at a time. To allow multiple disclo ## Disabled -A DisclosureGroup can be disabled using the `isDisabled` prop. +A DisclosureGroup can be disabled using the `isDisabled` prop. ```tsx example @@ -226,9 +221,7 @@ In some use cases, you may want to add an interactive element, like a button, ad
@@ -242,9 +235,7 @@ In some use cases, you may want to add an interactive element, like a button, ad
diff --git a/packages/react-aria-components/docs/GridList.mdx b/packages/react-aria-components/docs/GridList.mdx index b00bc9796c8..5cfc236c786 100644 --- a/packages/react-aria-components/docs/GridList.mdx +++ b/packages/react-aria-components/docs/GridList.mdx @@ -51,27 +51,28 @@ type: component ```tsx example import {GridList, GridListItem, Button} from 'react-aria-components'; import {MyCheckbox} from './Checkbox'; +import {Info} from 'lucide-react'; Charizard - + Blastoise - + Venusaur - + Pikachu - + ``` @@ -309,6 +310,7 @@ This example wraps `GridList` and all of its children together into a single com ```tsx example export=true import type {GridListProps, GridListItemProps} from 'react-aria-components'; +import {GripVertical} from 'lucide-react'; export function MyGridList({children, ...props}: GridListProps) { return ( @@ -324,7 +326,7 @@ export function MyItem({children, ...props}: Omit {({selectionMode, selectionBehavior, allowsDragging}) => <> {/* Add elements for drag and drop and selection. */} - {allowsDragging && } + {allowsDragging && } {selectionMode === 'multiple' && selectionBehavior === 'toggle' && } {children} } @@ -340,6 +342,17 @@ export function MyItem({children, ...props}: Omit ``` +
+ Show CSS +```css hidden +.react-aria-GridListItem .react-aria-Button[slot='drag'] { + display: inline-flex; + align-items: center; + justify-content: center; +} +``` +
+ ## Content So far, our examples have shown static collections, where the data is hard coded. @@ -371,7 +384,7 @@ function ExampleList(props: GridListProps) { {item => ( {item.name} - + )} diff --git a/packages/react-aria-components/docs/Group.mdx b/packages/react-aria-components/docs/Group.mdx index d5f1e4634c8..8f25ed6197e 100644 --- a/packages/react-aria-components/docs/Group.mdx +++ b/packages/react-aria-components/docs/Group.mdx @@ -39,12 +39,13 @@ type: component ```tsx example import {TextField, Label, Group, Input, Button} from 'react-aria-components'; +import {Plus} from 'lucide-react'; - + ``` diff --git a/packages/react-aria-components/docs/Menu.mdx b/packages/react-aria-components/docs/Menu.mdx index 4a9d620d167..2749ca58cbb 100644 --- a/packages/react-aria-components/docs/Menu.mdx +++ b/packages/react-aria-components/docs/Menu.mdx @@ -48,9 +48,10 @@ type: component ```tsx example import {MenuTrigger, Button, Popover, Menu, MenuItem} from 'react-aria-components'; +import {Menu as MenuIcon} from 'lucide-react'; - + alert('open')}>Open @@ -209,6 +210,7 @@ This example wraps `MenuTrigger` and all of its children together into a single ```tsx example export=true import type {MenuProps, MenuTriggerProps, MenuItemProps} from 'react-aria-components'; +import {ChevronRight} from 'lucide-react'; interface MyMenuButtonProps extends MenuProps, Omit { label?: string @@ -238,7 +240,7 @@ export function MyItem(props: Omit & {children?: Reac <> {props.children} {hasSubmenu && ( - + )} )} diff --git a/packages/react-aria-components/docs/NumberField.mdx b/packages/react-aria-components/docs/NumberField.mdx index b82df7a2c67..d0f5fb3b993 100644 --- a/packages/react-aria-components/docs/NumberField.mdx +++ b/packages/react-aria-components/docs/NumberField.mdx @@ -46,13 +46,14 @@ type: component ```tsx example import {NumberField, Label, Group, Input, Button} from 'react-aria-components'; +import {Plus, Minus} from 'lucide-react'; - + - + ``` @@ -90,6 +91,9 @@ import {NumberField, Label, Group, Input, Button} from 'react-aria-components'; font-size: 1.4rem; width: 2.3rem; padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; &[slot=decrement] { border-start-end-radius: 0; diff --git a/packages/react-aria-components/docs/Popover.mdx b/packages/react-aria-components/docs/Popover.mdx index 47badf0c502..10a95bb9405 100644 --- a/packages/react-aria-components/docs/Popover.mdx +++ b/packages/react-aria-components/docs/Popover.mdx @@ -207,6 +207,7 @@ This example wraps `Popover` and all of its children together into a single comp ```tsx example export=true import type {PopoverProps} from 'react-aria-components'; +import {HelpCircle} from 'lucide-react'; interface MyPopoverProps extends Omit { children: React.ReactNode @@ -226,7 +227,7 @@ function MyPopover({children, ...props}: MyPopoverProps) { } - + Help

For help accessing your account, please contact support.

@@ -243,21 +244,22 @@ prop. See - + In left-to-right, this is on the left. In right-to-left, this is on the right. - + This popover is above the button. - + This popover is below the button. - + In left-to-right, this is on the right. In right-to-left, this is on the left.
diff --git a/packages/react-aria-components/docs/RangeCalendar.mdx b/packages/react-aria-components/docs/RangeCalendar.mdx index eb890b67828..a5a67361e6d 100644 --- a/packages/react-aria-components/docs/RangeCalendar.mdx +++ b/packages/react-aria-components/docs/RangeCalendar.mdx @@ -43,12 +43,13 @@ type: component ```tsx example import {RangeCalendar, Heading, Button, CalendarGrid, CalendarCell} from 'react-aria-components'; +import {ChevronLeft, ChevronRight} from 'lucide-react';
- + - +
{date => } @@ -240,9 +241,9 @@ function MyRangeCalendar({errorMessage, ...props}: MyRangeC return (
- + - +
{date => } @@ -415,9 +416,9 @@ Multiple `CalendarGrid` elements can be rendered to show multiple months at once ```tsx example
- + - +
@@ -440,9 +441,9 @@ The `pageBehavior` prop allows you to control how the calendar navigates between ```tsx example
- + - +
@@ -790,9 +791,9 @@ import {CalendarGridHeader, CalendarHeaderCell, CalendarGridBody} from 'react-ar
- + - +
@@ -976,9 +977,9 @@ function RangeCalendarValue() {
- + - +
{date => } diff --git a/packages/react-aria-components/docs/SearchField.mdx b/packages/react-aria-components/docs/SearchField.mdx index a372b011da3..f484ceadd40 100644 --- a/packages/react-aria-components/docs/SearchField.mdx +++ b/packages/react-aria-components/docs/SearchField.mdx @@ -47,11 +47,12 @@ type: component ```tsx example import {SearchField, Label, Input, Button} from 'react-aria-components'; +import {X} from 'lucide-react'; - + ``` @@ -242,7 +243,7 @@ export function MySearchField({label, description, errorMessage, placeholder, .. {label && } - + {description && {description}} {errorMessage} @@ -334,7 +335,7 @@ import {Form, FieldError} from 'react-aria-components'; {/*- end highlight -*/} - + {/*- begin highlight -*/} {/*- end highlight -*/} @@ -374,7 +375,7 @@ The `description` slot can be used to associate additional help text with a sear - + {/*- begin highlight -*/} Enter an email for us to contact you about your order. {/*- end highlight -*/} @@ -513,7 +514,7 @@ Render props may also be used as children to alter what elements are rendered ba <> - {state.value !== '' && } + {state.value !== '' && } )} diff --git a/packages/react-aria-components/docs/Select.mdx b/packages/react-aria-components/docs/Select.mdx index 33ae3dac72c..8bd84efdb10 100644 --- a/packages/react-aria-components/docs/Select.mdx +++ b/packages/react-aria-components/docs/Select.mdx @@ -51,12 +51,13 @@ type: component ```tsx example import {Select, SelectValue, Label, Button, Popover, ListBox, ListBoxItem} from 'react-aria-components'; +import {ChevronDown} from 'lucide-react'; + {description && {description}} + {errorMessage} + + ) + ); +} diff --git a/starters/docs/src/ColorPicker.css b/starters/docs/src/ColorPicker.css new file mode 100644 index 00000000000..a7269ff9628 --- /dev/null +++ b/starters/docs/src/ColorPicker.css @@ -0,0 +1,43 @@ +@import './Button.css'; +@import "./ColorArea.css"; +@import "./ColorSlider.css"; +@import "./ColorSwatch.css"; +@import "./ColorSwatchPicker.css"; +@import "./ColorField.css"; +@import "./Popover.css"; +@import './Dialog.css'; +@import './ListBox.css'; +@import './Select.css'; +@import "./theme.css"; + +.color-picker { + background: none; + border: none; + padding: 0; + display: flex; + align-items: center; + gap: 8px; + outline: none; + border-radius: 4px; + appearance: none; + vertical-align: middle; + font-size: 0.875rem; + color: var(--text-color); + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: 2px; + } +} + +.color-picker-dialog { + outline: none; + padding: 15px; + display: flex; + flex-direction: column; + gap: 8px; + min-width: 192px; + max-height: inherit; + box-sizing: border-box; + overflow: auto; +} diff --git a/starters/docs/src/ColorPicker.tsx b/starters/docs/src/ColorPicker.tsx new file mode 100644 index 00000000000..c2ca042d8c8 --- /dev/null +++ b/starters/docs/src/ColorPicker.tsx @@ -0,0 +1,47 @@ +'use client'; +import { + ColorPicker as AriaColorPicker, + ColorPickerProps as AriaColorPickerProps, + DialogTrigger, + Popover +} from 'react-aria-components'; +import {Button} from './Button'; +import {ColorSwatch} from './ColorSwatch'; +import {ColorSlider} from './ColorSlider'; +import {ColorArea} from './ColorArea'; +import {ColorField} from './ColorField'; + +import './ColorPicker.css'; + +export interface ColorPickerProps extends AriaColorPickerProps { + label?: string; + children?: React.ReactNode; +} + +export function ColorPicker({ label, children, ...props }: ColorPickerProps) { + return ( + ( + + + + + {children || ( + <> + + + + + )} + + + + ) + ); +} diff --git a/starters/docs/src/ColorSlider.css b/starters/docs/src/ColorSlider.css new file mode 100644 index 00000000000..51f606c59c7 --- /dev/null +++ b/starters/docs/src/ColorSlider.css @@ -0,0 +1,82 @@ +.react-aria-ColorSlider { + display: grid; + grid-template-areas: "label output" + "track track"; + grid-template-columns: 1fr auto; + gap: 4px; + width: 100%; + max-width: 300px; + + .react-aria-Label { + grid-area: label; + color: var(--text-color); + } + + .react-aria-SliderOutput { + grid-area: output; + width: 4ch; + text-align: end; + color: var(--text-color); + } + + .react-aria-SliderTrack { + grid-area: track; + border-radius: 4px; + } + + &[data-orientation=horizontal] { + .react-aria-SliderTrack { + height: 28px; + } + + .react-aria-ColorThumb { + top: 50%; + } + } +} + +.react-aria-ColorThumb { + border: 2px solid white; + box-shadow: 0 0 0 1px black, inset 0 0 0 1px black; + width: 20px; + height: 20px; + border-radius: 50%; + box-sizing: border-box; + + &[data-focus-visible] { + width: 24px; + height: 24px; + } +} + +.react-aria-ColorSlider { + &[data-orientation=vertical] { + height: 150px; + display: block; + + .react-aria-Label, + .react-aria-SliderOutput { + display: none; + } + + .react-aria-SliderTrack { + width: 28px; + height: 100%; + } + + .react-aria-ColorThumb { + left: 50%; + } + } + + &[data-disabled] { + .react-aria-SliderTrack { + background: gray !important; + } + + .react-aria-ColorThumb { + background: gray !important; + opacity: 0.5; + } + } +} diff --git a/starters/docs/src/ColorSlider.tsx b/starters/docs/src/ColorSlider.tsx new file mode 100644 index 00000000000..7c6c3fc53bd --- /dev/null +++ b/starters/docs/src/ColorSlider.tsx @@ -0,0 +1,34 @@ +'use client'; +import { + ColorSlider as AriaColorSlider, + ColorSliderProps as AriaColorSliderProps, + ColorThumb, + Label, + SliderOutput, + SliderTrack +} from 'react-aria-components'; + +import './ColorSlider.css'; + +export interface ColorSliderProps extends AriaColorSliderProps { + label?: string; +} + +export function ColorSlider({ label, ...props }: ColorSliderProps) { + return ( + ( + + + + ({ + background: `${defaultStyle.background}, + repeating-conic-gradient(#CCC 0% 25%, white 0% 50%) 50% / 16px 16px` + })} + > + + + + ) + ); +} diff --git a/starters/docs/src/ColorSwatch.css b/starters/docs/src/ColorSwatch.css new file mode 100644 index 00000000000..8ea7c052a2e --- /dev/null +++ b/starters/docs/src/ColorSwatch.css @@ -0,0 +1,8 @@ +@import "./ColorSlider.css"; + +.react-aria-ColorSwatch { + width: 32px; + height: 32px; + border-radius: 4px; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); +} diff --git a/starters/docs/src/ColorSwatch.tsx b/starters/docs/src/ColorSwatch.tsx new file mode 100644 index 00000000000..bb434cc7cc1 --- /dev/null +++ b/starters/docs/src/ColorSwatch.tsx @@ -0,0 +1,21 @@ +'use client'; +import { + ColorSwatch as AriaColorSwatch, + ColorSwatchProps +} from 'react-aria-components'; + +import './ColorSwatch.css'; + +export function ColorSwatch(props: ColorSwatchProps) { + return ( + ( + ({ + background: `linear-gradient(${color}, ${color}), + repeating-conic-gradient(#CCC 0% 25%, white 0% 50%) 50% / 16px 16px` + })} + /> + ) + ); +} diff --git a/starters/docs/src/ColorSwatchPicker.css b/starters/docs/src/ColorSwatchPicker.css new file mode 100644 index 00000000000..630be3c8856 --- /dev/null +++ b/starters/docs/src/ColorSwatchPicker.css @@ -0,0 +1,42 @@ +@import "./ColorSwatch.css"; +@import "./ColorField.css"; +@import "./theme.css"; + +.react-aria-ColorSwatchPicker { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.react-aria-ColorSwatchPickerItem { + position: relative; + outline: none; + border-radius: 4px; + width: fit-content; + forced-color-adjust: none; + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: 2px; + } + + &[data-selected]::after { + content: ''; + position: absolute; + inset: 0; + border: 2px solid black; + outline: 2px solid white; + outline-offset: -4px; + border-radius: inherit; + } + + &[data-disabled] { + opacity: 0.2; + } +} + +.react-aria-ColorSwatchPicker { + &[data-layout=stack] { + flex-direction: column; + } +} diff --git a/starters/docs/src/ColorSwatchPicker.tsx b/starters/docs/src/ColorSwatchPicker.tsx new file mode 100644 index 00000000000..34b62a5e106 --- /dev/null +++ b/starters/docs/src/ColorSwatchPicker.tsx @@ -0,0 +1,33 @@ +'use client'; +import { + ColorSwatchPicker as AriaColorSwatchPicker, + ColorSwatchPickerItem as AriaColorSwatchPickerItem, + ColorSwatchPickerItemProps, + ColorSwatchPickerProps +} from 'react-aria-components'; + +import {ColorSwatch} from './ColorSwatch'; + +import './ColorSwatchPicker.css'; + +export function ColorSwatchPicker( + { children, ...props }: ColorSwatchPickerProps +) { + return ( + ( + + {children} + + ) + ); +} + +export function ColorSwatchPickerItem(props: ColorSwatchPickerItemProps) { + return ( + ( + + + + ) + ); +} diff --git a/starters/docs/src/ColorWheel.css b/starters/docs/src/ColorWheel.css new file mode 100644 index 00000000000..d01216ab32b --- /dev/null +++ b/starters/docs/src/ColorWheel.css @@ -0,0 +1,26 @@ +.react-aria-ColorThumb { + border: 2px solid white; + box-shadow: 0 0 0 1px black, inset 0 0 0 1px black; + width: 20px; + height: 20px; + border-radius: 50%; + box-sizing: border-box; + + &[data-focus-visible] { + width: 24px; + height: 24px; + } +} + +.react-aria-ColorWheel { + &[data-disabled] { + .react-aria-ColorWheelTrack { + background: gray !important; + } + + .react-aria-ColorThumb { + background: gray !important; + opacity: 0.5; + } + } +} diff --git a/starters/docs/src/ColorWheel.tsx b/starters/docs/src/ColorWheel.tsx new file mode 100644 index 00000000000..d5965505a77 --- /dev/null +++ b/starters/docs/src/ColorWheel.tsx @@ -0,0 +1,22 @@ +'use client'; +import { + ColorThumb, + ColorWheel as AriaColorWheel, + ColorWheelProps as AriaColorWheelProps, + ColorWheelTrack +} from 'react-aria-components'; + +import './ColorWheel.css'; +export interface ColorWheelProps + extends Omit {} + +export function ColorWheel(props: ColorWheelProps) { + return ( + ( + + + + + ) + ); +} diff --git a/starters/docs/src/ComboBox.css b/starters/docs/src/ComboBox.css new file mode 100644 index 00000000000..41c2b99ae79 --- /dev/null +++ b/starters/docs/src/ComboBox.css @@ -0,0 +1,132 @@ +@import './Checkbox.css'; +@import './ListBox.css'; +@import './Popover.css'; +@import './Form.css'; +@import './Button.css'; +@import "./theme.css"; + +.react-aria-ComboBox { + color: var(--text-color); + + > div:has(.react-aria-Input) { + display: flex; + align-items: center; + } + + .react-aria-Input { + margin: 0; + font-size: 0.9380000000000001rem; + background: var(--field-background); + color: var(--field-text-color); + border: 1px solid var(--border-color); + border-radius: 6px; + padding: 0.25025rem 1.75rem 0.25025rem 0.499625rem; + vertical-align: middle; + outline: none; + min-width: 0; + + &[data-focused] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -1px; + } + } + + .react-aria-Button { + background: var(--highlight-background); + color: var(--highlight-foreground); + forced-color-adjust: none; + border-radius: 4px; + border: none; + margin-left: -1.49975rem; + width: 1.250375rem; + height: 1.250375rem; + padding: 0; + font-size: 0.749875rem; + cursor: default; + flex-shrink: 0; + + &[data-pressed] { + box-shadow: none; + background: var(--highlight-background); + } + } +} + +.react-aria-Popover[data-trigger=ComboBox] { + width: var(--trigger-width); + + .react-aria-ListBox { + display: block; + width: unset; + max-height: inherit; + min-height: unset; + border: none; + + .react-aria-Header { + padding-left: 1.374625rem; + } + } + + .react-aria-ListBoxItem { + padding: 0 0.499625rem 0 1.374625rem; + + &[data-focus-visible] { + outline: none; + } + + &[data-selected] { + font-weight: 600; + background: unset; + color: var(--text-color); + + &::before { + content: '✓'; + content: '✓' / ''; + alt: ' '; + position: absolute; + top: 4px; + left: 4px; + } + } + + &[data-focused], + &[data-pressed] { + background: var(--highlight-background); + color: var(--highlight-foreground); + } + } +} + +.react-aria-ListBoxItem[href] { + text-decoration: none; + cursor: pointer; +} + +.react-aria-ComboBox { + .react-aria-Input { + &[data-disabled] { + border-color: var(--border-color-disabled); + } + } + + .react-aria-Button { + &[data-disabled] { + background: var(--border-color-disabled); + } + } + + .react-aria-Input { + &[data-invalid]:not([data-focused]) { + border-color: var(--invalid-color); + } + } + + .react-aria-FieldError { + font-size: 12px; + color: var(--invalid-color); + } + + [slot=description] { + font-size: 12px; + } +} diff --git a/starters/docs/src/ComboBox.tsx b/starters/docs/src/ComboBox.tsx new file mode 100644 index 00000000000..33e58ff837d --- /dev/null +++ b/starters/docs/src/ComboBox.tsx @@ -0,0 +1,52 @@ +'use client'; +import { + Button, + ComboBox as AriaComboBox, + ComboBoxProps as AriaComboBoxProps, + FieldError, + Input, + Label, + ListBoxItemProps, + Popover, + Text, + ValidationResult +} from 'react-aria-components'; +import {ListBox, ListBoxItem} from './ListBox'; +import {ChevronDown} from 'lucide-react'; + +import './ComboBox.css'; + +export interface ComboBoxProps + extends Omit, 'children'> { + label?: string; + description?: string | null; + errorMessage?: string | ((validation: ValidationResult) => string); + children: React.ReactNode | ((item: T) => React.ReactNode); +} + +export function ComboBox( + { label, description, errorMessage, children, ...props }: ComboBoxProps +) { + return ( + ( + + +
+ + +
+ {description && {description}} + {errorMessage} + + + {children} + + +
+ ) + ); +} + +export function ComboBoxItem(props: ListBoxItemProps) { + return ; +} diff --git a/starters/docs/src/DateField.css b/starters/docs/src/DateField.css new file mode 100644 index 00000000000..9602cb0327b --- /dev/null +++ b/starters/docs/src/DateField.css @@ -0,0 +1,70 @@ +@import './Form.css'; +@import './Button.css'; +@import "./theme.css"; + +.react-aria-DateField { + color: var(--text-color); + display: flex; + flex-direction: column; +} + +.react-aria-DateInput { + display: inline; + padding: 4px; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--field-background); + width: fit-content; + min-width: 150px; + white-space: nowrap; + forced-color-adjust: none; + + &[data-focus-within] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -1px; + } +} + +.react-aria-DateSegment { + padding: 0 2px; + font-variant-numeric: tabular-nums; + text-align: end; + color: var(--text-color); + + &[data-type=literal] { + padding: 0; + } + + &[data-placeholder] { + color: var(--text-color-placeholder); + font-style: italic; + } + + &:focus { + color: var(--highlight-foreground); + background: var(--highlight-background); + outline: none; + border-radius: 4px; + caret-color: transparent; + } + + &[data-invalid] { + color: var(--invalid-color); + + &:focus { + background: var(--highlight-background-invalid); + color: var(--highlight-foreground); + } + } +} + +.react-aria-DateField { + .react-aria-FieldError { + font-size: 12px; + color: var(--invalid-color); + } + + [slot=description] { + font-size: 12px; + } +} diff --git a/starters/docs/src/DateField.tsx b/starters/docs/src/DateField.tsx new file mode 100644 index 00000000000..091f293a2b6 --- /dev/null +++ b/starters/docs/src/DateField.tsx @@ -0,0 +1,38 @@ +'use client'; +import { + DateField as AriaDateField, + DateFieldProps as AriaDateFieldProps, + DateInput, + DateSegment, + DateValue, + FieldError, + Label, + Text, + ValidationResult +} from 'react-aria-components'; + +import './DateField.css'; + +export interface DateFieldProps + extends AriaDateFieldProps { + label?: string; + description?: string; + errorMessage?: string | ((validation: ValidationResult) => string); +} + +export function DateField( + { label, description, errorMessage, ...props }: DateFieldProps +) { + return ( + ( + + + + {(segment) => } + + {description && {description}} + {errorMessage} + + ) + ); +} diff --git a/starters/docs/src/DatePicker.css b/starters/docs/src/DatePicker.css new file mode 100644 index 00000000000..3a060e92dbc --- /dev/null +++ b/starters/docs/src/DatePicker.css @@ -0,0 +1,71 @@ +@import './Button.css'; +@import './Popover.css'; +@import './Dialog.css'; +@import './DateField.css'; +@import './Calendar.css'; +@import './Form.css'; +@import "./theme.css"; + +.react-aria-DatePicker { + color: var(--text-color); + + .react-aria-Group { + display: flex; + width: fit-content; + align-items: center; + } + + .react-aria-Button { + background: var(--highlight-background); + color: var(--highlight-foreground); + border: 2px solid var(--field-background); + forced-color-adjust: none; + border-radius: 4px; + border: none; + margin-left: -1.687875rem; + width: 1.250375rem; + height: 1.250375rem; + padding: 0; + font-size: 0.749875rem; + box-sizing: content-box; + + &[data-pressed] { + box-shadow: none; + background: var(--highlight-background); + } + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: 2px; + } + } + + .react-aria-DateInput { + padding: 4px 2.1875rem 4px 8px; + } +} + +.react-aria-Popover[data-trigger=DatePicker] { + max-width: unset; +} + +.react-aria-DatePicker { + &[data-invalid] { + .react-aria-DateInput:after { + content: '🚫' / ''; + content: '🚫'; + alt: ' '; + flex: 1; + text-align: end; + } + } + + .react-aria-FieldError { + font-size: 12px; + color: var(--invalid-color); + } + + [slot=description] { + font-size: 12px; + } +} diff --git a/starters/docs/src/DatePicker.tsx b/starters/docs/src/DatePicker.tsx new file mode 100644 index 00000000000..063010a60d7 --- /dev/null +++ b/starters/docs/src/DatePicker.tsx @@ -0,0 +1,50 @@ +'use client'; +import { + DateInput, + DatePicker as AriaDatePicker, + DatePickerProps as AriaDatePickerProps, + DateSegment, + DateValue, + FieldError, + Group, + Label, + Text, + ValidationResult +} from 'react-aria-components'; +import {Button} from './Button'; +import {Calendar} from './Calendar'; +import {Popover} from './Popover'; +import {ChevronDown} from 'lucide-react'; + +import './DatePicker.css'; + +export interface DatePickerProps + extends AriaDatePickerProps { + label?: string; + description?: string; + errorMessage?: string | ((validation: ValidationResult) => string); +} + +export function DatePicker( + { label, description, errorMessage, firstDayOfWeek, ...props }: + DatePickerProps +) { + return ( + ( + + + + + {(segment) => } + + + + {description && {description}} + {errorMessage} + + + + + ) + ); +} diff --git a/starters/docs/src/DateRangePicker.css b/starters/docs/src/DateRangePicker.css new file mode 100644 index 00000000000..52ea752198f --- /dev/null +++ b/starters/docs/src/DateRangePicker.css @@ -0,0 +1,109 @@ +@import './Button.css'; +@import './Popover.css'; +@import './Dialog.css'; +@import './DateField.css'; +@import './RangeCalendar.css'; +@import './Form.css'; +@import "./theme.css"; + +.react-aria-DateRangePicker { + color: var(--text-color); + + .react-aria-Group { + display: flex; + align-items: center; + width: fit-content; + min-width: 220px; + max-width: 100%; + box-sizing: border-box; + overflow: auto; + position: relative; + padding: 4px 4px 4px 8px; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--field-background); + white-space: nowrap; + + &[data-pressed] { + box-shadow: none; + background: var(--highlight-background); + } + + &[data-focus-within] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -1px; + } + } + + [slot=start] + span { + padding: 0 4px; + } + + [slot=end] { + margin-right: 1.75rem; + flex: 1; + } + + .react-aria-Button { + background: var(--highlight-background); + color: var(--highlight-foreground); + border: 2px solid var(--field-background); + forced-color-adjust: none; + border-radius: 4px; + border: none; + margin-left: auto; + width: 1.250375rem; + height: 1.250375rem; + padding: 0; + font-size: 0.749875rem; + box-sizing: content-box; + flex-shrink: 0; + position: sticky; + right: 0; + + &[data-pressed] { + box-shadow: none; + background: var(--highlight-background); + } + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: 2px; + } + } + + .react-aria-DateInput { + width: unset; + min-width: unset; + padding: unset; + border: unset; + outline: unset; + } +} + +.react-aria-Popover[data-trigger=DateRangePicker] { + max-width: unset; +} + +.react-aria-DateRangePicker { + &[data-invalid] { + [slot=end]:after { + content: '🚫' / ''; + content: '🚫'; + alt: ' '; + flex: 1; + text-align: end; + margin-left: 1.3125rem; + margin-right: -1.3125rem; + } + } + + .react-aria-FieldError { + font-size: 12px; + color: var(--invalid-color); + } + + [slot=description] { + font-size: 12px; + } +} diff --git a/starters/docs/src/DateRangePicker.tsx b/starters/docs/src/DateRangePicker.tsx new file mode 100644 index 00000000000..fdfafc2f45d --- /dev/null +++ b/starters/docs/src/DateRangePicker.tsx @@ -0,0 +1,53 @@ +'use client'; +import { + DateInput, + DateRangePicker as AriaDateRangePicker, + DateRangePickerProps as AriaDateRangePickerProps, + DateSegment, + DateValue, + FieldError, + Group, + Label, + Text, + ValidationResult +} from 'react-aria-components'; +import {Button} from './Button'; +import {Popover} from './Popover'; +import {RangeCalendar} from './RangeCalendar'; +import {ChevronDown} from 'lucide-react'; +import './DateRangePicker.css'; + +export interface DateRangePickerProps + extends AriaDateRangePickerProps { + label?: string; + description?: string; + errorMessage?: string | ((validation: ValidationResult) => string); +} + +export function DateRangePicker( + { label, description, errorMessage, firstDayOfWeek, ...props }: + DateRangePickerProps +) { + return ( + ( + + + + + {(segment) => } + + + + {(segment) => } + + + + {description && {description}} + {errorMessage} + + + + + ) + ); +} diff --git a/starters/docs/src/Dialog.css b/starters/docs/src/Dialog.css new file mode 100644 index 00000000000..a5051aeb2c6 --- /dev/null +++ b/starters/docs/src/Dialog.css @@ -0,0 +1,17 @@ +@import "./theme.css"; +@import './Button.css'; +@import './TextField.css'; +@import './Modal.css'; + +.react-aria-Dialog { + outline: none; + padding: 30px; + max-height: inherit; + box-sizing: border-box; + overflow: auto; + + .react-aria-Heading[slot=title] { + line-height: 1em; + margin-top: 0; + } +} diff --git a/starters/docs/src/Dialog.tsx b/starters/docs/src/Dialog.tsx new file mode 100644 index 00000000000..f9d491da3dd --- /dev/null +++ b/starters/docs/src/Dialog.tsx @@ -0,0 +1,7 @@ +'use client'; +import {Dialog as RACDialog, DialogProps} from 'react-aria-components'; +import './Dialog.css'; + +export function Dialog(props: DialogProps) { + return ; +} diff --git a/starters/docs/src/Disclosure.css b/starters/docs/src/Disclosure.css new file mode 100644 index 00000000000..97a0ce5fc11 --- /dev/null +++ b/starters/docs/src/Disclosure.css @@ -0,0 +1,32 @@ +@import "./theme.css"; +@import './Button.css'; + +.react-aria-Disclosure { + .react-aria-Button[slot=trigger] { + background: none; + border: none; + box-shadow: none; + font-weight: bold; + font-size: 16px; + display: flex; + align-items: center; + gap: 8px; + + svg { + rotate: 0deg; + transition: rotate 200ms; + fill: none; + stroke: currentColor; + stroke-width: 3px; + } + } + + &[data-expanded] .react-aria-Button[slot=trigger] svg { + rotate: 90deg; + } +} + +.react-aria-DisclosurePanel { + margin-left: 32px; + color: var(--text-color); +} diff --git a/starters/docs/src/Disclosure.tsx b/starters/docs/src/Disclosure.tsx new file mode 100644 index 00000000000..27fa6eae384 --- /dev/null +++ b/starters/docs/src/Disclosure.tsx @@ -0,0 +1,34 @@ +'use client'; +import { + Button, + Disclosure as AriaDisclosure, + DisclosurePanel, + DisclosureProps as AriaDisclosureProps, + Heading +} from 'react-aria-components'; +import {ChevronRight} from 'lucide-react'; + +import './Disclosure.css'; + +export interface DisclosureProps extends Omit { + title?: string; + children?: React.ReactNode; +} + +export function Disclosure({ title, children, ...props }: DisclosureProps) { + return ( + ( + + + + + +

{children}

+
+
+ ) + ); +} diff --git a/starters/docs/src/DisclosureGroup.css b/starters/docs/src/DisclosureGroup.css new file mode 100644 index 00000000000..a6bfd3f1af0 --- /dev/null +++ b/starters/docs/src/DisclosureGroup.css @@ -0,0 +1,3 @@ +@import "./theme.css"; +@import './Button.css'; +@import './Disclosure.css' diff --git a/starters/docs/src/DisclosureGroup.tsx b/starters/docs/src/DisclosureGroup.tsx new file mode 100644 index 00000000000..a4ba8b635ff --- /dev/null +++ b/starters/docs/src/DisclosureGroup.tsx @@ -0,0 +1,7 @@ +'use client'; +import {DisclosureGroup as RACDisclosureGroup, DisclosureGroupProps} from 'react-aria-components'; +import './DisclosureGroup.css'; + +export function DisclosureGroup(props: DisclosureGroupProps) { + return ; +} diff --git a/starters/docs/src/Form.css b/starters/docs/src/Form.css new file mode 100644 index 00000000000..984c3d0a8b3 --- /dev/null +++ b/starters/docs/src/Form.css @@ -0,0 +1,31 @@ +@import "./theme.css"; +@import './Button.css'; + +.react-aria-Form { + display: flex; + flex-direction: column; + align-items: start; + gap: 8px; +} + +.react-aria-Form [role=alert] { + border: 2px solid var(--invalid-color); + background: var(--overlay-background); + border-radius: 6px; + padding: 12px; + max-width: 250px; + outline: none; + + &:focus-visible { + outline: 2px solid var(--focus-ring-color); + outline-offset: 2px; + } + + h3 { + margin-top: 0; + } + + p { + margin-bottom: 0; + } +} diff --git a/starters/docs/src/Form.tsx b/starters/docs/src/Form.tsx new file mode 100644 index 00000000000..2daca77d814 --- /dev/null +++ b/starters/docs/src/Form.tsx @@ -0,0 +1,7 @@ +'use client'; +import {Form as RACForm, FormProps} from 'react-aria-components'; +import './Form.css'; + +export function Form(props: FormProps) { + return ; +} diff --git a/starters/docs/src/GridList.css b/starters/docs/src/GridList.css new file mode 100644 index 00000000000..823d4d8bb4a --- /dev/null +++ b/starters/docs/src/GridList.css @@ -0,0 +1,190 @@ +@import './Button.css'; +@import './Checkbox.css'; +@import './ToggleButton.css'; +@import "./theme.css"; + +.react-aria-GridList { + display: flex; + flex-direction: column; + gap: 2px; + max-height: inherit; + overflow: auto; + padding: 4px; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--overlay-background); + forced-color-adjust: none; + outline: none; + width: 250px; + max-height: 300px; + min-height: 100px; + box-sizing: border-box; + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -1px; + } + + .react-aria-GridListItem { + display: flex; + align-items: center; + gap: 0.499625rem; + min-height: 28px; + padding: 0.25025rem 0.25025rem 0.25025rem 0.499625rem; + border-radius: 6px; + outline: none; + cursor: default; + color: var(--text-color); + font-size: 0.9380000000000001rem; + position: relative; + transform: translateZ(0); + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -2px; + } + + &[data-pressed] { + background: var(--gray-100); + } + + &[data-selected] { + background: var(--highlight-background); + color: var(--highlight-foreground); + --focus-ring-color: var(--highlight-foreground); + + &[data-focus-visible] { + outline-color: var(--highlight-foreground); + outline-offset: -4px; + } + + .react-aria-Button { + color: var(--highlight-foreground); + --highlight-hover: rgb(255 255 255 / 0.1); + --highlight-pressed: rgb(255 255 255 / 0.2); + } + } + + &[data-disabled] { + color: var(--text-color-disabled); + } + + .react-aria-Button:not([slot]) { + margin-left: auto; + } + + .react-aria-Button { + background: transparent; + border: none; + font-size: 1.05rem; + line-height: 1.2em; + padding: 0.25025rem 0.375375rem; + transition: background 200ms; + + &[data-hovered] { + background: var(--highlight-hover); + } + + &[data-pressed] { + background: var(--highlight-pressed); + box-shadow: none; + } + } + + .react-aria-Button[slot='drag'] { + display: inline-flex; + align-items: center; + justify-content: center; + } + } + + /* join selected items if :has selector is supported */ + @supports selector(:has(.foo)) { + gap: 0; + + .react-aria-GridListItem[data-selected]:has(+ [data-selected]), + .react-aria-GridListItem[data-selected]:has(+ .react-aria-DropIndicator + [data-selected]) { + border-end-start-radius: 0; + border-end-end-radius: 0; + } + + .react-aria-GridListItem[data-selected] + [data-selected], + .react-aria-GridListItem[data-selected] + .react-aria-DropIndicator + [data-selected] { + border-start-start-radius: 0; + border-start-end-radius: 0; + } + } + + :where(.react-aria-GridListItem) .react-aria-Checkbox { + --selected-color: var(--highlight-foreground); + --selected-color-pressed: var(--highlight-foreground-pressed); + --checkmark-color: var(--highlight-background); + --background-color: var(--highlight-background); + } +} + +.react-aria-GridListItem[data-href] { + cursor: pointer; +} + +.react-aria-GridList { + &[data-empty] { + align-items: center; + justify-content: center; + font-style: italic; + } +} + +.react-aria-GridListItem { + &[data-allows-dragging] { + padding-left: 4px; + } + + &[data-dragging] { + opacity: 0.6; + } + + [slot=drag] { + all: unset; + width: 15px; + text-align: center; + + &[data-focus-visible] { + border-radius: 4px; + outline: 2px solid var(--focus-ring-color); + } + } +} + +.react-aria-DropIndicator { + &[data-drop-target] { + outline: 1px solid var(--highlight-background); + } + + @supports not selector(:has(.foo)) { + /* Undo gap in browsers that don't support :has */ + margin-bottom: -2px; + } +} + +.react-aria-GridList[data-drop-target] { + outline: 2px solid var(--highlight-background); + outline-offset: -1px; + background: var(--highlight-overlay); +} + +.react-aria-GridListItem[data-drop-target] { + outline: 2px solid var(--highlight-background); + background:var(--highlight-overlay); +} + +.react-aria-DropIndicator { + &[data-drop-target] { + outline: 1px solid var(--highlight-background); + } + + @supports not selector(:has(.foo)) { + /* Undo gap in browsers that don't support :has */ + margin-bottom: -2px; + } +} diff --git a/starters/docs/src/GridList.tsx b/starters/docs/src/GridList.tsx new file mode 100644 index 00000000000..318bdba69f8 --- /dev/null +++ b/starters/docs/src/GridList.tsx @@ -0,0 +1,47 @@ +'use client'; +import { + Button, + GridList as AriaGridList, + GridListItem as AriaGridListItem, + GridListItemProps, + GridListProps +} from 'react-aria-components'; +import {Checkbox} from './Checkbox'; +import {VerticalGrip} from 'lucide-react'; +import './GridList.css'; + +export function GridList( + { children, ...props }: GridListProps +) { + return ( + ( + + {children} + + ) + ); +} + +export function GridListItem( + { children, ...props }: Omit & { + children?: React.ReactNode; + } +) { + let textValue = typeof children === 'string' ? children : undefined; + return ( + ( + + {({ selectionMode, selectionBehavior, allowsDragging }) => ( + <> + {/* Add elements for drag and drop and selection. */} + {allowsDragging && } + {selectionMode === 'multiple' && selectionBehavior === 'toggle' && ( + + )} + {children} + + )} + + ) + ); +} diff --git a/starters/docs/src/Link.css b/starters/docs/src/Link.css new file mode 100644 index 00000000000..6e582149850 --- /dev/null +++ b/starters/docs/src/Link.css @@ -0,0 +1,32 @@ +@import "./theme.css"; + +.react-aria-Link { + color: var(--link-color); + font-size: 18px; + transition: all 200ms; + text-decoration: underline; + cursor: pointer; + outline: none; + position: relative; + + &[data-hovered] { + text-decoration-style: wavy; + } + + &[data-pressed] { + color: var(--link-color-pressed); + } + + &[data-focus-visible]:after { + content: ''; + position: absolute; + inset: -3px -6px; + border-radius: 6px; + border: 2px solid var(--focus-ring-color); + } + + &[data-disabled] { + cursor: default; + color: var(--text-color-disabled); + } +} diff --git a/starters/docs/src/Link.tsx b/starters/docs/src/Link.tsx new file mode 100644 index 00000000000..b3662fa638a --- /dev/null +++ b/starters/docs/src/Link.tsx @@ -0,0 +1,7 @@ +'use client'; +import {Link as RACLink, LinkProps} from 'react-aria-components'; +import './Link.css'; + +export function Link(props: LinkProps) { + return ; +} diff --git a/starters/docs/src/ListBox.css b/starters/docs/src/ListBox.css new file mode 100644 index 00000000000..3a84a03b3f4 --- /dev/null +++ b/starters/docs/src/ListBox.css @@ -0,0 +1,219 @@ +@import './Checkbox.css'; +@import "./theme.css"; + +.react-aria-ListBox { + display: flex; + flex-direction: column; + gap: 4px; + max-height: inherit; + overflow: auto; + padding: 4px; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--overlay-background); + forced-color-adjust: none; + outline: none; + width: 250px; + max-height: 300px; + min-height: 100px; + box-sizing: border-box; + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -1px; + } +} + +.react-aria-ListBoxItem { + padding: 0 0.499625rem; + border-radius: 6px; + outline: none; + cursor: default; + color: var(--text-color); + font-size: 0.9380000000000001rem; + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + min-height: 32px; + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -2px; + } + + &[data-selected] { + background: var(--highlight-background); + color: var(--highlight-foreground); + + &[data-focus-visible] { + outline-color: var(--highlight-foreground); + outline-offset: -4px; + } + } +} + +.react-aria-ListBoxItem[href] { + text-decoration: none; + cursor: pointer; + -webkit-touch-callout: none; +} + +.react-aria-ListBox { + .react-aria-ListBoxSection:not(:first-child) { + margin-top: 12px; + } + + .react-aria-Header { + font-size: 1.000125rem; + font-weight: bold; + padding: 0 0.6247499999999999rem; + } +} + +.react-aria-ListBoxItem { + [slot=label] { + font-weight: bold; + } + + [slot=description] { + font-size: small; + } +} + +.react-aria-ListBox[data-orientation=horizontal], +.react-aria-ListBox[data-layout=grid] { + flex-direction: row; + width: fit-content; + max-width: 100%; + padding: 4px; + + .react-aria-ListBoxItem { + position: relative; + margin: 0; + padding: 4px; + + & img { + object-fit: cover; + aspect-ratio: 1/1; + max-width: 150px; + margin-bottom: 4px; + border-radius: 4px; + } + + &[data-selected] { + background: none; + color: inherit; + + &:after { + content: '✓'; + content: '✓' / ''; + alt: ' '; + position: absolute; + top: 8px; + right: 8px; + background: var(--highlight-background); + border: 2px solid var(--highlight-foreground); + color: var(--highlight-foreground); + width: 22px; + height: 22px; + border-radius: 22px; + box-sizing: border-box; + font-size: 14px; + line-height: 1em; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 0 8px rgb(0 0 0 / .5); + } + } + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -2px; + } + } +} + +.react-aria-ListBox[data-layout=grid] { + display: grid; + grid-template-columns: 1fr 1fr; + scrollbar-gutter: stable; +} + +.react-aria-ListBox[data-layout=grid][data-orientation=horizontal] { + width: 100%; + max-width: none; + display: grid; + grid-auto-flow: column; + grid-template-rows: 58px 58px; + grid-template-columns: none; + grid-auto-columns: 250px; + max-height: 200px; + gap: 8px; + + .react-aria-ListBoxItem { + display: grid; + grid-template-areas: "image ." + "image title" + "image description" + "image ."; + grid-template-columns: auto 1fr; + grid-template-rows: 1fr auto auto 1fr; + column-gap: 8px; + + & img { + width: 50px; + height: 50px; + grid-area: image; + margin-bottom: 0; + } + + [slot=label] { + grid-area: title; + } + + [slot=description] { + grid-area: description; + } + } +} + +.react-aria-ListBoxItem { + &[data-disabled] { + color: var(--text-color-disabled); + } +} + +.react-aria-ListBox { + &[data-empty] { + align-items: center; + justify-content: center; + font-style: italic; + } +} + +.react-aria-ListBoxItem { + &[data-dragging] { + opacity: 0.6; + } +} + +.react-aria-DropIndicator[data-drop-target] { + outline: 1px solid var(--highlight-background); +} + +.react-aria-ListBox[data-drop-target] { + outline: 2px solid var(--highlight-background); + outline-offset: -1px; + background: var(--highlight-overlay) +} + +.react-aria-ListBoxItem[data-drop-target] { + outline: 2px solid var(--highlight-background); + background: var(--highlight-overlay) +} + +.react-aria-DropIndicator[data-drop-target] { + outline: 1px solid var(--highlight-background); +} diff --git a/starters/docs/src/ListBox.tsx b/starters/docs/src/ListBox.tsx new file mode 100644 index 00000000000..4a448121676 --- /dev/null +++ b/starters/docs/src/ListBox.tsx @@ -0,0 +1,25 @@ +'use client'; +import { + ListBox as AriaListBox, + ListBoxItem as AriaListBoxItem, + ListBoxItemProps, + ListBoxProps +} from 'react-aria-components'; + +import './ListBox.css'; + +export function ListBox( + { children, ...props }: ListBoxProps +) { + return ( + ( + + {children} + + ) + ); +} + +export function ListBoxItem(props: ListBoxItemProps) { + return ; +} diff --git a/starters/docs/src/Menu.css b/starters/docs/src/Menu.css new file mode 100644 index 00000000000..2b3eb4f3c7f --- /dev/null +++ b/starters/docs/src/Menu.css @@ -0,0 +1,120 @@ +@import './Button.css'; +@import './Popover.css'; +@import './SearchField.css'; +@import "./theme.css"; + +.react-aria-Menu { + max-height: inherit; + box-sizing: border-box; + overflow: auto; + padding: 2px; + min-width: 150px; + box-sizing: border-box; + outline: none; +} + +.react-aria-MenuItem { + margin: 2px; + padding: 0.25025rem 0.499625rem; + border-radius: 6px; + outline: none; + cursor: default; + color: var(--text-color); + font-size: 0.9380000000000001rem; + position: relative; + display: grid; + grid-template-areas: "label kbd" + "desc kbd"; + align-items: center; + column-gap: 20px; + forced-color-adjust: none; + + &[data-focused] { + background: var(--highlight-background); + color: var(--highlight-foreground); + } +} + +.react-aria-MenuItem { + &[data-selection-mode] { + padding-left: 24px; + + &::before { + position: absolute; + left: 4px; + font-weight: 600; + } + + &[data-selection-mode=multiple][data-selected]::before { + content: '✓'; + content: '✓' / ''; + alt: ' '; + position: absolute; + left: 4px; + font-weight: 600; + } + + &[data-selection-mode=single][data-selected]::before { + content: '●'; + content: '●' / ''; + transform: scale(0.7) + } + } +} + +.react-aria-MenuItem[href] { + text-decoration: none; + cursor: pointer; +} + +.react-aria-Menu { + .react-aria-MenuSection:not(:first-child) { + margin-top: 12px; + } + + .react-aria-Header { + font-size: 1.000125rem; + font-weight: bold; + padding: 0 0.6247499999999999rem; + } + + .react-aria-Separator { + height: 1px; + background: var(--border-color); + margin: 2px 4px; + } +} + +.react-aria-MenuItem { + [slot=label] { + font-weight: bold; + grid-area: label; + } + + [slot=description] { + font-size: small; + grid-area: desc; + } + + kbd { + grid-area: kbd; + font-family: monospace; + text-align: end; + } + + &[data-disabled] { + color: var(--text-color-disabled); + } +} + +.react-aria-Popover[data-trigger=SubmenuTrigger][data-placement="right"] { + margin-left: -5px; +} + +.react-aria-Popover[data-trigger=SubmenuTrigger][data-placement="left"] { + margin-right: -5px; +} + +.react-aria-Popover[data-trigger=SubmenuTrigger] .react-aria-SearchField { + margin: 4px 8px; +} diff --git a/starters/docs/src/Menu.tsx b/starters/docs/src/Menu.tsx new file mode 100644 index 00000000000..24d8a9bbde7 --- /dev/null +++ b/starters/docs/src/Menu.tsx @@ -0,0 +1,55 @@ +'use client'; +import { + Button, + Menu, + MenuItem as AriaMenuItem, + MenuItemProps, + MenuProps, + MenuTrigger, + MenuTriggerProps, + Popover +} from 'react-aria-components'; +import {ChevronRight} from 'lucide-react'; + +import './Menu.css'; + +export interface MenuButtonProps + extends MenuProps, Omit { + label?: string; +} + +export function MenuButton( + { label, children, ...props }: MenuButtonProps +) { + return ( + + + + + {children} + + + + ); +} + +export function MenuItem( + props: Omit & { children?: React.ReactNode } +) { + let textValue = props.textValue || + (typeof props.children === 'string' ? props.children : undefined); + return ( + ( + + {({ hasSubmenu }) => ( + <> + {props.children} + {hasSubmenu && ( + + )} + + )} + + ) + ); +} diff --git a/starters/docs/src/Meter.css b/starters/docs/src/Meter.css new file mode 100644 index 00000000000..6fe0833fe62 --- /dev/null +++ b/starters/docs/src/Meter.css @@ -0,0 +1,37 @@ +@import "./theme.css"; + +.react-aria-Meter { + --fill-color: forestgreen; + + display: grid; + grid-template-areas: "label value" + "bar bar"; + grid-template-columns: 1fr auto; + gap: 4px; + width: 250px; + color: var(--text-color); + + .value { + grid-area: value; + } + + .bar { + grid-area: bar; + box-shadow: inset 0px 0px 0px 1px var(--border-color); + forced-color-adjust: none; + height: 10px; + border-radius: 5px; + overflow: hidden; + } + + .fill { + background: var(--fill-color); + height: 100%; + } +} + +@media (forced-colors: active) { + .react-aria-Meter { + --fill-color: Highlight; + } +} diff --git a/starters/docs/src/Meter.tsx b/starters/docs/src/Meter.tsx new file mode 100644 index 00000000000..8e6485f0a0e --- /dev/null +++ b/starters/docs/src/Meter.tsx @@ -0,0 +1,30 @@ +'use client'; +import { + Label, + Meter as AriaMeter, + MeterProps as AriaMeterProps +} from 'react-aria-components'; + +import './Meter.css'; + +export interface MeterProps extends AriaMeterProps { + label?: string; +} + +export function Meter({ label, ...props }: MeterProps) { + return ( + ( + + {({ percentage, valueText }) => ( + <> + + {valueText} +
+
+
+ + )} + + ) + ); +} diff --git a/starters/docs/src/Modal.css b/starters/docs/src/Modal.css new file mode 100644 index 00000000000..f8fec116425 --- /dev/null +++ b/starters/docs/src/Modal.css @@ -0,0 +1,84 @@ +@import './Button.css'; +@import './TextField.css'; +@import "./theme.css"; + +.react-aria-ModalOverlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: var(--visual-viewport-height); + background: rgba(0 0 0 / .5); + display: flex; + align-items: center; + justify-content: center; + z-index: 100; + + &[data-entering] { + animation: modal-fade 200ms; + } + + &[data-exiting] { + animation: modal-fade 150ms reverse ease-in; + } +} + +.react-aria-Modal { + box-shadow: 0 8px 20px rgba(0 0 0 / 0.1); + border-radius: 6px; + background: var(--overlay-background); + color: var(--text-color); + border: 1px solid var(--gray-400); + outline: none; + max-width: 300px; + + &[data-entering] { + animation: modal-zoom 300ms cubic-bezier(0.175, 0.885, 0.32, 1.275); + } + + .react-aria-TextField { + margin-bottom: 8px; + } +} + +@keyframes modal-fade { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes modal-zoom { + from { + transform: scale(0.8); + } + + to { + transform: scale(1); + } +} + +@keyframes mymodal-blur { + from { + background: rgba(45 0 0 / 0); + backdrop-filter: blur(0); + } + + to { + background: rgba(45 0 0 / .3); + backdrop-filter: blur(10px); + } +} + +@keyframes mymodal-slide { + from { + transform: translateX(100%); + } + + to { + transform: translateX(0); + } +} diff --git a/starters/docs/src/Modal.tsx b/starters/docs/src/Modal.tsx new file mode 100644 index 00000000000..98d08e60c82 --- /dev/null +++ b/starters/docs/src/Modal.tsx @@ -0,0 +1,7 @@ +'use client'; +import {Modal as RACModal, ModalOverlayProps} from 'react-aria-components'; +import './Modal.css'; + +export function Modal(props: ModalOverlayProps) { + return ; +} diff --git a/starters/docs/src/NumberField.css b/starters/docs/src/NumberField.css new file mode 100644 index 00000000000..8ef9a35ad28 --- /dev/null +++ b/starters/docs/src/NumberField.css @@ -0,0 +1,92 @@ +@import './Button.css'; +@import './Form.css'; +@import "./theme.css"; + +.react-aria-NumberField { + margin-bottom: 8px; + color: var(--text-color); + + .react-aria-Group { + display: flex; + width: fit-content; + border-radius: 4px; + + &[data-focus-within] { + outline: 1px solid var(--focus-ring-color); + .react-aria-Input, + .react-aria-Button { + border-color: var(--focus-ring-color); + } + } + } + + .react-aria-Button { + font-size: 1.2249999999999999rem; + width: 2.0124999999999997rem; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + + &[slot=decrement] { + border-start-end-radius: 0; + border-end-end-radius: 0; + } + + &[slot=increment] { + border-start-start-radius: 0; + border-end-start-radius: 0; + } + } + + .react-aria-Input { + background: var(--field-background); + border: 1px solid var(--border-color); + border-radius: 0; + color: var(--field-text-color); + margin: 0 -1px; + z-index: 1; + font-size: 0.875rem; + padding: 0.375375rem 0.499625rem; + outline: none; + width: 5.25rem; + flex: 1; + } + + &[data-invalid] { + .react-aria-Input, + .react-aria-Button { + border-color: var(--invalid-color); + } + + &:focus-within { + .react-aria-Input, + .react-aria-Button { + border-color: var(--focus-ring-color); + } + } + } + + .react-aria-FieldError { + font-size: 12px; + color: var(--invalid-color); + } + + [slot=description] { + font-size: 12px; + } + + .react-aria-Button { + &[data-disabled] { + border-color: var(--border-color-disabled); + color: var(--text-color-disabled); + } + } + + .react-aria-Input { + &[data-disabled] { + border-color: var(--border-color-disabled); + color: var(--text-color-disabled); + } + } +} diff --git a/starters/docs/src/NumberField.tsx b/starters/docs/src/NumberField.tsx new file mode 100644 index 00000000000..e997a1acbc2 --- /dev/null +++ b/starters/docs/src/NumberField.tsx @@ -0,0 +1,40 @@ +'use client'; +import { + Button, + FieldError, + Group, + Input, + Label, + NumberField as AriaNumberField, + NumberFieldProps as AriaNumberFieldProps, + Text, + ValidationResult +} from 'react-aria-components'; +import {Plus, Minus} from 'lucide-react'; + +import './NumberField.css'; + +export interface NumberFieldProps extends AriaNumberFieldProps { + label?: string; + description?: string; + errorMessage?: string | ((validation: ValidationResult) => string); +} + +export function NumberField( + { label, description, errorMessage, ...props }: NumberFieldProps +) { + return ( + ( + + + + + + + + {description && {description}} + {errorMessage} + + ) + ); +} diff --git a/starters/docs/src/Popover.css b/starters/docs/src/Popover.css new file mode 100644 index 00000000000..0851134fd41 --- /dev/null +++ b/starters/docs/src/Popover.css @@ -0,0 +1,74 @@ +@import './Button.css'; +@import './Dialog.css'; +@import './Switch.css'; +@import "./theme.css"; + +.react-aria-Popover { + --background-color: var(--overlay-background); + + border: 1px solid var(--border-color); + box-shadow: 0 8px 20px rgba(0 0 0 / 0.1); + border-radius: 6px; + background: var(--background-color); + color: var(--text-color); + outline: none; + max-width: 250px; + transition: transform 200ms, opacity 200ms; + + .react-aria-OverlayArrow svg { + display: block; + fill: var(--background-color); + stroke: var(--border-color); + stroke-width: 1px; + } + + &[data-entering], + &[data-exiting] { + transform: var(--origin); + opacity: 0; + } + + &[data-placement=top] { + --origin: translateY(8px); + + &:has(.react-aria-OverlayArrow) { + margin-bottom: 6px; + } + } + + &[data-placement=bottom] { + --origin: translateY(-8px); + + &:has(.react-aria-OverlayArrow) { + margin-top: 6px; + } + + .react-aria-OverlayArrow svg { + transform: rotate(180deg); + } + } + + &[data-placement=right] { + --origin: translateX(-8px); + + &:has(.react-aria-OverlayArrow) { + margin-left: 6px; + } + + .react-aria-OverlayArrow svg { + transform: rotate(90deg); + } + } + + &[data-placement=left] { + --origin: translateX(8px); + + &:has(.react-aria-OverlayArrow) { + margin-right: 6px; + } + + .react-aria-OverlayArrow svg { + transform: rotate(-90deg); + } + } +} diff --git a/starters/docs/src/Popover.tsx b/starters/docs/src/Popover.tsx new file mode 100644 index 00000000000..25e1517b0d7 --- /dev/null +++ b/starters/docs/src/Popover.tsx @@ -0,0 +1,30 @@ +'use client'; +import { + OverlayArrow, + Popover as AriaPopover, + PopoverProps as AriaPopoverProps +} from 'react-aria-components'; + +import './Popover.css'; + +export interface PopoverProps extends Omit { + children: React.ReactNode; + hideArrow?: boolean; +} + +export function Popover({ children, hideArrow, ...props }: PopoverProps) { + return ( + ( + + {!hideArrow && ( + + + + + + )} + {children} + + ) + ); +} diff --git a/starters/docs/src/ProgressBar.css b/starters/docs/src/ProgressBar.css new file mode 100644 index 00000000000..de35556b806 --- /dev/null +++ b/starters/docs/src/ProgressBar.css @@ -0,0 +1,49 @@ +@import "./theme.css"; + +.react-aria-ProgressBar { + display: grid; + grid-template-areas: "label value" + "bar bar"; + grid-template-columns: 1fr auto; + gap: 4px; + width: 250px; + color: var(--text-color); + + .value { + grid-area: value; + } + + .bar { + grid-area: bar; + box-shadow: inset 0px 0px 0px 1px var(--border-color); + forced-color-adjust: none; + height: 10px; + border-radius: 5px; + overflow: hidden; + will-change: transform; + } + + .fill { + background: var(--highlight-background); + height: 100%; + } + + &:not([aria-valuenow]) { + .fill { + width: 120px; + border-radius: inherit; + animation: indeterminate 1.5s infinite ease-in-out; + will-change: transform; + } + } +} + +@keyframes indeterminate { + from { + transform: translateX(-100%); + } + + to { + transform: translateX(250px); + } +} diff --git a/starters/docs/src/ProgressBar.tsx b/starters/docs/src/ProgressBar.tsx new file mode 100644 index 00000000000..0604969fbe9 --- /dev/null +++ b/starters/docs/src/ProgressBar.tsx @@ -0,0 +1,30 @@ +'use client'; +import { + Label, + ProgressBar as AriaProgressBar, + ProgressBarProps as AriaProgressBarProps +} from 'react-aria-components'; + +import './ProgressBar.css'; + +export interface ProgressBarProps extends AriaProgressBarProps { + label?: string; +} + +export function ProgressBar({ label, ...props }: ProgressBarProps) { + return ( + ( + + {({ percentage, valueText }) => ( + <> + + {valueText} +
+
+
+ + )} + + ) + ); +} diff --git a/starters/docs/src/RadioGroup.css b/starters/docs/src/RadioGroup.css new file mode 100644 index 00000000000..1d32232f0f5 --- /dev/null +++ b/starters/docs/src/RadioGroup.css @@ -0,0 +1,89 @@ +@import './Button.css'; +@import './Form.css'; +@import "./theme.css"; + +.react-aria-RadioGroup { + display: flex; + flex-direction: column; + gap: 8px; + color: var(--text-color); +} + +.react-aria-Radio { + display: flex; + /* This is needed so the HiddenInput is positioned correctly */ + position: relative; + align-items: center; + gap: 0.499625rem; + font-size: 1.000125rem; + color: var(--text-color); + forced-color-adjust: none; + + &:before { + content: ''; + display: block; + width: 1.12525rem; + height: 1.12525rem; + box-sizing: border-box; + border: 0.125125rem solid var(--border-color); + background: var(--field-background); + border-radius: 1.12525rem; + transition: all 200ms; + } + + &[data-pressed]:before { + border-color: var(--border-color-pressed); + } + + &[data-selected] { + &:before { + border-color: var(--highlight-background); + border-width: 0.375375rem; + } + + &[data-pressed]:before { + border-color: var(--highlight-background-pressed); + } + } + + &[data-focus-visible]:before { + outline: 2px solid var(--focus-ring-color); + outline-offset: 2px; + } + + &[data-invalid] { + &:before { + border-color: var(--invalid-color); + } + + &[data-pressed]:before { + border-color: var(--invalid-color-pressed); + } + } +} + +.react-aria-RadioGroup { + .react-aria-FieldError { + font-size: 12px; + color: var(--invalid-color); + } + + [slot=description] { + font-size: 12px; + } + + &[data-orientation=horizontal] { + flex-direction: row; + align-items: center; + } +} + +.react-aria-Radio { + &[data-disabled] { + color: var(--text-color-disabled); + + &:before { + border-color: var(--border-color-disabled); + } + } +} diff --git a/starters/docs/src/RadioGroup.tsx b/starters/docs/src/RadioGroup.tsx new file mode 100644 index 00000000000..49180e63082 --- /dev/null +++ b/starters/docs/src/RadioGroup.tsx @@ -0,0 +1,39 @@ +'use client'; +import { + FieldError, + Label, + RadioGroup as AriaRadioGroup, + RadioGroupProps as AriaRadioGroupProps, + Text, + ValidationResult +} from 'react-aria-components'; + +import './RadioGroup.css'; + +export interface RadioGroupProps extends Omit { + children?: React.ReactNode; + label?: string; + description?: string; + errorMessage?: string | ((validation: ValidationResult) => string); +} + +export function RadioGroup( + { + label, + description, + errorMessage, + children, + ...props + }: RadioGroupProps +) { + return ( + ( + + + {children} + {description && {description}} + {errorMessage} + + ) + ); +} diff --git a/starters/docs/src/RangeCalendar.css b/starters/docs/src/RangeCalendar.css new file mode 100644 index 00000000000..dcb289c7576 --- /dev/null +++ b/starters/docs/src/RangeCalendar.css @@ -0,0 +1,103 @@ +@import './Button.css'; +@import "./theme.css"; + +.react-aria-RangeCalendar { + width: fit-content; + max-width: 100%; + color: var(--text-color); + + & header { + display: flex; + align-items: center; + margin: 0 4px 0.4375rem 4px; + + .react-aria-Heading { + flex: 1; + margin: 0; + text-align: center; + font-size: 1.203125rem; + } + } + + .react-aria-Button { + width: 1.75rem; + height: 1.75rem; + padding: 0; + } + + & table { + border-collapse: collapse; + + & td { + padding: 2px 0; + } + } + + .react-aria-CalendarCell { + width: 2.00025rem; + line-height: 2.00025rem; + text-align: center; + border-radius: 6px; + cursor: default; + outline: none; + forced-color-adjust: none; + + &[data-outside-month] { + display: none; + } + + &[data-pressed] { + background: var(--gray-100); + } + + &[data-focus-visible] { + outline: 2px solid var(--highlight-background); + outline-offset: -2px; + } + + &[data-selected] { + background: var(--highlight-background); + color: var(--highlight-foreground); + border-radius: 0; + + &[data-focus-visible] { + outline-color: var(--highlight-foreground); + outline-offset: -3px; + } + } + + &[data-selection-start] { + border-start-start-radius: 6px; + border-end-start-radius: 6px; + } + + &[data-selection-end] { + border-start-end-radius: 6px; + border-end-end-radius: 6px; + } + } + + .react-aria-CalendarCell { + &[data-disabled] { + color: var(--text-color-disabled); + } + } + + .react-aria-CalendarCell { + + &[data-unavailable] { + text-decoration: line-through; + color: var(--invalid-color); + } + + &[data-invalid] { + background: var(--invalid-color); + color: var(--highlight-foreground); + } + } + + [slot=errorMessage] { + font-size: 12px; + color: var(--invalid-color); + } +} diff --git a/starters/docs/src/RangeCalendar.tsx b/starters/docs/src/RangeCalendar.tsx new file mode 100644 index 00000000000..8a2debf9715 --- /dev/null +++ b/starters/docs/src/RangeCalendar.tsx @@ -0,0 +1,38 @@ +'use client'; +import { + CalendarCell, + CalendarGrid, + DateValue, + Heading, + RangeCalendar as AriaRangeCalendar, + RangeCalendarProps as AriaRangeCalendarProps, + Text +} from 'react-aria-components'; +import {Button} from './Button'; +import {ChevronLeft, ChevronRight} from 'lucide-react'; +import './RangeCalendar.css'; + +export interface RangeCalendarProps + extends AriaRangeCalendarProps { + errorMessage?: string; +} + +export function RangeCalendar( + { errorMessage, ...props }: RangeCalendarProps +) { + return ( + ( + +
+ + + +
+ + {(date) => } + + {errorMessage && {errorMessage}} +
+ ) + ); +} diff --git a/starters/docs/src/SearchField.css b/starters/docs/src/SearchField.css new file mode 100644 index 00000000000..5c0eb244280 --- /dev/null +++ b/starters/docs/src/SearchField.css @@ -0,0 +1,90 @@ +@import './Button.css'; +@import './Form.css'; +@import "./theme.css"; + +.react-aria-SearchField { + display: grid; + grid-template-areas: "label label" + "input button" + "help help"; + grid-template-columns: 1fr auto; + align-items: center; + width: fit-content; + color: var(--text-color); + + .react-aria-Input { + grid-area: input; + width: 100%; + padding: 0.25025rem 1.49975rem 0.25025rem 0.25025rem; + margin: 0; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--field-background); + font-size: 1.000125rem; + color: var(--field-text-color); + outline: none; + + &::-webkit-search-cancel-button, + &::-webkit-search-decoration { + -webkit-appearance: none; + } + + &::placeholder { + color: var(--text-color-placeholder); + opacity: 1; + } + + &[data-focused] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -1px; + } + } + + .react-aria-Button { + grid-area: button; + width: 1.000125rem; + height: 1.000125rem; + border-radius: 1.000125rem; + margin-left: -1.250375rem; + font-size: 0.749875rem; + line-height: 0.749875rem; + vertical-align: middle; + text-align: center; + background: var(--gray-500); + color: var(--gray-50); + border: none; + padding: 0; + + &[data-pressed] { + background: var(--gray-600); + } + } + + &[data-empty] button { + display: none; + } + + .react-aria-Input{ + &[data-invalid] { + border-color: var(--invalid-color); + } + } + + .react-aria-FieldError { + grid-area: help; + font-size: 12px; + color: var(--invalid-color); + } + + [slot=description] { + grid-area: help; + font-size: 12px; + } + + .react-aria-Input { + &[data-disabled] { + border-color: var(--border-color-disabled); + color: var(--text-color-disabled); + } + } +} diff --git a/starters/docs/src/SearchField.tsx b/starters/docs/src/SearchField.tsx new file mode 100644 index 00000000000..4497af15edc --- /dev/null +++ b/starters/docs/src/SearchField.tsx @@ -0,0 +1,36 @@ +'use client'; +import { + Button, + FieldError, + Input, + Label, + SearchField as AriaSearchField, + SearchFieldProps as AriaSearchFieldProps, + Text, + ValidationResult +} from 'react-aria-components'; +import {X} from 'lucide-react'; +import './SearchField.css'; + +export interface SearchFieldProps extends AriaSearchFieldProps { + label?: string; + description?: string; + errorMessage?: string | ((validation: ValidationResult) => string); + placeholder?: string; +} + +export function SearchField( + { label, description, errorMessage, placeholder, ...props }: SearchFieldProps +) { + return ( + ( + + {label && } + + + {description && {description}} + {errorMessage} + + ) + ); +} diff --git a/starters/docs/src/Select.css b/starters/docs/src/Select.css new file mode 100644 index 00000000000..12c5db76ce4 --- /dev/null +++ b/starters/docs/src/Select.css @@ -0,0 +1,149 @@ +@import './ListBox.css'; +@import './Popover.css'; +@import './Button.css'; +@import './Form.css'; +@import "./theme.css"; + +.react-aria-Select { + color: var(--text-color); + position: relative; + max-width: 250px; + width: fit-content; + + .react-aria-Button { + box-shadow: 0 1px 2px rgba(0 0 0 / 0.1); + border-radius: 6px; + font-size: 0.9380000000000001rem; + padding: 0.25025rem 0.25025rem 0.25025rem 0.499625rem; + display: flex; + align-items: center; + width: 100%; + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -1px; + } + } + + .react-aria-SelectValue { + flex: 1 0 auto; + text-align: start; + + &[data-placeholder] { + font-style: italic; + color: var(--text-color-placeholder); + } + } + + span[aria-hidden] { + width: 1.429rem; + height: 1.429rem; + line-height: 1.375rem; + margin-left: 1rem; + padding: 1px; + background: var(--highlight-background); + color: var(--highlight-foreground); + forced-color-adjust: none; + border-radius: 4px; + font-size: 0.857rem; + display: flex; + align-items: center; + justify-content: center; + } +} + +.react-aria-Popover[data-trigger=Select] { + min-width: var(--trigger-width); + + .react-aria-ListBox { + display: block; + width: unset; + max-height: inherit; + min-height: unset; + border: none; + + .react-aria-Header { + padding-left: 1.374625rem; + } + } + + .react-aria-ListBoxItem { + padding: 0 0.499625rem 0 1.374625rem; + + &[data-focus-visible] { + outline: none; + } + + &[data-selected] { + font-weight: 600; + background: unset; + color: var(--text-color); + + &::before { + content: '✓'; + content: '✓' / ''; + alt: ' '; + position: absolute; + top: 4px; + left: 4px; + } + } + + &[data-focused], + &[data-pressed] { + background: var(--highlight-background); + color: var(--highlight-foreground); + } + } +} + +.react-aria-ListBoxItem[href] { + text-decoration: none; + cursor: pointer; +} + +.react-aria-Select { + .react-aria-SelectValue { + [slot=description] { + display: none; + } + } + + .react-aria-Button { + &[data-disabled] { + border-color: var(--border-color-disabled); + color: var(--text-color-disabled); + span[aria-hidden] { + background: var(--border-color-disabled); + color: var(--text-color-disabled); + } + + .react-aria-SelectValue { + &[data-placeholder] { + color: var(--text-color-disabled); + } + } + } + } +} + +@media (forced-colors: active) { + .react-aria-Select { + .react-aria-Button { + &[data-disabled] span[aria-hidden] { + background: 0 0; + } + } + } +} + +.react-aria-Select { + .react-aria-FieldError { + font-size: 12px; + color: var(--invalid-color); + } + + [slot=description] { + font-size: 12px; + } +} diff --git a/starters/docs/src/Select.tsx b/starters/docs/src/Select.tsx new file mode 100644 index 00000000000..9b3175c0dab --- /dev/null +++ b/starters/docs/src/Select.tsx @@ -0,0 +1,54 @@ +'use client'; +import { + Button, + FieldError, + Label, + ListBoxItemProps, + Popover, + Select as AriaSelect, + SelectProps as AriaSelectProps, + SelectValue, + Text, + ValidationResult +} from 'react-aria-components'; +import {ListBox, ListBoxItem} from './ListBox'; +import {ChevronDown} from 'lucide-react'; +import './Select.css'; + +export interface SelectProps + extends Omit, 'children'> { + label?: string; + description?: string; + errorMessage?: string | ((validation: ValidationResult) => string); + items?: Iterable; + children: React.ReactNode | ((item: T) => React.ReactNode); +} + +export function Select( + { label, description, errorMessage, children, items, ...props }: SelectProps< + T + > +) { + return ( + ( + + + + {description && {description}} + {errorMessage} + + + {children} + + + + ) + ); +} + +export function SelectItem(props: ListBoxItemProps) { + return ; +} diff --git a/starters/docs/src/Slider.css b/starters/docs/src/Slider.css new file mode 100644 index 00000000000..cd8716f5696 --- /dev/null +++ b/starters/docs/src/Slider.css @@ -0,0 +1,106 @@ +@import './NumberField.css'; +@import "./theme.css"; + +.react-aria-Slider { + display: grid; + grid-template-areas: "label output" + "track track"; + grid-template-columns: 1fr auto; + max-width: 300px; + color: var(--text-color); + + .react-aria-Label { + grid-area: label; + } + + .react-aria-SliderOutput { + grid-area: output; + } + + .react-aria-SliderTrack { + grid-area: track; + position: relative; + + /* track line */ + &:before { + content: ''; + display: block; + position: absolute; + background: var(--border-color); + } + } + + .react-aria-SliderThumb { + width: 1.250375rem; + height: 1.250375rem; + border-radius: 50%; + background: var(--highlight-background); + border: 2px solid var(--background-color); + forced-color-adjust: none; + + &[data-dragging] { + background: var(--highlight-background-pressed); + } + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + } + } + + &[data-orientation=horizontal] { + flex-direction: column; + width: 300px; + + .react-aria-SliderTrack { + height: 30px; + width: 100%; + + &:before { + height: 3px; + width: 100%; + top: 50%; + transform: translateY(-50%); + } + } + + .react-aria-SliderThumb { + top: 50%; + } + } + + &[data-orientation=vertical] { + height: 150px; + display: block; + + .react-aria-Label, + .react-aria-SliderOutput { + display: none; + } + + .react-aria-SliderTrack { + width: 30px; + height: 100%; + + &:before { + width: 3px; + height: 100%; + left: 50%; + transform: translateX(-50%); + } + } + + .react-aria-SliderThumb { + left: 50%; + } + } + + &[data-disabled] { + .react-aria-SliderTrack:before { + background: var(--border-color-disabled); + } + + .react-aria-SliderThumb { + background: var(--border-color-disabled); + } + } +} diff --git a/starters/docs/src/Slider.tsx b/starters/docs/src/Slider.tsx new file mode 100644 index 00000000000..c2e813564cb --- /dev/null +++ b/starters/docs/src/Slider.tsx @@ -0,0 +1,38 @@ +'use client'; +import { + Label, + Slider as AriaSlider, + SliderOutput, + SliderProps as AriaSliderProps, + SliderThumb, + SliderTrack +} from 'react-aria-components'; + +import './Slider.css'; + +export interface SliderProps extends AriaSliderProps { + label?: string; + thumbLabels?: string[]; +} + +export function Slider( + { label, thumbLabels, ...props }: SliderProps +) { + return ( + ( + + {label && } + + {({ state }) => + state.values.map((_, i) => state.getThumbValueLabel(i)).join(' – ')} + + + {({ state }) => + state.values.map((_, i) => ( + + ))} + + + ) + ); +} diff --git a/starters/docs/src/Switch.css b/starters/docs/src/Switch.css new file mode 100644 index 00000000000..88dc3691a8a --- /dev/null +++ b/starters/docs/src/Switch.css @@ -0,0 +1,76 @@ +@import "./theme.css"; + +.react-aria-Switch { + display: flex; + /* This is needed so the HiddenInput is positioned correctly */ + position: relative; + align-items: center; + gap: 0.499625rem; + font-size: 1.000125rem; + color: var(--text-color); + forced-color-adjust: none; + + .indicator { + width: 1.75rem; + height: 1.000125rem; + border: 2px solid var(--border-color); + background: var(--background-color); + border-radius: 1.000125rem; + transition: all 200ms; + + &:before { + content: ''; + display: block; + margin: 0.125125rem; + width: 0.749875rem; + height: 0.749875rem; + background: var(--highlight-background); + border-radius: 16px; + transition: all 200ms; + } + } + + &[data-pressed] .indicator { + border-color: var(--border-color-pressed); + + &:before { + background: var(--highlight-background-pressed); + } + } + + &[data-selected] { + .indicator { + border-color: var(--highlight-background); + background: var(--highlight-background); + + &:before { + background: var(--field-background); + transform: translateX(100%); + } + } + + &[data-pressed] { + .indicator { + border-color: var(--highlight-background-pressed); + background: var(--highlight-background-pressed); + } + } + } + + &[data-focus-visible] .indicator { + outline: 2px solid var(--focus-ring-color); + outline-offset: 2px; + } + + &[data-disabled] { + color: var(--text-color-disabled); + + .indicator { + border-color: var(--border-color-disabled); + + &:before { + background: var(--border-color-disabled); + } + } + } +} diff --git a/starters/docs/src/Switch.tsx b/starters/docs/src/Switch.tsx new file mode 100644 index 00000000000..704b449a7b7 --- /dev/null +++ b/starters/docs/src/Switch.tsx @@ -0,0 +1,22 @@ +'use client'; +import { + Switch as AriaSwitch, + SwitchProps as AriaSwitchProps +} from 'react-aria-components'; + +import './Switch.css'; + +export interface SwitchProps extends Omit { + children: React.ReactNode; +} + +export function Switch({ children, ...props }: SwitchProps) { + return ( + ( + +
+ {children} + + ) + ); +} diff --git a/starters/docs/src/Table.css b/starters/docs/src/Table.css new file mode 100644 index 00000000000..24e496c3baf --- /dev/null +++ b/starters/docs/src/Table.css @@ -0,0 +1,277 @@ +@import './Button.css'; +@import './ToggleButton.css'; +@import './CheckboxGroup.css'; +@import './Checkbox.css'; +@import './Popover.css'; +@import './Menu.css'; +@import "./theme.css"; + +.react-aria-Table { + padding: 0.25025rem; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--overlay-background); + outline: none; + border-spacing: 0; + min-height: 100px; + align-self: start; + max-width: 100%; + word-break: break-word; + forced-color-adjust: none; + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -1px; + } + + .react-aria-TableHeader { + color: var(--text-color); + + &:after { + content: ''; + display: table-row; + height: 2px; + } + + & tr:last-child .react-aria-Column { + border-bottom: 1px solid var(--border-color); + cursor: default; + } + } + + .react-aria-Row { + --radius-top: 6px; + --radius-bottom: 6px; + --radius: var(--radius-top) var(--radius-top) var(--radius-bottom) var(--radius-bottom); + border-radius: var(--radius); + clip-path: inset(0 round var(--radius)); /* firefox */ + outline: none; + cursor: default; + color: var(--text-color); + font-size: 0.9380000000000001rem; + position: relative; + transform: scale(1); + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -2px; + } + + &[data-pressed] { + background: var(--gray-100); + } + + &[data-selected] { + background: var(--highlight-background); + color: var(--highlight-foreground); + --focus-ring-color: var(--highlight-foreground); + + &[data-focus-visible], + .react-aria-Cell[data-focus-visible] { + outline-offset: -4px; + } + } + + &[data-disabled] { + color: var(--text-color-disabled); + } + } + + .react-aria-Cell, + .react-aria-Column { + padding: 4px 8px; + text-align: left; + outline: none; + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -2px; + } + } + + .react-aria-Cell { + transform: translateZ(0); + + &:first-child { + border-radius: var(--radius-top) 0 0 var(--radius-bottom); + } + + &:last-child { + border-radius: 0 var(--radius-top) var(--radius-bottom) 0; + } + } + + /* join selected items if :has selector is supported */ + @supports selector(:has(.foo)) { + .react-aria-Row[data-selected]:has(+ [data-selected]), + .react-aria-Row[data-selected]:has(+ .react-aria-DropIndicator + [data-selected]) { + --radius-bottom: 0px; + } + + .react-aria-Row[data-selected] + [data-selected], + .react-aria-Row[data-selected] + .react-aria-DropIndicator + [data-selected]{ + --radius-top: 0px; + } + } +} + +:where(.react-aria-Row) .react-aria-Checkbox { + --selected-color: var(--highlight-foreground); + --selected-color-pressed: var(--highlight-foreground-pressed); + --checkmark-color: var(--highlight-background); + --background-color: var(--highlight-background); +} + +.react-aria-Row[data-href] { + cursor: pointer; +} + +.react-aria-Column { + .column-header { + display: flex; + align-items: center; + } + + .sort-indicator { + padding: 0 2px; + display: inline-flex; + align-items: center; + justify-content: center; + } + + &:not([data-sort-direction]) .sort-indicator { + visibility: hidden; + } +} + +.react-aria-TableBody { + &[data-empty] { + text-align: center; + font-style: italic; + } +} + +.react-aria-ResizableTableContainer { + max-width: 400px; + overflow: auto; + position: relative; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--background-color); + + .react-aria-Table { + border: none; + } + + .column-name, + .react-aria-Button { + --background-color: var(--overlay-background); + flex: 1; + font: inherit; + text-align: start; + color: inherit; + overflow: hidden; + text-overflow: ellipsis; + border-color: transparent; + transition: background 200ms; + &[data-hovered] { + background: var(--highlight-hover); + } + + &[data-pressed] { + background: var(--highlight-pressed); + box-shadow: none; + } + + &:focus-visible { + outline: 2px solid var(--focus-ring-color); + } + } + + .react-aria-ColumnResizer { + width: 15px; + background-color: grey; + height: 25px; + flex: 0 0 auto; + touch-action: none; + box-sizing: border-box; + border: 5px; + border-style: none solid; + border-color: transparent; + background-clip: content-box; + + &[data-resizable-direction=both] { + cursor: ew-resize; + } + + &[data-resizable-direction=left] { + cursor: e-resize; + } + + &[data-resizable-direction=right] { + cursor: w-resize; + } + + &[data-focus-visible] { + background-color: var(--focus-ring-color); + } + + &[data-resizing] { + border-color: var(--focus-ring-color); + background-color: transparent; + } + } + + .react-aria-Column, + .react-aria-Cell { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +} + +.react-aria-Row { + &[data-dragging] { + opacity: 0.6; + transform: translateZ(0); + } + + [slot=drag] { + all: unset; + width: 15px; + text-align: center; + + &[data-focus-visible] { + border-radius: 4px; + outline: 2px solid var(--focus-ring-color); + } + } +} + +.react-aria-DropIndicator[data-drop-target] { + outline: 1px solid var(--highlight-background); + transform: translateZ(0); +} + +.react-aria-Table[data-drop-target] { + outline: 2px solid var(--highlight-background); + outline-offset: -1px; + background: var(--highlight-overlay) +} + +.react-aria-Row[data-drop-target] { + outline: 2px solid var(--highlight-background); + background: var(--highlight-overlay) +} + +.react-aria-DropIndicator[data-drop-target] { + outline: 1px solid var(--highlight-background); + transform: translateZ(0); +} + +.react-aria-Cell img { + height: 30px; + width: 30px; + object-fit: cover; + display: block; +} diff --git a/starters/docs/src/Table.tsx b/starters/docs/src/Table.tsx new file mode 100644 index 00000000000..4fc31f6641a --- /dev/null +++ b/starters/docs/src/Table.tsx @@ -0,0 +1,92 @@ +'use client'; +import { + Button, + Cell, + Collection, + Column as AriaColumn, + ColumnProps, + Row as AriaRow, + RowProps, + Table as AriaTable, + TableHeader as AriaTableHeader, + TableHeaderProps, + TableProps, + useTableOptions +} from 'react-aria-components'; +import {Checkbox} from './Checkbox'; +import {ChevronUp, ChevronDown, GripVertical} from 'lucide-react'; +import './Table.css'; + +export function Table(props: TableProps) { + return ; +} + +export function Column( + props: Omit & { children?: React.ReactNode } +) { + return ( + ( + + {({ allowsSorting, sortDirection }) => ( +
+ {props.children} + {allowsSorting && ( + + )} +
+ )} +
+ ) + ); +} + +export function TableHeader( + { columns, children }: TableHeaderProps +) { + let { selectionBehavior, selectionMode, allowsDragging } = useTableOptions(); + + return ( + ( + + {/* Add extra columns for drag and drop and selection. */} + {allowsDragging && } + {selectionBehavior === 'toggle' && ( + + {selectionMode === 'multiple' && } + + )} + + {children} + + + ) + ); +} + +export function Row( + { id, columns, children, ...otherProps }: RowProps +) { + let { selectionBehavior, allowsDragging } = useTableOptions(); + + return ( + ( + + {allowsDragging && ( + + + + )} + {selectionBehavior === 'toggle' && ( + + + + )} + + {children} + + + ) + ); +} diff --git a/starters/docs/src/Tabs.css b/starters/docs/src/Tabs.css new file mode 100644 index 00000000000..c18b5f8f792 --- /dev/null +++ b/starters/docs/src/Tabs.css @@ -0,0 +1,102 @@ +@import './Button.css'; +@import './Link.css'; +@import "./theme.css"; + +.react-aria-Tabs { + display: flex; + color: var(--text-color); + + &[data-orientation=horizontal] { + flex-direction: column; + } +} + +.react-aria-TabList { + display: flex; + + &[data-orientation=horizontal] { + border-bottom: 1px solid var(--border-color); + + .react-aria-Tab { + border-bottom: 3px solid var(--border-color); + } + } +} + +.react-aria-Tab { + padding: 10px; + cursor: default; + outline: none; + position: relative; + color: var(--text-color-base); + transition: color 200ms; + --border-color: transparent; + forced-color-adjust: none; + + &[data-hovered], + &[data-focused] { + color: var(--text-color-hover); + } + + &[data-selected] { + --border-color: var(--highlight-background); + color: var(--text-color); + } + + &[data-disabled] { + color: var(--text-color-disabled); + &[data-selected] { + --border-color: var(--text-color-disabled); + } + } + + &[data-focus-visible]:after { + content: ''; + position: absolute; + inset: 4px; + border-radius: 4px; + border: 2px solid var(--focus-ring-color); + } +} + +.react-aria-TabPanel { + margin-top: 4px; + padding: 10px; + border-radius: 4px; + outline: none; + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + } +} + +.react-aria-Tabs { + &[data-orientation=vertical] { + flex-direction: row; + } +} + +.react-aria-TabList { + &[data-orientation=vertical] { + flex-direction: column; + border-inline-end: 1px solid gray; + + .react-aria-Tab { + border-inline-end: 3px solid var(--border-color, transparent); + } + } +} + +.react-aria-Tab { + &[data-disabled] { + color: var(--text-color-disabled); + &[data-selected] { + --border-color: var(--border-color-disabled); + } + } +} + +.react-aria-Tab[href] { + text-decoration: none; + cursor: pointer; +} diff --git a/starters/docs/src/Tabs.tsx b/starters/docs/src/Tabs.tsx new file mode 100644 index 00000000000..f5ed380baa0 --- /dev/null +++ b/starters/docs/src/Tabs.tsx @@ -0,0 +1,7 @@ +'use client'; +import {Tabs as RACTabs, TabsProps} from 'react-aria-components'; +import './Tabs.css'; + +export function Tabs(props: TabsProps) { + return ; +} diff --git a/starters/docs/src/TagGroup.css b/starters/docs/src/TagGroup.css new file mode 100644 index 00000000000..f65c51af854 --- /dev/null +++ b/starters/docs/src/TagGroup.css @@ -0,0 +1,102 @@ +@import './ToggleButton.css'; +@import "./theme.css"; + +.react-aria-TagGroup { + display: flex; + flex-direction: column; + gap: 2px; + font-size: small; + color: var(--text-color); +} + +.react-aria-TagList { + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +.react-aria-Tag { + color: var(--text-color); + border: 1px solid var(--border-color); + forced-color-adjust: none; + border-radius: 4px; + padding: 2px 8px; + font-size: 0.812875rem; + outline: none; + cursor: default; + display: flex; + align-items: center; + transition: border-color 200ms; + + &[data-hovered] { + border-color: var(--border-color-hover); + } + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: 2px; + } + + &[data-selected] { + border-color: var(--highlight-background); + background: var(--highlight-background); + color: var(--highlight-foreground); + } + + [slot=remove] { + background: none; + border: 1px solid var(--border-color); + padding: 0; + margin-left: 4px; + color: var(--text-color-base); + transition: color 200ms; + outline: none; + font-size: 0.95em; + border-radius: 100%; + aspect-ratio: 1/1; + height: calc(100% - 4px); + display: flex; + align-items: center; + justify-content: center; + + &[data-hovered] { + color: var(--text-color-hover); + } + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -1px; + } + } + + &[data-selected] { + [slot=remove] { + color: inherit; + } + } +} + +.react-aria-Tag[data-href] { + text-decoration: none; + cursor: pointer; +} + +.react-aria-TagList { + .react-aria-Tag { + &[data-disabled] { + border-color: var(--border-color-disabled); + color: var(--text-color-disabled); + } + } +} + +.react-aria-TagGroup { + [slot=description] { + font-size: 12px; + } + + [slot=errorMessage] { + font-size: 12px; + color: var(--invalid-color); + } +} diff --git a/starters/docs/src/TagGroup.tsx b/starters/docs/src/TagGroup.tsx new file mode 100644 index 00000000000..fb6a7058330 --- /dev/null +++ b/starters/docs/src/TagGroup.tsx @@ -0,0 +1,68 @@ +'use client'; +import { + Button, + Label, + Tag as AriaTag, + TagGroup as AriaTagGroup, + TagGroupProps as AriaTagGroupProps, + TagList, + TagListProps, + TagProps, + Text +} from 'react-aria-components'; +import {X} from 'lucide-react'; +import './TagGroup.css'; + +export interface TagGroupProps + extends + Omit, + Pick, 'items' | 'children' | 'renderEmptyState'> { + label?: string; + description?: string; + errorMessage?: string; +} + +export function TagGroup( + { + label, + description, + errorMessage, + items, + children, + renderEmptyState, + ...props + }: TagGroupProps +) { + return ( + ( + + + + {children} + + {description && {description}} + {errorMessage && {errorMessage}} + + ) + ); +} + +export function Tag( + { children, ...props }: Omit & { + children?: React.ReactNode; + } +) { + let textValue = typeof children === 'string' ? children : undefined; + return ( + ( + + {({ allowsRemoving }) => ( + <> + {children} + {allowsRemoving && } + + )} + + ) + ); +} diff --git a/starters/docs/src/TextField.css b/starters/docs/src/TextField.css new file mode 100644 index 00000000000..3ca4c45f624 --- /dev/null +++ b/starters/docs/src/TextField.css @@ -0,0 +1,50 @@ +@import './Button.css'; +@import './Form.css'; +@import "./theme.css"; + +.react-aria-TextField { + display: flex; + flex-direction: column; + width: fit-content; + color: var(--text-color); + + .react-aria-Input, + .react-aria-TextArea { + padding: 0.25025rem; + margin: 0; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--field-background); + font-size: 1.000125rem; + color: var(--field-text-color); + + &[data-focused] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -1px; + } + } + + .react-aria-Input, + .react-aria-TextArea { + &[data-invalid] { + border-color: var(--invalid-color); + } + } + + .react-aria-FieldError { + font-size: 12px; + color: var(--invalid-color); + } + + [slot=description] { + font-size: 12px; + } + + .react-aria-Input, + .react-aria-TextArea { + &[data-disabled] { + border-color: var(--border-color-disabled); + color: var(--text-color-disabled); + } + } +} diff --git a/starters/docs/src/TextField.tsx b/starters/docs/src/TextField.tsx new file mode 100644 index 00000000000..60a7914b6cc --- /dev/null +++ b/starters/docs/src/TextField.tsx @@ -0,0 +1,33 @@ +'use client'; +import { + FieldError, + Input, + Label, + Text, + TextField as AriaTextField, + TextFieldProps as AriaTextFieldProps, + ValidationResult +} from 'react-aria-components'; + +import './TextField.css'; + +export interface TextFieldProps extends AriaTextFieldProps { + label?: string; + description?: string; + errorMessage?: string | ((validation: ValidationResult) => string); +} + +export function TextField( + { label, description, errorMessage, ...props }: TextFieldProps +) { + return ( + ( + + + + {description && {description}} + {errorMessage} + + ) + ); +} diff --git a/starters/docs/src/TimeField.css b/starters/docs/src/TimeField.css new file mode 100644 index 00000000000..6d249142e8a --- /dev/null +++ b/starters/docs/src/TimeField.css @@ -0,0 +1,70 @@ +@import './Form.css'; +@import './Button.css'; +@import "./theme.css"; + +.react-aria-TimeField { + color: var(--text-color); + display: flex; + flex-direction: column; +} + +.react-aria-DateInput { + display: inline; + padding: 4px; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--field-background); + width: fit-content; + min-width: 150px; + white-space: nowrap; + forced-color-adjust: none; + + &[data-focus-within] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -1px; + } +} + +.react-aria-DateSegment { + padding: 0 2px; + font-variant-numeric: tabular-nums; + text-align: end; + color: var(--text-color); + + &[data-type=literal] { + padding: 0; + } + + &[data-placeholder] { + color: var(--text-color-placeholder); + font-style: italic; + } + + &:focus { + color: var(--highlight-foreground); + background: var(--highlight-background); + outline: none; + border-radius: 4px; + caret-color: transparent; + } + + &[data-invalid] { + color: var(--invalid-color); + + &:focus { + background: var(--highlight-background-invalid); + color: var(--highlight-foreground); + } + } +} + +.react-aria-TimeField { + .react-aria-FieldError { + font-size: 12px; + color: var(--invalid-color); + } + + [slot=description] { + font-size: 12px; + } +} diff --git a/starters/docs/src/TimeField.tsx b/starters/docs/src/TimeField.tsx new file mode 100644 index 00000000000..cd5529469c1 --- /dev/null +++ b/starters/docs/src/TimeField.tsx @@ -0,0 +1,38 @@ +'use client'; +import { + DateInput, + DateSegment, + FieldError, + Label, + Text, + TimeField as AriaTimeField, + TimeFieldProps as AriaTimeFieldProps, + TimeValue, + ValidationResult +} from 'react-aria-components'; + +import './TimeField.css'; + +export interface TimeFieldProps + extends AriaTimeFieldProps { + label?: string; + description?: string; + errorMessage?: string | ((validation: ValidationResult) => string); +} + +export function TimeField( + { label, description, errorMessage, ...props }: TimeFieldProps +) { + return ( + ( + + + + {(segment) => } + + {description && {description}} + {errorMessage} + + ) + ); +} diff --git a/starters/docs/src/ToggleButton.css b/starters/docs/src/ToggleButton.css new file mode 100644 index 00000000000..ab44aa03aae --- /dev/null +++ b/starters/docs/src/ToggleButton.css @@ -0,0 +1,47 @@ +@import "./theme.css"; + +.react-aria-ToggleButton { + color: var(--text-color); + background: var(--button-background); + border: 1px solid var(--border-color); + forced-color-adjust: none; + border-radius: 4px; + appearance: none; + vertical-align: middle; + font-size: 0.875rem; + text-align: center; + margin: 0; + outline: none; + padding: 6px 10px; + display: inline-flex; + align-items: center; + justify-content: center; + + &[data-pressed] { + box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1); + background: var(--button-background-pressed); + border-color: var(--border-color-pressed); + } + + &[data-selected] { + background: var(--highlight-background); + border-color: var(--highlight-background); + color: var(--highlight-foreground); + + &[data-pressed] { + background: var(--highlight-background-pressed); + border-color: var(--highlight-background-pressed); + } + } + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: 2px; + } + + &[data-disabled] { + border-color: var(--border-color-disabled); + background: var(--button-background); + color: var(--text-color-disabled); + } +} diff --git a/starters/docs/src/ToggleButton.tsx b/starters/docs/src/ToggleButton.tsx new file mode 100644 index 00000000000..cc8c4f666f2 --- /dev/null +++ b/starters/docs/src/ToggleButton.tsx @@ -0,0 +1,7 @@ +'use client'; +import {ToggleButton as RACToggleButton, ToggleButtonProps} from 'react-aria-components'; +import './ToggleButton.css'; + +export function ToggleButton(props: ToggleButtonProps) { + return ; +} diff --git a/starters/docs/src/ToggleButtonGroup.css b/starters/docs/src/ToggleButtonGroup.css new file mode 100644 index 00000000000..5f097ccdc9c --- /dev/null +++ b/starters/docs/src/ToggleButtonGroup.css @@ -0,0 +1,56 @@ +@import "./theme.css"; +@import './Button.css'; +@import './ToggleButton.css'; + +.react-aria-ToggleButtonGroup { + display: flex; + + > button { + border-radius: 0; + z-index: 1; + + &[data-disabled] { + z-index: 0; + } + + &[data-selected], + &[data-focus-visible] { + z-index: 2; + } + } +} + +.react-aria-ToggleButtonGroup[data-orientation=horizontal] { + flex-direction: row; + + > button { + margin-inline-start: -1px; + + &:first-child { + border-radius: 4px 0 0 4px; + margin-inline-start: 0; + } + + &:last-child { + border-radius: 0 4px 4px 0; + } + } +} + +.react-aria-ToggleButtonGroup[data-orientation=vertical] { + flex-direction: column; + width: fit-content; + + > button { + margin-block-start: -1px; + + &:first-child { + border-radius: 4px 4px 0 0; + margin-block-start: 0; + } + + &:last-child { + border-radius: 0 0 4px 4px; + } + } +} diff --git a/starters/docs/src/ToggleButtonGroup.tsx b/starters/docs/src/ToggleButtonGroup.tsx new file mode 100644 index 00000000000..390eea7f654 --- /dev/null +++ b/starters/docs/src/ToggleButtonGroup.tsx @@ -0,0 +1,7 @@ +'use client'; +import {ToggleButtonGroup as RACToggleButtonGroup, ToggleButtonGroupProps} from 'react-aria-components'; +import './ToggleButtonGroup.css'; + +export function ToggleButtonGroup(props: ToggleButtonGroupProps) { + return ; +} diff --git a/starters/docs/src/Toolbar.css b/starters/docs/src/Toolbar.css new file mode 100644 index 00000000000..7b8ea5bb7d6 --- /dev/null +++ b/starters/docs/src/Toolbar.css @@ -0,0 +1,50 @@ +@import './Checkbox.css'; +@import './Button.css'; +@import './ToggleButton.css'; +@import "./theme.css"; + +.react-aria-Toolbar { + display: flex; + flex-wrap: wrap; + gap: 5px; + + &[data-orientation=horizontal] { + flex-direction: row; + } + + .react-aria-Group { + display: contents; + } + + .react-aria-ToggleButton { + width: 32px; + } +} + +.react-aria-Separator { + align-self: stretch; + background-color: var(--border-color); + + &[aria-orientation=vertical] { + width: 1px; + margin: 0px 10px; + } +} + +.react-aria-Toolbar { + width: fit-content; + + &[data-orientation=vertical] { + flex-direction: column; + align-items: start; + } +} + +.react-aria-Separator { + &:not([aria-orientation=vertical]) { + border: none; + height: 1px; + width: 100%; + margin: 10px 0; + } +} diff --git a/starters/docs/src/Toolbar.tsx b/starters/docs/src/Toolbar.tsx new file mode 100644 index 00000000000..472fed91cc7 --- /dev/null +++ b/starters/docs/src/Toolbar.tsx @@ -0,0 +1,7 @@ +'use client'; +import {Toolbar as RACToolbar, ToolbarProps} from 'react-aria-components'; +import './Toolbar.css'; + +export function Toolbar(props: ToolbarProps) { + return ; +} diff --git a/starters/docs/src/Tooltip.css b/starters/docs/src/Tooltip.css new file mode 100644 index 00000000000..2999c7ab08d --- /dev/null +++ b/starters/docs/src/Tooltip.css @@ -0,0 +1,56 @@ +@import './Button.css'; +@import "./theme.css"; + +.react-aria-Tooltip { + box-shadow: 0 8px 20px rgba(0 0 0 / 0.1); + border-radius: 4px; + background: var(--highlight-background); + color: var(--highlight-foreground); + forced-color-adjust: none; + outline: none; + padding: 2px 8px; + max-width: 150px; + /* fixes FF gap */ + transform: translate3d(0, 0, 0); + transition: transform 200ms, opacity 200ms; + + &[data-entering], + &[data-exiting] { + transform: var(--origin); + opacity: 0; + } + + &[data-placement=top] { + margin-bottom: 8px; + --origin: translateY(4px); + } + + &[data-placement=bottom] { + margin-top: 8px; + --origin: translateY(-4px); + & .react-aria-OverlayArrow svg { + transform: rotate(180deg); + } + } + + &[data-placement=right] { + margin-left: 8px; + --origin: translateX(-4px); + & .react-aria-OverlayArrow svg { + transform: rotate(90deg); + } + } + + &[data-placement=left] { + margin-right: 8px; + --origin: translateX(4px); + & .react-aria-OverlayArrow svg { + transform: rotate(-90deg); + } + } + + & .react-aria-OverlayArrow svg { + display: block; + fill: var(--highlight-background); + } +} diff --git a/starters/docs/src/Tooltip.tsx b/starters/docs/src/Tooltip.tsx new file mode 100644 index 00000000000..655f709e952 --- /dev/null +++ b/starters/docs/src/Tooltip.tsx @@ -0,0 +1,27 @@ +'use client'; +import { + OverlayArrow, + Tooltip as AriaTooltip, + TooltipProps as AriaTooltipProps +} from 'react-aria-components'; + +import './Tooltip.css'; + +export interface TooltipProps extends Omit { + children: React.ReactNode; +} + +export function Tooltip({ children, ...props }: TooltipProps) { + return ( + ( + + + + + + + {children} + + ) + ); +} diff --git a/starters/docs/src/Tree.css b/starters/docs/src/Tree.css new file mode 100644 index 00000000000..894d8116d01 --- /dev/null +++ b/starters/docs/src/Tree.css @@ -0,0 +1,201 @@ +@import "./theme.css"; +@import './Button.css'; +@import './ToggleButton.css'; +@import './Checkbox.css'; + +.react-aria-Tree { + display: flex; + flex-direction: column; + gap: 2px; + overflow: auto; + padding: 4px; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--overlay-background); + forced-color-adjust: none; + outline: none; + width: 250px; + max-height: 300px; + box-sizing: border-box; + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -1px; + } + + .react-aria-TreeItem { + display: flex; + align-items: center; + gap: 0.499625rem; + min-height: 28px; + padding: 0.25025rem 0.25025rem 0.25025rem 0.499625rem; + --padding: 16px; + border-radius: 6px; + outline: none; + cursor: default; + color: var(--text-color); + font-size: 0.9380000000000001rem; + position: relative; + transform: translateZ(0); + + .react-aria-Button[slot=chevron] { + all: unset; + display: flex; + visibility: hidden; + align-items: center; + justify-content: center; + width: 1.1375rem; + height: 100%; + padding-left: calc((var(--tree-item-level) - 1) * var(--padding)); + + svg { + rotate: 0deg; + transition: rotate 200ms; + fill: none; + stroke: currentColor; + stroke-width: 3px; + } + } + + &[data-has-child-items] .react-aria-Button[slot=chevron] { + visibility: visible; + } + + &[data-expanded] .react-aria-Button[slot=chevron] svg { + rotate: 90deg; + } + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -2px; + } + + &[data-pressed] { + background: var(--gray-100); + } + + &[data-selected] { + background: var(--highlight-background); + color: var(--highlight-foreground); + --focus-ring-color: var(--highlight-foreground); + + &[data-focus-visible] { + outline-color: var(--highlight-foreground); + outline-offset: -4px; + } + + .react-aria-Button { + color: var(--highlight-foreground); + --highlight-hover: rgb(255 255 255 / 0.1); + --highlight-pressed: rgb(255 255 255 / 0.2); + } + } + + &[data-disabled] { + color: var(--text-color-disabled); + } + + .react-aria-Button:not([slot]) { + margin-left: auto; + background: transparent; + border: none; + font-size: 1.05rem; + line-height: 1.2em; + padding: 0.25025rem 0.375375rem; + transition: background 200ms; + + &[data-hovered] { + background: var(--highlight-hover); + } + + &[data-pressed] { + background: var(--highlight-pressed); + box-shadow: none; + } + } + } + + /* join selected items if :has selector is supported */ + @supports selector(:has(.foo)) { + gap: 0; + + .react-aria-TreeItem[data-selected]:has(+ [data-selected]) { + border-end-start-radius: 0; + border-end-end-radius: 0; + } + + .react-aria-TreeItem[data-selected] + [data-selected] { + border-start-start-radius: 0; + border-start-end-radius: 0; + } + } + + :where(.react-aria-TreeItem) .react-aria-Checkbox { + --selected-color: var(--highlight-foreground); + --selected-color-pressed: var(--highlight-foreground-pressed); + --checkmark-color: var(--highlight-background); + --background-color: var(--highlight-background); + } +} + +.react-aria-TreeItem[data-href] { + cursor: pointer; +} + +.react-aria-Tree { + &[data-empty] { + display: flex; + align-items: center; + justify-content: center; + font-style: italic; + } +} + +.react-aria-TreeItem { + &[data-allows-dragging] { + padding-left: 4px; + } + + &[data-dragging] { + opacity: 0.6; + } + + &[data-drop-target] { + outline: 2px solid var(--highlight-background); + background: var(--highlight-overlay); + } + + [slot=drag] { + all: unset; + width: 15px; + text-align: center; + + &[data-focus-visible] { + border-radius: 4px; + outline: 2px solid var(--focus-ring-color); + } + } +} + +.react-aria-Tree { + &[data-selection-mode=multiple] { + --checkbox-width: 28px; + } + + &[data-allows-dragging] { + --drag-button-width: 23px; + } + + .react-aria-DropIndicator { + &[data-drop-target] { + outline: 1px solid var(--highlight-background); + margin-left: calc(8px + var(--checkbox-width, 0px) + var(--drag-button-width, 0px) + 26px + (var(--tree-item-level) - 1) * 16px); + } + } +} + +.react-aria-Tree[data-drop-target] { + outline: 2px solid var(--highlight-background); + outline-offset: -1px; + background: var(--highlight-overlay); +} diff --git a/starters/docs/src/Tree.tsx b/starters/docs/src/Tree.tsx new file mode 100644 index 00000000000..9c42d110da6 --- /dev/null +++ b/starters/docs/src/Tree.tsx @@ -0,0 +1,63 @@ +'use client'; +import { + Button, + Tree as AriaTree, + TreeItem as AriaTreeItem, + TreeItemContent as AriaTreeItemContent, + TreeItemContentProps, + TreeItemContentRenderProps, + TreeItemProps as AriaTreeItemProps, + TreeProps +} from 'react-aria-components'; +import {ChevronRight} from 'lucide-react'; + +import {Checkbox} from './Checkbox'; + +import './Tree.css'; + +export function Tree(props: TreeProps) { + return ; +} + +export function TreeItemContent( + props: Omit & { children?: React.ReactNode } +) { + return ( + ( + + {( + { selectionBehavior, selectionMode, allowsDragging }: + TreeItemContentRenderProps + ) => ( + <> + {allowsDragging && } + {selectionBehavior === 'toggle' && selectionMode !== 'none' && ( + + )} + + {props.children} + + )} + + ) + ); +} + +export interface TreeItemProps extends Partial { + title: string; +} + +export function TreeItem(props: TreeItemProps) { + return ( + ( + + + {props.title} + + {props.children} + + ) + ); +} diff --git a/starters/docs/src/theme.css b/starters/docs/src/theme.css new file mode 100644 index 00000000000..e754f609ef3 --- /dev/null +++ b/starters/docs/src/theme.css @@ -0,0 +1,118 @@ +/* color themes for dark and light modes, generated with Leonardo. + * Light: https://leonardocolor.io/theme.html?name=Light&config=%7B%22baseScale%22%3A%22Gray%22%2C%22colorScales%22%3A%5B%7B%22name%22%3A%22Gray%22%2C%22colorKeys%22%3A%5B%22%23000000%22%5D%2C%22colorspace%22%3A%22RGB%22%2C%22ratios%22%3A%5B%22-1.12%22%2C%221.45%22%2C%222.05%22%2C%223.02%22%2C%224.54%22%2C%227%22%2C%2210.86%22%5D%2C%22smooth%22%3Afalse%7D%2C%7B%22name%22%3A%22Purple%22%2C%22colorKeys%22%3A%5B%22%235e30eb%22%5D%2C%22colorspace%22%3A%22RGB%22%2C%22ratios%22%3A%5B%22-1.12%22%2C%221.45%22%2C%222.05%22%2C%223.02%22%2C%224.54%22%2C%227%22%2C%2210.86%22%5D%2C%22smooth%22%3Afalse%7D%2C%7B%22name%22%3A%22Red%22%2C%22colorKeys%22%3A%5B%22%23e32400%22%5D%2C%22colorspace%22%3A%22RGB%22%2C%22ratios%22%3A%5B%22-1.12%22%2C%221.45%22%2C%222.05%22%2C%223.02%22%2C%224.54%22%2C%227%22%2C%2210.86%22%5D%2C%22smooth%22%3Afalse%7D%5D%2C%22lightness%22%3A98%2C%22contrast%22%3A1%2C%22saturation%22%3A100%2C%22formula%22%3A%22wcag2%22%7D */ +:root { + --background-color: #f8f8f8; + --gray-50: #ffffff; + --gray-100: #d0d0d0; + --gray-200: #afafaf; + --gray-300: #8f8f8f; + --gray-400: #717171; + --gray-500: #555555; + --gray-600: #393939; + --purple-100: #d5c9fa; + --purple-200: #b8a3f6; + --purple-300: #997cf2; + --purple-400: #7a54ef; + --purple-500: #582ddc; + --purple-600: #3c1e95; + --red-100: #f7c4ba; + --red-200: #f29887; + --red-300: #eb664d; + --red-400: #de2300; + --red-500: #a81b00; + --red-600: #731200; + --highlight-hover: rgb(0 0 0 / 0.07); + --highlight-pressed: rgb(0 0 0 / 0.15); +} + +/* Dark: https://leonardocolor.io/theme.html?name=Dark&config=%7B%22baseScale%22%3A%22Gray%22%2C%22colorScales%22%3A%5B%7B%22name%22%3A%22Gray%22%2C%22colorKeys%22%3A%5B%22%23000000%22%5D%2C%22colorspace%22%3A%22RGB%22%2C%22ratios%22%3A%5B%22-1.12%22%2C%221.45%22%2C%222.05%22%2C%223.02%22%2C%224.54%22%2C%227%22%2C%2210.86%22%5D%2C%22smooth%22%3Afalse%7D%2C%7B%22name%22%3A%22Purple%22%2C%22colorKeys%22%3A%5B%22%235e30eb%22%5D%2C%22colorspace%22%3A%22RGB%22%2C%22ratios%22%3A%5B%22-1.12%22%2C%221.45%22%2C%222.05%22%2C%223.02%22%2C%224.54%22%2C%227%22%2C%2210.86%22%5D%2C%22smooth%22%3Afalse%7D%2C%7B%22name%22%3A%22Red%22%2C%22colorKeys%22%3A%5B%22%23e32400%22%5D%2C%22colorspace%22%3A%22RGB%22%2C%22ratios%22%3A%5B%22-1.12%22%2C%221.45%22%2C%222.05%22%2C%223.02%22%2C%224.54%22%2C%227%22%2C%2210.86%22%5D%2C%22smooth%22%3Afalse%7D%5D%2C%22lightness%22%3A11%2C%22contrast%22%3A1%2C%22saturation%22%3A100%2C%22formula%22%3A%22wcag2%22%7D */ +@media (prefers-color-scheme: dark) { + :root { + --background-color: #1d1d1d; + --gray-50: #101010; + --gray-100: #393939; + --gray-200: #4f4f4f; + --gray-300: #686868; + --gray-400: #848484; + --gray-500: #a7a7a7; + --gray-600: #cfcfcf; + --purple-100: #3c1e95; + --purple-200: #522acd; + --purple-300: #6f46ed; + --purple-400: #8e6ef1; + --purple-500: #b099f5; + --purple-600: #d5c8fa; + --red-100: #721200; + --red-200: #9c1900; + --red-300: #cc2000; + --red-400: #e95034; + --red-500: #f08c79; + --red-600: #f7c3ba; + --highlight-hover: rgb(255 255 255 / 0.1); + --highlight-pressed: rgb(255 255 255 / 0.2); + } +} + +/* Semantic colors */ +:root { + --focus-ring-color: var(--purple-400); + --text-color: var(--gray-600); + --text-color-base: var(--gray-500); + --text-color-hover: var(--gray-600); + --text-color-disabled: var(--gray-200); + --text-color-placeholder: var(--gray-400); + --link-color: var(--purple-500); + --link-color-secondary: var(--gray-500); + --link-color-pressed: var(--purple-600); + --border-color: var(--gray-300); + --border-color-hover: var(--gray-400); + --border-color-pressed: var(--gray-400); + --border-color-disabled: var(--gray-100); + --field-background: var(--gray-50); + --field-text-color: var(--gray-600); + --overlay-background: var(--gray-50); + --button-background: var(--gray-50); + --button-background-pressed: var(--background-color); + /* these colors are the same between light and dark themes + * to ensure contrast with the foreground color */ + --highlight-background: #6f46ed; /* purple-300 from dark theme, 3.03:1 against background-color */ + --highlight-background-pressed: #522acd; /* purple-200 from dark theme */ + --highlight-background-invalid: #cc2000; /* red-300 from dark theme */ + --highlight-foreground: white; /* 5.56:1 against highlight-background */ + --highlight-foreground-pressed: #ddd; + --highlight-overlay: rgb(from #6f46ed r g b / 15%); + --invalid-color: var(--red-400); + --invalid-color-pressed: var(--red-500); +} + +/* Windows high contrast mode overrides */ +@media (forced-colors: active) { + :root { + --background-color: Canvas; + --focus-ring-color: Highlight; + --text-color: ButtonText; + --text-color-base: ButtonText; + --text-color-hover: ButtonText; + --text-color-disabled: GrayText; + --text-color-placeholder: ButtonText; + --link-color: LinkText; + --link-color-secondary: LinkText; + --link-color-pressed: LinkText; + --border-color: ButtonBorder; + --border-color-hover: ButtonBorder; + --border-color-pressed: ButtonBorder; + --border-color-disabled: GrayText; + --field-background: Field; + --field-text-color: FieldText; + --overlay-background: Canvas; + --button-background: ButtonFace; + --button-background-pressed: ButtonFace; + --highlight-background: Highlight; + --highlight-background-pressed: Highlight; + --highlight-background-invalid: LinkText; + --highlight-foreground: HighlightText; + --highlight-foreground-pressed: HighlightText; + --invalid-color: LinkText; + --invalid-color-pressed: LinkText; + } +} diff --git a/starters/docs/stories/Autocomplete.stories.tsx b/starters/docs/stories/Autocomplete.stories.tsx new file mode 100644 index 00000000000..50be16b1693 --- /dev/null +++ b/starters/docs/stories/Autocomplete.stories.tsx @@ -0,0 +1,33 @@ +import {Autocomplete} from '../src/Autocomplete'; +import {MenuItem} from 'react-aria-components'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Autocomplete, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ( + + Create new file... + Create new folder... + Assign to... + Assign to me + Change status... + Change priority... + Add label... + Remove label... + +); + +Example.args = { + label: 'Commands', + placeholder: 'Search commands...' +}; diff --git a/starters/docs/stories/Breadcrumbs.stories.tsx b/starters/docs/stories/Breadcrumbs.stories.tsx new file mode 100644 index 00000000000..60fe92a1774 --- /dev/null +++ b/starters/docs/stories/Breadcrumbs.stories.tsx @@ -0,0 +1,29 @@ +import {Breadcrumbs} from '../src/Breadcrumbs'; +import {Breadcrumb, Link} from 'react-aria-components'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Breadcrumbs, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ( + + + Home + + + React Aria + + + Breadcrumbs + + +); diff --git a/starters/docs/stories/Button.stories.tsx b/starters/docs/stories/Button.stories.tsx new file mode 100644 index 00000000000..5423b6945a2 --- /dev/null +++ b/starters/docs/stories/Button.stories.tsx @@ -0,0 +1,20 @@ +import {Button} from '../src/Button'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Button, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ; + +Example.args = { + onPress: () => alert('Hello world!') +}; diff --git a/starters/docs/stories/Calendar.stories.tsx b/starters/docs/stories/Calendar.stories.tsx new file mode 100644 index 00000000000..22270e02ef5 --- /dev/null +++ b/starters/docs/stories/Calendar.stories.tsx @@ -0,0 +1,18 @@ +import {Calendar} from '../src/Calendar'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Calendar, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ( + +); diff --git a/starters/docs/stories/Checkbox.stories.tsx b/starters/docs/stories/Checkbox.stories.tsx new file mode 100644 index 00000000000..5cca1c3e98f --- /dev/null +++ b/starters/docs/stories/Checkbox.stories.tsx @@ -0,0 +1,17 @@ +import {Checkbox} from '../src/Checkbox'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Checkbox, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => Unsubscribe +; diff --git a/starters/docs/stories/CheckboxGroup.stories.tsx b/starters/docs/stories/CheckboxGroup.stories.tsx new file mode 100644 index 00000000000..b90361a84cc --- /dev/null +++ b/starters/docs/stories/CheckboxGroup.stories.tsx @@ -0,0 +1,27 @@ +import {Checkbox} from '../src/Checkbox'; +import {CheckboxGroup} from '../src/CheckboxGroup'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: CheckboxGroup, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ( + + Soccer + Baseball + Basketball + +); + +Example.args = { + label: 'Favorite sports' +}; diff --git a/starters/docs/stories/ColorArea.stories.tsx b/starters/docs/stories/ColorArea.stories.tsx new file mode 100644 index 00000000000..281b76906f6 --- /dev/null +++ b/starters/docs/stories/ColorArea.stories.tsx @@ -0,0 +1,20 @@ +import {ColorArea} from '../src/ColorArea'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: ColorArea, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ; + +Example.args = { + defaultValue: 'hsl(30, 100%, 50%)' +}; diff --git a/starters/docs/stories/ColorField.stories.tsx b/starters/docs/stories/ColorField.stories.tsx new file mode 100644 index 00000000000..e0d23fe052f --- /dev/null +++ b/starters/docs/stories/ColorField.stories.tsx @@ -0,0 +1,20 @@ +import {ColorField} from '../src/ColorField'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: ColorField, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ; + +Example.args = { + label: 'Color' +}; diff --git a/starters/docs/stories/ColorPicker.stories.tsx b/starters/docs/stories/ColorPicker.stories.tsx new file mode 100644 index 00000000000..2f78d7309e7 --- /dev/null +++ b/starters/docs/stories/ColorPicker.stories.tsx @@ -0,0 +1,21 @@ +import {ColorPicker} from '../src/ColorPicker'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: ColorPicker, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ; + +Example.args = { + label: 'Fill color', + defaultValue: '#f00' +}; diff --git a/starters/docs/stories/ColorSlider.stories.tsx b/starters/docs/stories/ColorSlider.stories.tsx new file mode 100644 index 00000000000..9d92f666e16 --- /dev/null +++ b/starters/docs/stories/ColorSlider.stories.tsx @@ -0,0 +1,22 @@ +import {ColorSlider} from '../src/ColorSlider'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: ColorSlider, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ; + +Example.args = { + label: 'Red Opacity', + defaultValue: '#f00', + channel: 'alpha' +}; diff --git a/starters/docs/stories/ColorSwatch.stories.tsx b/starters/docs/stories/ColorSwatch.stories.tsx new file mode 100644 index 00000000000..2c43b0971db --- /dev/null +++ b/starters/docs/stories/ColorSwatch.stories.tsx @@ -0,0 +1,20 @@ +import {ColorSwatch} from '../src/ColorSwatch'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: ColorSwatch, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ; + +Example.args = { + color: '#f00a' +}; diff --git a/starters/docs/stories/ColorSwatchPicker.stories.tsx b/starters/docs/stories/ColorSwatchPicker.stories.tsx new file mode 100644 index 00000000000..0f84c337666 --- /dev/null +++ b/starters/docs/stories/ColorSwatchPicker.stories.tsx @@ -0,0 +1,28 @@ +import { + ColorSwatchPicker, + ColorSwatchPickerItem +} from '../src/ColorSwatchPicker'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: ColorSwatchPicker, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ( + + + + + + + + +); diff --git a/starters/docs/stories/ColorWheel.stories.tsx b/starters/docs/stories/ColorWheel.stories.tsx new file mode 100644 index 00000000000..6fd057b02bf --- /dev/null +++ b/starters/docs/stories/ColorWheel.stories.tsx @@ -0,0 +1,20 @@ +import {ColorWheel} from '../src/ColorWheel'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: ColorWheel, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ; + +Example.args = { + defaultValue: 'hsl(30, 100%, 50%)' +}; diff --git a/starters/docs/stories/ComboBox.stories.tsx b/starters/docs/stories/ComboBox.stories.tsx new file mode 100644 index 00000000000..14d645e3dd8 --- /dev/null +++ b/starters/docs/stories/ComboBox.stories.tsx @@ -0,0 +1,27 @@ +import {ComboBox, ComboBoxItem} from '../src/ComboBox'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: ComboBox, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ( + + Chocolate + Mint + Strawberry + Vanilla + +); + +Example.args = { + label: 'Ice cream flavor' +}; diff --git a/starters/docs/stories/DateField.stories.tsx b/starters/docs/stories/DateField.stories.tsx new file mode 100644 index 00000000000..e3b56fc7d9a --- /dev/null +++ b/starters/docs/stories/DateField.stories.tsx @@ -0,0 +1,20 @@ +import {DateField} from '../src/DateField'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: DateField, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ; + +Example.args = { + label: 'Event date' +}; diff --git a/starters/docs/stories/DatePicker.stories.tsx b/starters/docs/stories/DatePicker.stories.tsx new file mode 100644 index 00000000000..377b4cda13a --- /dev/null +++ b/starters/docs/stories/DatePicker.stories.tsx @@ -0,0 +1,20 @@ +import {DatePicker} from '../src/DatePicker'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: DatePicker, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ; + +Example.args = { + label: 'Event date' +}; diff --git a/starters/docs/stories/DateRangePicker.stories.tsx b/starters/docs/stories/DateRangePicker.stories.tsx new file mode 100644 index 00000000000..f6c28eed9cb --- /dev/null +++ b/starters/docs/stories/DateRangePicker.stories.tsx @@ -0,0 +1,20 @@ +import {DateRangePicker} from '../src/DateRangePicker'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: DateRangePicker, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ; + +Example.args = { + label: 'Event date' +}; diff --git a/starters/docs/stories/Dialog.stories.tsx b/starters/docs/stories/Dialog.stories.tsx new file mode 100644 index 00000000000..6b6c068a0e9 --- /dev/null +++ b/starters/docs/stories/Dialog.stories.tsx @@ -0,0 +1,47 @@ +import {Dialog} from '../src/Dialog'; +import { + Button, + DialogTrigger, + Heading, + Input, + Label, + Modal, + TextField +} from 'react-aria-components'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Dialog, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ( + + + + +
+ Sign up + + + + + + + + + +
+
+
+
+); diff --git a/starters/docs/stories/Disclosure.stories.tsx b/starters/docs/stories/Disclosure.stories.tsx new file mode 100644 index 00000000000..d3e0513c63e --- /dev/null +++ b/starters/docs/stories/Disclosure.stories.tsx @@ -0,0 +1,22 @@ +import {Disclosure} from '../src/Disclosure'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Disclosure, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ( + Details on managing your account +); + +Example.args = { + title: 'Manage your account' +}; diff --git a/starters/docs/stories/DisclosureGroup.stories.tsx b/starters/docs/stories/DisclosureGroup.stories.tsx new file mode 100644 index 00000000000..899d6e61f76 --- /dev/null +++ b/starters/docs/stories/DisclosureGroup.stories.tsx @@ -0,0 +1,30 @@ +import {DisclosureGroup} from '../src/DisclosureGroup'; +import {Disclosure} from '../src/Disclosure'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: DisclosureGroup, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ( + + +

Personal information form here.

+
+ +

Billing address form here.

+
+
+); + +Example.args = { + defaultExpandedKeys: ['personal'] +}; diff --git a/starters/docs/stories/Form.stories.tsx b/starters/docs/stories/Form.stories.tsx new file mode 100644 index 00000000000..768135b815e --- /dev/null +++ b/starters/docs/stories/Form.stories.tsx @@ -0,0 +1,32 @@ +import {Form} from '../src/Form'; +import { + Button, + FieldError, + Input, + Label, + TextField +} from 'react-aria-components'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Form, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ( +
+ + + + + + +
+); diff --git a/starters/docs/stories/GridList.stories.tsx b/starters/docs/stories/GridList.stories.tsx new file mode 100644 index 00000000000..64289f608d0 --- /dev/null +++ b/starters/docs/stories/GridList.stories.tsx @@ -0,0 +1,28 @@ +import {GridList, GridListItem} from '../src/GridList'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: GridList, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ( + + Chocolate + Mint + Strawberry + Vanilla + +); + +Example.args = { + onAction: undefined, + selectionMode: 'multiple' +}; diff --git a/starters/docs/stories/Link.stories.tsx b/starters/docs/stories/Link.stories.tsx new file mode 100644 index 00000000000..bda0f166bba --- /dev/null +++ b/starters/docs/stories/Link.stories.tsx @@ -0,0 +1,21 @@ +import {Link} from '../src/Link'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Link, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => The missing link; + +Example.args = { + href: 'https://www.imdb.com/title/tt6348138/', + target: '_blank' +}; diff --git a/starters/docs/stories/ListBox.stories.tsx b/starters/docs/stories/ListBox.stories.tsx new file mode 100644 index 00000000000..565072638f5 --- /dev/null +++ b/starters/docs/stories/ListBox.stories.tsx @@ -0,0 +1,28 @@ +import {ListBox, ListBoxItem} from '../src/ListBox'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: ListBox, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ( + + Chocolate + Mint + Strawberry + Vanilla + +); + +Example.args = { + onAction: undefined, + selectionMode: 'single' +}; diff --git a/starters/docs/stories/Menu.stories.tsx b/starters/docs/stories/Menu.stories.tsx new file mode 100644 index 00000000000..cc321ee85a0 --- /dev/null +++ b/starters/docs/stories/Menu.stories.tsx @@ -0,0 +1,24 @@ +import {MenuButton, MenuItem} from '../src/Menu'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: MenuButton, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ( + + Cut + Copy + Paste + +); + +Example.args = {}; diff --git a/starters/docs/stories/Meter.stories.tsx b/starters/docs/stories/Meter.stories.tsx new file mode 100644 index 00000000000..7f1dbdab198 --- /dev/null +++ b/starters/docs/stories/Meter.stories.tsx @@ -0,0 +1,21 @@ +import {Meter} from '../src/Meter'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Meter, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ; + +Example.args = { + label: 'Storage space', + value: 80 +}; diff --git a/starters/docs/stories/Modal.stories.tsx b/starters/docs/stories/Modal.stories.tsx new file mode 100644 index 00000000000..a07633822aa --- /dev/null +++ b/starters/docs/stories/Modal.stories.tsx @@ -0,0 +1,47 @@ +import {Modal} from '../src/Modal'; +import { + Button, + Dialog, + DialogTrigger, + Heading, + Input, + Label, + TextField +} from 'react-aria-components'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Modal, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ( + + + + +
+ Sign up + + + + + + + + + +
+
+
+
+); diff --git a/starters/docs/stories/NumberField.stories.tsx b/starters/docs/stories/NumberField.stories.tsx new file mode 100644 index 00000000000..4467c5bdb10 --- /dev/null +++ b/starters/docs/stories/NumberField.stories.tsx @@ -0,0 +1,20 @@ +import {NumberField} from '../src/NumberField'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: NumberField, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ; + +Example.args = { + label: 'Cookies' +}; diff --git a/starters/docs/stories/Popover.stories.tsx b/starters/docs/stories/Popover.stories.tsx new file mode 100644 index 00000000000..091b0a98bda --- /dev/null +++ b/starters/docs/stories/Popover.stories.tsx @@ -0,0 +1,27 @@ +import {Popover} from '../src/Popover'; +import {Button} from '../src/Button'; +import {DialogTrigger, Heading} from 'react-aria-components'; +import {HelpCircle} from 'lucide-react'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Popover, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ( + + + + Help +

For help accessing your account, please contact support.

+
+
+); diff --git a/starters/docs/stories/ProgressBar.stories.tsx b/starters/docs/stories/ProgressBar.stories.tsx new file mode 100644 index 00000000000..4c1a817bebb --- /dev/null +++ b/starters/docs/stories/ProgressBar.stories.tsx @@ -0,0 +1,21 @@ +import {ProgressBar} from '../src/ProgressBar'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: ProgressBar, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => ; + +Example.args = { + label: 'Loading…', + value: 80 +}; diff --git a/starters/docs/stories/RadioGroup.stories.tsx b/starters/docs/stories/RadioGroup.stories.tsx new file mode 100644 index 00000000000..7532b387553 --- /dev/null +++ b/starters/docs/stories/RadioGroup.stories.tsx @@ -0,0 +1,28 @@ +import {RadioGroup} from '../src/RadioGroup'; +import {Radio} from 'react-aria-components'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: RadioGroup, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +type Story = StoryFn; + +export const Example: Story = (args) => ( + + Soccer + Baseball + Basketball + +); + +Example.args = { + label: 'Favorite sport' +}; diff --git a/starters/docs/stories/RangeCalendar.stories.tsx b/starters/docs/stories/RangeCalendar.stories.tsx new file mode 100644 index 00000000000..d124c8d0453 --- /dev/null +++ b/starters/docs/stories/RangeCalendar.stories.tsx @@ -0,0 +1,19 @@ +import {RangeCalendar} from '../src/RangeCalendar'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: RangeCalendar, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +type Story = StoryFn; + +export const Example: Story = (args) => ( + +); diff --git a/starters/docs/stories/SearchField.stories.tsx b/starters/docs/stories/SearchField.stories.tsx new file mode 100644 index 00000000000..923155298b5 --- /dev/null +++ b/starters/docs/stories/SearchField.stories.tsx @@ -0,0 +1,21 @@ +import {SearchField} from '../src/SearchField'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: SearchField, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +type Story = StoryFn; + +export const Example: Story = (args) => ; + +Example.args = { + label: 'Search' +}; diff --git a/starters/docs/stories/Select.stories.tsx b/starters/docs/stories/Select.stories.tsx new file mode 100644 index 00000000000..e24cfd939c5 --- /dev/null +++ b/starters/docs/stories/Select.stories.tsx @@ -0,0 +1,28 @@ +import {Select, SelectItem} from '../src/Select'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Select, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +type Story = StoryFn; + +export const Example: Story = (args) => ( + +); + +Example.args = { + label: 'Ice cream flavor' +}; diff --git a/starters/docs/stories/Slider.stories.tsx b/starters/docs/stories/Slider.stories.tsx new file mode 100644 index 00000000000..083f195f821 --- /dev/null +++ b/starters/docs/stories/Slider.stories.tsx @@ -0,0 +1,23 @@ +import {Slider} from '../src/Slider'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Slider, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +type Story = StoryFn; + +export const Example: Story = (args) => ; + +Example.args = { + label: 'Range', + defaultValue: [30, 60], + thumbLabels: ['start', 'end'] +}; diff --git a/starters/docs/stories/Switch.stories.tsx b/starters/docs/stories/Switch.stories.tsx new file mode 100644 index 00000000000..e64f464386e --- /dev/null +++ b/starters/docs/stories/Switch.stories.tsx @@ -0,0 +1,16 @@ +import {Switch} from '../src/Switch'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Switch, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryFn; + +export const Example: Story = (args) => Wi-Fi; diff --git a/starters/docs/stories/Table.stories.tsx b/starters/docs/stories/Table.stories.tsx new file mode 100644 index 00000000000..3090cb7261e --- /dev/null +++ b/starters/docs/stories/Table.stories.tsx @@ -0,0 +1,48 @@ +import {Column, Row, Table, TableHeader} from '../src/Table'; +import {Cell, TableBody} from 'react-aria-components'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Table, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +type Story = StoryFn; + +export const Example: Story = (args) => ( + + + Name + Type + Date Modified + + + + Games + File folder + 6/7/2020 + + + Program Files + File folder + 4/7/2021 + + + bootmgr + System file + 11/20/2010 + + +
+); + +Example.args = { + onRowAction: undefined, + selectionMode: 'multiple' +}; diff --git a/starters/docs/stories/Tabs.stories.tsx b/starters/docs/stories/Tabs.stories.tsx new file mode 100644 index 00000000000..be2d4c96d3c --- /dev/null +++ b/starters/docs/stories/Tabs.stories.tsx @@ -0,0 +1,39 @@ +import {Tabs} from '../src/Tabs'; +import {Tab, TabList, TabPanel} from 'react-aria-components'; +import {fn} from '@storybook/test'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Tabs, + parameters: { + layout: 'centered' + }, + args: { + onSelectionChange: fn() + }, + tags: ['autodocs'] +}; + +export default meta; + +type Story = StoryFn; + +export const Example: Story = (args) => ( + + + Founding of Rome + Monarchy and Republic + Empire + + + Arma virumque cano, Troiae qui primus ab oris. + + + Senatus Populusque Romanus. + + + Alea jacta est. + + +); diff --git a/starters/docs/stories/TagGroup.stories.tsx b/starters/docs/stories/TagGroup.stories.tsx new file mode 100644 index 00000000000..289a4ebbf07 --- /dev/null +++ b/starters/docs/stories/TagGroup.stories.tsx @@ -0,0 +1,29 @@ +import {Tag, TagGroup} from '../src/TagGroup'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: TagGroup, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +type Story = StoryFn; + +export const Example: Story = (args) => ( + + Chocolate + Mint + Strawberry + Vanilla + +); + +Example.args = { + label: 'Ice cream flavor', + selectionMode: 'single' +}; diff --git a/starters/docs/stories/TextField.stories.tsx b/starters/docs/stories/TextField.stories.tsx new file mode 100644 index 00000000000..0eb42385414 --- /dev/null +++ b/starters/docs/stories/TextField.stories.tsx @@ -0,0 +1,21 @@ +import {TextField} from '../src/TextField'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: TextField, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +type Story = StoryFn; + +export const Example: Story = (args) => ; + +Example.args = { + label: 'Name' +}; diff --git a/starters/docs/stories/TimeField.stories.tsx b/starters/docs/stories/TimeField.stories.tsx new file mode 100644 index 00000000000..c97c441398d --- /dev/null +++ b/starters/docs/stories/TimeField.stories.tsx @@ -0,0 +1,21 @@ +import {TimeField} from '../src/TimeField'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: TimeField, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +type Story = StoryFn; + +export const Example: Story = (args) => ; + +Example.args = { + label: 'Event time' +}; diff --git a/starters/docs/stories/ToggleButton.stories.tsx b/starters/docs/stories/ToggleButton.stories.tsx new file mode 100644 index 00000000000..efd128a500a --- /dev/null +++ b/starters/docs/stories/ToggleButton.stories.tsx @@ -0,0 +1,18 @@ +import {ToggleButton} from '../src/ToggleButton'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: ToggleButton, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +type Story = StoryFn; + +export const Example: Story = (args) => Pin +; diff --git a/starters/docs/stories/ToggleButtonGroup.stories.tsx b/starters/docs/stories/ToggleButtonGroup.stories.tsx new file mode 100644 index 00000000000..f9d79ebd34b --- /dev/null +++ b/starters/docs/stories/ToggleButtonGroup.stories.tsx @@ -0,0 +1,24 @@ +import {ToggleButtonGroup} from '../src/ToggleButtonGroup'; +import {ToggleButton} from 'react-aria-components'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: ToggleButtonGroup, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +type Story = StoryFn; + +export const Example: Story = (args) => ( + + Left + Center + Right + +); diff --git a/starters/docs/stories/Toolbar.stories.tsx b/starters/docs/stories/Toolbar.stories.tsx new file mode 100644 index 00000000000..a1d72d462e7 --- /dev/null +++ b/starters/docs/stories/Toolbar.stories.tsx @@ -0,0 +1,53 @@ +import {Toolbar} from '../src/Toolbar'; +import { + Button, + Checkbox, + Group, + Separator, + ToggleButton +} from 'react-aria-components'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Toolbar, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +type Story = StoryFn; + +export const Example: Story = (args) => ( + + + + B + + + I + + + U + + + + + + + + + + +
+ +
+ Night Mode +
+
+); diff --git a/starters/docs/stories/Tooltip.stories.tsx b/starters/docs/stories/Tooltip.stories.tsx new file mode 100644 index 00000000000..d6fb5397738 --- /dev/null +++ b/starters/docs/stories/Tooltip.stories.tsx @@ -0,0 +1,25 @@ +import {Tooltip} from '../src/Tooltip'; +import {TooltipTrigger} from 'react-aria-components'; +import {Button} from '../src/Button'; +import {Save} from 'lucide-react'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Tooltip, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +type Story = StoryFn; + +export const Example: Story = (args) => ( + + + Save + +); diff --git a/starters/docs/stories/Tree.stories.tsx b/starters/docs/stories/Tree.stories.tsx new file mode 100644 index 00000000000..aa041d95cff --- /dev/null +++ b/starters/docs/stories/Tree.stories.tsx @@ -0,0 +1,34 @@ +import {Tree, TreeItem} from '../src/Tree'; + +import type {Meta, StoryFn} from '@storybook/react'; + +const meta: Meta = { + component: Tree, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +type Story = StoryFn; + +export const Example: Story = (args) => ( + + + + + + + + + + + +); + +Example.args = { + style: { height: '300px' }, + defaultExpandedKeys: ['documents', 'photos', 'project'] +};