From 8a38c430d511692ff66be1ac8499fed17c643659 Mon Sep 17 00:00:00 2001 From: Robert Snow Date: Wed, 2 Jul 2025 13:08:25 +1000 Subject: [PATCH 01/15] initial commit --- starters/docs/.gitignore | 2 - starters/docs/package.json | 8 +- starters/docs/src/Autocomplete.css | 37 +++ starters/docs/src/Autocomplete.tsx | 40 +++ starters/docs/src/Breadcrumbs.css | 53 ++++ starters/docs/src/Breadcrumbs.tsx | 7 + starters/docs/src/Button.css | 42 +++ starters/docs/src/Button.tsx | 7 + starters/docs/src/Calendar.css | 81 ++++++ starters/docs/src/Calendar.tsx | 38 +++ starters/docs/src/Checkbox.css | 102 +++++++ starters/docs/src/Checkbox.tsx | 31 ++ starters/docs/src/CheckboxGroup.css | 20 ++ starters/docs/src/CheckboxGroup.tsx | 43 +++ starters/docs/src/ColorArea.css | 32 +++ starters/docs/src/ColorArea.tsx | 20 ++ starters/docs/src/ColorField.css | 49 ++++ starters/docs/src/ColorField.tsx | 35 +++ starters/docs/src/ColorPicker.css | 43 +++ starters/docs/src/ColorPicker.tsx | 50 ++++ starters/docs/src/ColorSlider.css | 78 +++++ starters/docs/src/ColorSlider.tsx | 36 +++ starters/docs/src/ColorSwatch.css | 8 + starters/docs/src/ColorSwatch.tsx | 23 ++ starters/docs/src/ColorSwatchPicker.css | 42 +++ starters/docs/src/ColorSwatchPicker.tsx | 37 +++ starters/docs/src/ColorWheel.css | 26 ++ starters/docs/src/ColorWheel.tsx | 24 ++ starters/docs/src/ComboBox.css | 132 +++++++++ starters/docs/src/ComboBox.tsx | 52 ++++ starters/docs/src/DateField.css | 70 +++++ starters/docs/src/DateField.tsx | 38 +++ starters/docs/src/DatePicker.css | 71 +++++ starters/docs/src/DatePicker.tsx | 64 +++++ starters/docs/src/DateRangePicker.css | 104 +++++++ starters/docs/src/DateRangePicker.tsx | 68 +++++ starters/docs/src/Dialog.css | 17 ++ starters/docs/src/Dialog.tsx | 7 + starters/docs/src/Disclosure.css | 33 +++ starters/docs/src/Disclosure.tsx | 35 +++ starters/docs/src/DisclosureGroup.css | 3 + starters/docs/src/DisclosureGroup.tsx | 7 + starters/docs/src/Form.css | 31 ++ starters/docs/src/Form.tsx | 7 + starters/docs/src/GridList.css | 184 ++++++++++++ starters/docs/src/GridList.tsx | 51 ++++ starters/docs/src/Link.css | 32 +++ starters/docs/src/Link.tsx | 7 + starters/docs/src/ListBox.css | 219 ++++++++++++++ starters/docs/src/ListBox.tsx | 25 ++ starters/docs/src/Menu.css | 120 ++++++++ starters/docs/src/Menu.tsx | 58 ++++ starters/docs/src/Meter.css | 37 +++ starters/docs/src/Meter.tsx | 30 ++ starters/docs/src/Modal.css | 84 ++++++ starters/docs/src/Modal.tsx | 7 + starters/docs/src/NumberField.css | 89 ++++++ starters/docs/src/NumberField.tsx | 39 +++ starters/docs/src/Popover.css | 74 +++++ starters/docs/src/Popover.tsx | 30 ++ starters/docs/src/ProgressBar.css | 49 ++++ starters/docs/src/ProgressBar.tsx | 30 ++ starters/docs/src/RadioGroup.css | 89 ++++++ starters/docs/src/RadioGroup.tsx | 39 +++ starters/docs/src/RangeCalendar.css | 103 +++++++ starters/docs/src/RangeCalendar.tsx | 38 +++ starters/docs/src/SearchField.css | 90 ++++++ starters/docs/src/SearchField.tsx | 38 +++ starters/docs/src/Select.css | 142 +++++++++ starters/docs/src/Select.tsx | 59 ++++ starters/docs/src/Slider.css | 106 +++++++ starters/docs/src/Slider.tsx | 38 +++ starters/docs/src/Switch.css | 76 +++++ starters/docs/src/Switch.tsx | 22 ++ starters/docs/src/Table.css | 269 ++++++++++++++++++ starters/docs/src/Table.tsx | 98 +++++++ starters/docs/src/Tabs.css | 102 +++++++ starters/docs/src/Tabs.tsx | 7 + starters/docs/src/TagGroup.css | 99 +++++++ starters/docs/src/TagGroup.tsx | 68 +++++ starters/docs/src/TextField.css | 50 ++++ starters/docs/src/TextField.tsx | 33 +++ starters/docs/src/TimeField.css | 70 +++++ starters/docs/src/TimeField.tsx | 38 +++ starters/docs/src/ToggleButton.css | 44 +++ starters/docs/src/ToggleButton.tsx | 7 + starters/docs/src/ToggleButtonGroup.css | 56 ++++ starters/docs/src/ToggleButtonGroup.tsx | 7 + starters/docs/src/Toolbar.css | 50 ++++ starters/docs/src/Toolbar.tsx | 7 + starters/docs/src/Tooltip.css | 56 ++++ starters/docs/src/Tooltip.tsx | 27 ++ starters/docs/src/Tree.css | 203 +++++++++++++ starters/docs/src/Tree.tsx | 64 +++++ starters/docs/src/theme.css | 118 ++++++++ .../docs/stories/Autocomplete.stories.tsx | 32 +++ starters/docs/stories/Breadcrumbs.stories.tsx | 28 ++ starters/docs/stories/Button.stories.tsx | 19 ++ starters/docs/stories/Calendar.stories.tsx | 17 ++ starters/docs/stories/Checkbox.stories.tsx | 16 ++ .../docs/stories/CheckboxGroup.stories.tsx | 26 ++ starters/docs/stories/ColorArea.stories.tsx | 19 ++ starters/docs/stories/ColorField.stories.tsx | 19 ++ starters/docs/stories/ColorPicker.stories.tsx | 20 ++ starters/docs/stories/ColorSlider.stories.tsx | 21 ++ starters/docs/stories/ColorSwatch.stories.tsx | 19 ++ .../stories/ColorSwatchPicker.stories.tsx | 27 ++ starters/docs/stories/ColorWheel.stories.tsx | 19 ++ starters/docs/stories/ComboBox.stories.tsx | 26 ++ starters/docs/stories/DateField.stories.tsx | 19 ++ starters/docs/stories/DatePicker.stories.tsx | 19 ++ .../docs/stories/DateRangePicker.stories.tsx | 19 ++ starters/docs/stories/Dialog.stories.tsx | 46 +++ starters/docs/stories/Disclosure.stories.tsx | 21 ++ .../docs/stories/DisclosureGroup.stories.tsx | 54 ++++ starters/docs/stories/Form.stories.tsx | 31 ++ starters/docs/stories/GridList.stories.tsx | 27 ++ starters/docs/stories/Link.stories.tsx | 20 ++ starters/docs/stories/ListBox.stories.tsx | 27 ++ starters/docs/stories/Menu.stories.tsx | 22 ++ starters/docs/stories/Meter.stories.tsx | 20 ++ starters/docs/stories/Modal.stories.tsx | 46 +++ starters/docs/stories/NumberField.stories.tsx | 19 ++ starters/docs/stories/Popover.stories.tsx | 24 ++ starters/docs/stories/ProgressBar.stories.tsx | 20 ++ starters/docs/stories/RadioGroup.stories.tsx | 26 ++ .../docs/stories/RangeCalendar.stories.tsx | 17 ++ starters/docs/stories/SearchField.stories.tsx | 19 ++ starters/docs/stories/Select.stories.tsx | 26 ++ starters/docs/stories/Slider.stories.tsx | 21 ++ starters/docs/stories/Switch.stories.tsx | 15 + starters/docs/stories/Table.stories.tsx | 46 +++ starters/docs/stories/Tabs.stories.tsx | 33 +++ starters/docs/stories/TagGroup.stories.tsx | 27 ++ starters/docs/stories/TextField.stories.tsx | 19 ++ starters/docs/stories/TimeField.stories.tsx | 19 ++ .../docs/stories/ToggleButton.stories.tsx | 16 ++ .../stories/ToggleButtonGroup.stories.tsx | 22 ++ starters/docs/stories/Toolbar.stories.tsx | 51 ++++ starters/docs/stories/Tooltip.stories.tsx | 21 ++ starters/docs/stories/Tree.stories.tsx | 32 +++ 141 files changed, 6377 insertions(+), 6 deletions(-) create mode 100644 starters/docs/src/Autocomplete.css create mode 100644 starters/docs/src/Autocomplete.tsx create mode 100644 starters/docs/src/Breadcrumbs.css create mode 100644 starters/docs/src/Breadcrumbs.tsx create mode 100644 starters/docs/src/Button.css create mode 100644 starters/docs/src/Button.tsx create mode 100644 starters/docs/src/Calendar.css create mode 100644 starters/docs/src/Calendar.tsx create mode 100644 starters/docs/src/Checkbox.css create mode 100644 starters/docs/src/Checkbox.tsx create mode 100644 starters/docs/src/CheckboxGroup.css create mode 100644 starters/docs/src/CheckboxGroup.tsx create mode 100644 starters/docs/src/ColorArea.css create mode 100644 starters/docs/src/ColorArea.tsx create mode 100644 starters/docs/src/ColorField.css create mode 100644 starters/docs/src/ColorField.tsx create mode 100644 starters/docs/src/ColorPicker.css create mode 100644 starters/docs/src/ColorPicker.tsx create mode 100644 starters/docs/src/ColorSlider.css create mode 100644 starters/docs/src/ColorSlider.tsx create mode 100644 starters/docs/src/ColorSwatch.css create mode 100644 starters/docs/src/ColorSwatch.tsx create mode 100644 starters/docs/src/ColorSwatchPicker.css create mode 100644 starters/docs/src/ColorSwatchPicker.tsx create mode 100644 starters/docs/src/ColorWheel.css create mode 100644 starters/docs/src/ColorWheel.tsx create mode 100644 starters/docs/src/ComboBox.css create mode 100644 starters/docs/src/ComboBox.tsx create mode 100644 starters/docs/src/DateField.css create mode 100644 starters/docs/src/DateField.tsx create mode 100644 starters/docs/src/DatePicker.css create mode 100644 starters/docs/src/DatePicker.tsx create mode 100644 starters/docs/src/DateRangePicker.css create mode 100644 starters/docs/src/DateRangePicker.tsx create mode 100644 starters/docs/src/Dialog.css create mode 100644 starters/docs/src/Dialog.tsx create mode 100644 starters/docs/src/Disclosure.css create mode 100644 starters/docs/src/Disclosure.tsx create mode 100644 starters/docs/src/DisclosureGroup.css create mode 100644 starters/docs/src/DisclosureGroup.tsx create mode 100644 starters/docs/src/Form.css create mode 100644 starters/docs/src/Form.tsx create mode 100644 starters/docs/src/GridList.css create mode 100644 starters/docs/src/GridList.tsx create mode 100644 starters/docs/src/Link.css create mode 100644 starters/docs/src/Link.tsx create mode 100644 starters/docs/src/ListBox.css create mode 100644 starters/docs/src/ListBox.tsx create mode 100644 starters/docs/src/Menu.css create mode 100644 starters/docs/src/Menu.tsx create mode 100644 starters/docs/src/Meter.css create mode 100644 starters/docs/src/Meter.tsx create mode 100644 starters/docs/src/Modal.css create mode 100644 starters/docs/src/Modal.tsx create mode 100644 starters/docs/src/NumberField.css create mode 100644 starters/docs/src/NumberField.tsx create mode 100644 starters/docs/src/Popover.css create mode 100644 starters/docs/src/Popover.tsx create mode 100644 starters/docs/src/ProgressBar.css create mode 100644 starters/docs/src/ProgressBar.tsx create mode 100644 starters/docs/src/RadioGroup.css create mode 100644 starters/docs/src/RadioGroup.tsx create mode 100644 starters/docs/src/RangeCalendar.css create mode 100644 starters/docs/src/RangeCalendar.tsx create mode 100644 starters/docs/src/SearchField.css create mode 100644 starters/docs/src/SearchField.tsx create mode 100644 starters/docs/src/Select.css create mode 100644 starters/docs/src/Select.tsx create mode 100644 starters/docs/src/Slider.css create mode 100644 starters/docs/src/Slider.tsx create mode 100644 starters/docs/src/Switch.css create mode 100644 starters/docs/src/Switch.tsx create mode 100644 starters/docs/src/Table.css create mode 100644 starters/docs/src/Table.tsx create mode 100644 starters/docs/src/Tabs.css create mode 100644 starters/docs/src/Tabs.tsx create mode 100644 starters/docs/src/TagGroup.css create mode 100644 starters/docs/src/TagGroup.tsx create mode 100644 starters/docs/src/TextField.css create mode 100644 starters/docs/src/TextField.tsx create mode 100644 starters/docs/src/TimeField.css create mode 100644 starters/docs/src/TimeField.tsx create mode 100644 starters/docs/src/ToggleButton.css create mode 100644 starters/docs/src/ToggleButton.tsx create mode 100644 starters/docs/src/ToggleButtonGroup.css create mode 100644 starters/docs/src/ToggleButtonGroup.tsx create mode 100644 starters/docs/src/Toolbar.css create mode 100644 starters/docs/src/Toolbar.tsx create mode 100644 starters/docs/src/Tooltip.css create mode 100644 starters/docs/src/Tooltip.tsx create mode 100644 starters/docs/src/Tree.css create mode 100644 starters/docs/src/Tree.tsx create mode 100644 starters/docs/src/theme.css create mode 100644 starters/docs/stories/Autocomplete.stories.tsx create mode 100644 starters/docs/stories/Breadcrumbs.stories.tsx create mode 100644 starters/docs/stories/Button.stories.tsx create mode 100644 starters/docs/stories/Calendar.stories.tsx create mode 100644 starters/docs/stories/Checkbox.stories.tsx create mode 100644 starters/docs/stories/CheckboxGroup.stories.tsx create mode 100644 starters/docs/stories/ColorArea.stories.tsx create mode 100644 starters/docs/stories/ColorField.stories.tsx create mode 100644 starters/docs/stories/ColorPicker.stories.tsx create mode 100644 starters/docs/stories/ColorSlider.stories.tsx create mode 100644 starters/docs/stories/ColorSwatch.stories.tsx create mode 100644 starters/docs/stories/ColorSwatchPicker.stories.tsx create mode 100644 starters/docs/stories/ColorWheel.stories.tsx create mode 100644 starters/docs/stories/ComboBox.stories.tsx create mode 100644 starters/docs/stories/DateField.stories.tsx create mode 100644 starters/docs/stories/DatePicker.stories.tsx create mode 100644 starters/docs/stories/DateRangePicker.stories.tsx create mode 100644 starters/docs/stories/Dialog.stories.tsx create mode 100644 starters/docs/stories/Disclosure.stories.tsx create mode 100644 starters/docs/stories/DisclosureGroup.stories.tsx create mode 100644 starters/docs/stories/Form.stories.tsx create mode 100644 starters/docs/stories/GridList.stories.tsx create mode 100644 starters/docs/stories/Link.stories.tsx create mode 100644 starters/docs/stories/ListBox.stories.tsx create mode 100644 starters/docs/stories/Menu.stories.tsx create mode 100644 starters/docs/stories/Meter.stories.tsx create mode 100644 starters/docs/stories/Modal.stories.tsx create mode 100644 starters/docs/stories/NumberField.stories.tsx create mode 100644 starters/docs/stories/Popover.stories.tsx create mode 100644 starters/docs/stories/ProgressBar.stories.tsx create mode 100644 starters/docs/stories/RadioGroup.stories.tsx create mode 100644 starters/docs/stories/RangeCalendar.stories.tsx create mode 100644 starters/docs/stories/SearchField.stories.tsx create mode 100644 starters/docs/stories/Select.stories.tsx create mode 100644 starters/docs/stories/Slider.stories.tsx create mode 100644 starters/docs/stories/Switch.stories.tsx create mode 100644 starters/docs/stories/Table.stories.tsx create mode 100644 starters/docs/stories/Tabs.stories.tsx create mode 100644 starters/docs/stories/TagGroup.stories.tsx create mode 100644 starters/docs/stories/TextField.stories.tsx create mode 100644 starters/docs/stories/TimeField.stories.tsx create mode 100644 starters/docs/stories/ToggleButton.stories.tsx create mode 100644 starters/docs/stories/ToggleButtonGroup.stories.tsx create mode 100644 starters/docs/stories/Toolbar.stories.tsx create mode 100644 starters/docs/stories/Tooltip.stories.tsx create mode 100644 starters/docs/stories/Tree.stories.tsx diff --git a/starters/docs/.gitignore b/starters/docs/.gitignore index fbf3418d3a7..20687473be0 100644 --- a/starters/docs/.gitignore +++ b/starters/docs/.gitignore @@ -1,3 +1 @@ -src -stories storybook-static diff --git a/starters/docs/package.json b/starters/docs/package.json index de826fae07f..94b6ab27857 100644 --- a/starters/docs/package.json +++ b/starters/docs/package.json @@ -1,4 +1,8 @@ { + "scripts": { + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" + }, "devDependencies": { "@babel/preset-react": "^7.24.1", "@storybook/addon-interactions": "^8.6.14", @@ -23,9 +27,5 @@ "resolutions": { "@types/mime": "3.0.4", "jackspeak": "2.1.1" - }, - "scripts": { - "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build" } } diff --git a/starters/docs/src/Autocomplete.css b/starters/docs/src/Autocomplete.css new file mode 100644 index 00000000000..a5fd3ced5ec --- /dev/null +++ b/starters/docs/src/Autocomplete.css @@ -0,0 +1,37 @@ +@import './Menu.css'; +@import './Button.css'; +@import './SearchField.css'; +@import "./theme.css"; + +.my-autocomplete { + display: flex; + flex-direction: column; + gap: 12px; + max-width: 300px; + height: 180px; + border: 1px solid var(--border-color); + padding: 16px; + border-radius: 10px; + background: var(--overlay-background); +} + +.react-aria-SearchField { + width: 100%; +} + +.react-aria-Label { + margin-bottom: .5em; +} + +.react-aria-Menu { + &[data-empty] { + align-items: center; + justify-content: center; + font-style: italic; + } +} + +.react-aria-MenuItem[href] { + text-decoration: none; + cursor: pointer; +} diff --git a/starters/docs/src/Autocomplete.tsx b/starters/docs/src/Autocomplete.tsx new file mode 100644 index 00000000000..2ddbac157bf --- /dev/null +++ b/starters/docs/src/Autocomplete.tsx @@ -0,0 +1,40 @@ +'use client'; +import { + Autocomplete as AriaAutocomplete, + AutocompleteProps as AriaAutocompleteProps, + Key, + Menu, + useFilter +} from 'react-aria-components'; + +import {MySearchField} from './SearchField'; + +import './Autocomplete.css'; + +export interface AutocompleteProps + extends Omit { + label?: string; + placeholder?: string; + items?: Iterable; + children: React.ReactNode | ((item: T) => React.ReactNode); + onAction?: (id: Key) => void; +} + +export function Autocomplete( + { label, placeholder, items, children, onAction, ...props }: + AutocompleteProps +) { + let { contains } = useFilter({ sensitivity: 'base' }); + return ( + ( +
+ + + + {children} + + +
+ ) + ); +} diff --git a/starters/docs/src/Breadcrumbs.css b/starters/docs/src/Breadcrumbs.css new file mode 100644 index 00000000000..0964ebba054 --- /dev/null +++ b/starters/docs/src/Breadcrumbs.css @@ -0,0 +1,53 @@ +@import "./theme.css"; + +.react-aria-Breadcrumbs { + display: flex; + align-items: center; + list-style: none; + margin: 0; + padding: 0; + font-size: 18px; + color: var(--text-color); + + .react-aria-Breadcrumb:not(:last-child)::after { + content: '›'; + content: '›' / ''; + alt: ' '; + padding: 0 5px; + } + + .react-aria-Link { + color: var(--link-color-secondary); + outline: none; + position: relative; + text-decoration: none; + cursor: pointer; + + &[data-hovered] { + text-decoration: underline; + } + + &[data-current] { + color: var(--text-color); + font-weight: bold; + } + + &[data-focus-visible]:after { + content: ''; + position: absolute; + inset: -2px -4px; + border-radius: 6px; + border: 2px solid var(--focus-ring-color); + } + } + + .react-aria-Link { + &[data-disabled] { + cursor: default; + + &:not([data-current]) { + color: var(--text-color-disabled); + } + } + } +} diff --git a/starters/docs/src/Breadcrumbs.tsx b/starters/docs/src/Breadcrumbs.tsx new file mode 100644 index 00000000000..5234251ff12 --- /dev/null +++ b/starters/docs/src/Breadcrumbs.tsx @@ -0,0 +1,7 @@ +'use client'; +import {Breadcrumbs as RACBreadcrumbs, BreadcrumbsProps} from 'react-aria-components'; +import './Breadcrumbs.css'; + +export function Breadcrumbs(props: BreadcrumbsProps) { + return ; +} diff --git a/starters/docs/src/Button.css b/starters/docs/src/Button.css new file mode 100644 index 00000000000..b63c193bc67 --- /dev/null +++ b/starters/docs/src/Button.css @@ -0,0 +1,42 @@ +@import "./theme.css"; + +.react-aria-Button { + color: var(--text-color); + background: var(--button-background); + border: 1px solid var(--border-color); + border-radius: 4px; + appearance: none; + vertical-align: middle; + font-size: 0.875rem; + text-align: center; + margin: 0; + outline: none; + padding: 6px 10px; + text-decoration: none; + + &[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-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -1px; + } + + &[data-disabled]{ + border-color: var(--border-color-disabled); + color: var(--text-color-disabled); + } +} + +@keyframes toggle { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} diff --git a/starters/docs/src/Button.tsx b/starters/docs/src/Button.tsx new file mode 100644 index 00000000000..3520c7223ad --- /dev/null +++ b/starters/docs/src/Button.tsx @@ -0,0 +1,7 @@ +'use client'; +import {Button as RACButton, ButtonProps} from 'react-aria-components'; +import './Button.css'; + +export function Button(props: ButtonProps) { + return ; +} diff --git a/starters/docs/src/Calendar.css b/starters/docs/src/Calendar.css new file mode 100644 index 00000000000..d7d5e391ad3 --- /dev/null +++ b/starters/docs/src/Calendar.css @@ -0,0 +1,81 @@ +@import './Button.css'; +@import "./theme.css"; + +.react-aria-Calendar { + 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; + } + + .react-aria-CalendarCell { + width: 1.75rem; + line-height: 1.75rem; + text-align: center; + border-radius: 6px; + cursor: default; + outline: none; + margin: 1px; + forced-color-adjust: none; + + &[data-outside-month] { + display: none; + } + + &[data-pressed] { + background: var(--gray-100); + } + + &[data-focus-visible] { + outline: 2px solid var(--focus-ring-color); + outline-offset: 2px; + } + + &[data-selected] { + background: var(--highlight-background); + color: var(--highlight-foreground); + } + } + + .react-aria-CalendarCell { + &[data-disabled] { + color: var(--text-color-disabled); + } + } + + .react-aria-CalendarCell { + &[data-unavailable] { + text-decoration: line-through; + color: var(--invalid-color); + } + } + + .react-aria-CalendarCell { + &[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/Calendar.tsx b/starters/docs/src/Calendar.tsx new file mode 100644 index 00000000000..fcac45bebdd --- /dev/null +++ b/starters/docs/src/Calendar.tsx @@ -0,0 +1,38 @@ +'use client'; +import { + Button, + Calendar as AriaCalendar, + CalendarCell, + CalendarGrid, + CalendarProps as AriaCalendarProps, + DateValue, + Heading, + Text +} from 'react-aria-components'; + +import './Calendar.css'; + +export interface CalendarProps + extends AriaCalendarProps { + errorMessage?: string; +} + +export function Calendar( + { errorMessage, ...props }: CalendarProps +) { + return ( + ( + +
+ + + +
+ + {(date) => } + + {errorMessage && {errorMessage}} +
+ ) + ); +} diff --git a/starters/docs/src/Checkbox.css b/starters/docs/src/Checkbox.css new file mode 100644 index 00000000000..b51b5036da5 --- /dev/null +++ b/starters/docs/src/Checkbox.css @@ -0,0 +1,102 @@ +@import "./theme.css"; + +.react-aria-Checkbox { + --selected-color: var(--highlight-background); + --selected-color-pressed: var(--highlight-background-pressed); + --checkmark-color: var(--highlight-foreground); + + 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; + + .checkbox { + width: 1.000125rem; + height: 1.000125rem; + border: 2px solid var(--border-color); + border-radius: 4px; + transition: all 200ms; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + } + + svg { + width: 0.875rem; + height: 0.875rem; + fill: none; + stroke: var(--checkmark-color); + stroke-width: 3px; + stroke-dasharray: 22px; + stroke-dashoffset: 66; + transition: all 200ms; + } + + &[data-pressed] .checkbox { + border-color: var(--border-color-pressed); + } + + &[data-focus-visible] .checkbox { + outline: 2px solid var(--focus-ring-color); + outline-offset: 2px; + } + + &[data-selected], + &[data-indeterminate] { + .checkbox { + border-color: var(--selected-color); + background: var(--selected-color); + } + + &[data-pressed] .checkbox { + border-color: var(--selected-color-pressed); + background: var(--selected-color-pressed); + } + + svg { + stroke-dashoffset: 44; + } + } + + &[data-indeterminate] { + & svg { + stroke: none; + fill: var(--checkmark-color); + } + } + + &[data-invalid] { + .checkbox { + --checkmark-color: var(--gray-50); + border-color: var(--invalid-color); + } + + &[data-pressed] .checkbox { + border-color: var(--invalid-color-pressed); + } + + &[data-selected], + &[data-indeterminate] { + .checkbox { + background: var(--invalid-color); + } + + &[data-pressed] .checkbox { + background: var(--invalid-color-pressed); + } + } + } + + &[data-disabled] { + color: var(--text-color-disabled); + + .checkbox { + border-color: var(--border-color-disabled); + } + } +} diff --git a/starters/docs/src/Checkbox.tsx b/starters/docs/src/Checkbox.tsx new file mode 100644 index 00000000000..1c50c83b124 --- /dev/null +++ b/starters/docs/src/Checkbox.tsx @@ -0,0 +1,31 @@ +'use client'; +import {Checkbox as AriaCheckbox, CheckboxProps} from 'react-aria-components'; + +import './Checkbox.css'; + +export function Checkbox( + { children, ...props }: Omit & { + children?: React.ReactNode; + } +) { + return ( + ( + + {({ isIndeterminate }) => ( + <> +
+ +
+ {children} + + )} +
+ ) + ); +} + +export { Checkbox as MyCheckbox }; diff --git a/starters/docs/src/CheckboxGroup.css b/starters/docs/src/CheckboxGroup.css new file mode 100644 index 00000000000..7c0b43c7782 --- /dev/null +++ b/starters/docs/src/CheckboxGroup.css @@ -0,0 +1,20 @@ +@import './Checkbox.css'; +@import './Form.css'; +@import './Button.css'; +@import "./theme.css"; + +.react-aria-CheckboxGroup { + display: flex; + flex-direction: column; + gap: 0.499625rem; + color: var(--text-color); + + .react-aria-FieldError { + font-size: 12px; + color: var(--invalid-color); + } + + [slot=description] { + font-size: 12px; + } +} diff --git a/starters/docs/src/CheckboxGroup.tsx b/starters/docs/src/CheckboxGroup.tsx new file mode 100644 index 00000000000..adb1e044670 --- /dev/null +++ b/starters/docs/src/CheckboxGroup.tsx @@ -0,0 +1,43 @@ +'use client'; +import { + CheckboxGroup as AriaCheckboxGroup, + CheckboxGroupProps as AriaCheckboxGroupProps, + CheckboxProps as AriaCheckboxProps, + FieldError, + Label, + Text, + ValidationResult +} from 'react-aria-components'; + +import './CheckboxGroup.css'; + +export interface CheckboxGroupProps + extends Omit { + children?: React.ReactNode; + label?: string; + description?: string; + errorMessage?: string | ((validation: ValidationResult) => string); +} + +export function CheckboxGroup( + { + label, + description, + errorMessage, + children, + ...props + }: CheckboxGroupProps +) { + return ( + ( + + {label && } + {children} + {description && {description}} + {errorMessage} + + ) + ); +} + +export { CheckboxGroup as MyCheckboxGroup }; diff --git a/starters/docs/src/ColorArea.css b/starters/docs/src/ColorArea.css new file mode 100644 index 00000000000..d03f8542627 --- /dev/null +++ b/starters/docs/src/ColorArea.css @@ -0,0 +1,32 @@ +@import "./ColorSlider.css"; + +.react-aria-ColorArea { + width: 192px; + height: 192px; + border-radius: 4px; + flex-shrink: 0; +} + +.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-ColorArea { + &[data-disabled] { + background: gray !important; + + .react-aria-ColorThumb { + background: gray !important; + } + } +} diff --git a/starters/docs/src/ColorArea.tsx b/starters/docs/src/ColorArea.tsx new file mode 100644 index 00000000000..f53bb349706 --- /dev/null +++ b/starters/docs/src/ColorArea.tsx @@ -0,0 +1,20 @@ +'use client'; +import { + ColorArea as AriaColorArea, + ColorAreaProps, + ColorThumb +} from 'react-aria-components'; + +import './ColorArea.css'; + +export function ColorArea(props: ColorAreaProps) { + return ( + ( + + + + ) + ); +} + +export { ColorArea as MyColorArea }; diff --git a/starters/docs/src/ColorField.css b/starters/docs/src/ColorField.css new file mode 100644 index 00000000000..9669327602a --- /dev/null +++ b/starters/docs/src/ColorField.css @@ -0,0 +1,49 @@ +@import './Button.css'; +@import './Form.css'; +@import "./theme.css"; + +.react-aria-ColorField { + display: flex; + flex-direction: column; + color: var(--text-color); + + .react-aria-Input { + 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); + width: 100%; + max-width: 12ch; + box-sizing: border-box; + + &[data-focused] { + outline: 2px solid var(--focus-ring-color); + outline-offset: -1px; + } + } + + &[data-invalid] { + .react-aria-Input { + border-color: var(--invalid-color); + } + } + + .react-aria-FieldError { + font-size: 12px; + color: var(--invalid-color); + } + + [slot=description] { + 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/ColorField.tsx b/starters/docs/src/ColorField.tsx new file mode 100644 index 00000000000..5b4fb4a3c99 --- /dev/null +++ b/starters/docs/src/ColorField.tsx @@ -0,0 +1,35 @@ +'use client'; +import { + ColorField as AriaColorField, + ColorFieldProps as AriaColorFieldProps, + FieldError, + Input, + Label, + Text, + ValidationResult +} from 'react-aria-components'; + +import './ColorField.css'; + +export interface ColorFieldProps extends AriaColorFieldProps { + label?: string; + description?: string; + errorMessage?: string | ((validation: ValidationResult) => string); +} + +export function ColorField( + { label, description, errorMessage, ...props }: ColorFieldProps +) { + return ( + ( + + {label && } + + {description && {description}} + {errorMessage} + + ) + ); +} + +export { ColorField as MyColorField }; 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..6e1f6e9a53d --- /dev/null +++ b/starters/docs/src/ColorPicker.tsx @@ -0,0 +1,50 @@ +'use client'; +import { + Button, + ColorPicker as AriaColorPicker, + ColorPickerProps as AriaColorPickerProps, + Dialog, + DialogTrigger, + Popover +} from 'react-aria-components'; +import {MyColorSwatch} from './ColorSwatch'; +import {MyColorSlider} from './ColorSlider'; +import {MyColorArea} from './ColorArea'; +import {MyColorField} 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..24e21eefe72 --- /dev/null +++ b/starters/docs/src/ColorSlider.css @@ -0,0 +1,78 @@ +.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; + } + + .react-aria-SliderOutput { + grid-area: output; + } + + .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..0dce899e145 --- /dev/null +++ b/starters/docs/src/ColorSlider.tsx @@ -0,0 +1,36 @@ +'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` + })} + > + + + + ) + ); +} + +export { ColorSlider as MyColorSlider }; 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..1944d8a79a3 --- /dev/null +++ b/starters/docs/src/ColorSwatch.tsx @@ -0,0 +1,23 @@ +'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` + })} + /> + ) + ); +} + +export { ColorSwatch as MyColorSwatch }; 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..43d5721348c --- /dev/null +++ b/starters/docs/src/ColorSwatchPicker.tsx @@ -0,0 +1,37 @@ +'use client'; +import { + ColorSwatchPicker as AriaColorSwatchPicker, + ColorSwatchPickerItem as AriaColorSwatchPickerItem, + ColorSwatchPickerItemProps, + ColorSwatchPickerProps +} from 'react-aria-components'; + +import {MyColorSwatch} from './ColorSwatch'; + +import './ColorSwatchPicker.css'; + +export function ColorSwatchPicker( + { children, ...props }: ColorSwatchPickerProps +) { + return ( + ( + + {children} + + ) + ); +} + +export { ColorSwatchPicker as MyColorSwatchPicker }; + +export function ColorSwatchPickerItem(props: ColorSwatchPickerItemProps) { + return ( + ( + + + + ) + ); +} + +export { ColorSwatchPickerItem as MyColorSwatchPickerItem }; 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..f3251d2f92c --- /dev/null +++ b/starters/docs/src/ColorWheel.tsx @@ -0,0 +1,24 @@ +'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 ( + ( + + + + + ) + ); +} + +export { ColorWheel as MyColorWheel }; 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..85abd90455a --- /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, + ListBox, + ListBoxItem, + ListBoxItemProps, + Popover, + Text, + ValidationResult +} from 'react-aria-components'; + +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..440d4d5f1a2 --- /dev/null +++ b/starters/docs/src/DatePicker.tsx @@ -0,0 +1,64 @@ +'use client'; +import { + Button, + Calendar, + CalendarCell, + CalendarGrid, + DateInput, + DatePicker as AriaDatePicker, + DatePickerProps as AriaDatePickerProps, + DateSegment, + DateValue, + Dialog, + FieldError, + Group, + Heading, + Label, + Popover, + Text, + ValidationResult +} from 'react-aria-components'; + +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} + + + +
+ + + +
+ + {(date) => } + +
+
+
+
+ ) + ); +} diff --git a/starters/docs/src/DateRangePicker.css b/starters/docs/src/DateRangePicker.css new file mode 100644 index 00000000000..eb8088b9125 --- /dev/null +++ b/starters/docs/src/DateRangePicker.css @@ -0,0 +1,104 @@ +@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-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..a545754a7eb --- /dev/null +++ b/starters/docs/src/DateRangePicker.tsx @@ -0,0 +1,68 @@ +'use client'; +import { + Button, + CalendarCell, + CalendarGrid, + DateInput, + DateRangePicker as AriaDateRangePicker, + DateRangePickerProps as AriaDateRangePickerProps, + DateSegment, + DateValue, + Dialog, + FieldError, + Group, + Heading, + Label, + Popover, + RangeCalendar, + Text, + ValidationResult +} from 'react-aria-components'; + +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} + + + +
+ + + +
+ + {(date) => } + +
+
+
+
+ ) + ); +} 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..b2c4c90df3d --- /dev/null +++ b/starters/docs/src/Disclosure.css @@ -0,0 +1,33 @@ +@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; + width: 12px; + height: 12px; + fill: none; + stroke: currentColor; + stroke-width: 3px; + } + } + + &[data-expanded] .react-aria-Button[slot=trigger] svg { + rotate: 90deg; + } +} + +.react-aria-DisclosurePanel { + margin-left: 32px; +} diff --git a/starters/docs/src/Disclosure.tsx b/starters/docs/src/Disclosure.tsx new file mode 100644 index 00000000000..526aff0c006 --- /dev/null +++ b/starters/docs/src/Disclosure.tsx @@ -0,0 +1,35 @@ +'use client'; +import { + Button, + Disclosure as AriaDisclosure, + DisclosurePanel, + DisclosureProps as AriaDisclosureProps, + Heading +} from 'react-aria-components'; + +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..de535ddf85b --- /dev/null +++ b/starters/docs/src/GridList.css @@ -0,0 +1,184 @@ +@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; + } + } + } + + /* 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..7e405e5b82f --- /dev/null +++ b/starters/docs/src/GridList.tsx @@ -0,0 +1,51 @@ +'use client'; +import { + Button, + GridList as AriaGridList, + GridListItem as AriaGridListItem, + GridListItemProps, + GridListProps +} from 'react-aria-components'; +import {MyCheckbox} from './Checkbox'; + +import './GridList.css'; + +export function GridList( + { children, ...props }: GridListProps +) { + return ( + ( + + {children} + + ) + ); +} + +export { GridList as MyGridList }; + +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} + + )} + + ) + ); +} + +export { GridListItem as MyItem }; 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..7a7c37916d9 --- /dev/null +++ b/starters/docs/src/Menu.tsx @@ -0,0 +1,58 @@ +'use client'; +import { + Button, + Menu, + MenuItem as AriaMenuItem, + MenuItemProps, + MenuProps, + MenuTrigger, + MenuTriggerProps, + Popover +} from 'react-aria-components'; + +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 && ( + + + + )} + + )} + + ) + ); +} + +export { MenuItem as MyItem }; 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..9a86d71b9f4 --- /dev/null +++ b/starters/docs/src/NumberField.css @@ -0,0 +1,89 @@ +@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; + + &[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..15cb3d49523 --- /dev/null +++ b/starters/docs/src/NumberField.tsx @@ -0,0 +1,39 @@ +'use client'; +import { + Button, + FieldError, + Group, + Input, + Label, + NumberField as AriaNumberField, + NumberFieldProps as AriaNumberFieldProps, + Text, + ValidationResult +} from 'react-aria-components'; + +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..c29560d9c67 --- /dev/null +++ b/starters/docs/src/Popover.tsx @@ -0,0 +1,30 @@ +'use client'; +import { + Dialog, + OverlayArrow, + Popover as AriaPopover, + PopoverProps as AriaPopoverProps +} from 'react-aria-components'; + +import './Popover.css'; + +export interface PopoverProps extends Omit { + children: React.ReactNode; +} + +export function Popover({ children, ...props }: PopoverProps) { + return ( + ( + + + + + + + + {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..b479046ca69 --- /dev/null +++ b/starters/docs/src/RangeCalendar.tsx @@ -0,0 +1,38 @@ +'use client'; +import { + Button, + CalendarCell, + CalendarGrid, + DateValue, + Heading, + RangeCalendar as AriaRangeCalendar, + RangeCalendarProps as AriaRangeCalendarProps, + Text +} from 'react-aria-components'; + +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..a49dcda31ed --- /dev/null +++ b/starters/docs/src/SearchField.tsx @@ -0,0 +1,38 @@ +'use client'; +import { + Button, + FieldError, + Input, + Label, + SearchField as AriaSearchField, + SearchFieldProps as AriaSearchFieldProps, + Text, + ValidationResult +} from 'react-aria-components'; + +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} + + ) + ); +} + +export { SearchField as MySearchField }; diff --git a/starters/docs/src/Select.css b/starters/docs/src/Select.css new file mode 100644 index 00000000000..8b5710a5132 --- /dev/null +++ b/starters/docs/src/Select.css @@ -0,0 +1,142 @@ +@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 { + &[data-placeholder] { + font-style: italic; + color: var(--text-color-placeholder); + } + } + + span[aria-hidden] { + width: 1.3125rem; + line-height: 1.203125rem; + margin-left: 0.875rem; + padding: 1px; + background: var(--highlight-background); + color: var(--highlight-foreground); + forced-color-adjust: none; + border-radius: 4px; + font-size: 0.749875rem; + } +} + +.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..4b5c20383bf --- /dev/null +++ b/starters/docs/src/Select.tsx @@ -0,0 +1,59 @@ +'use client'; +import { + Button, + FieldError, + Label, + ListBox, + ListBoxItem, + ListBoxItemProps, + Popover, + Select as AriaSelect, + SelectProps as AriaSelectProps, + SelectValue, + Text, + ValidationResult +} from 'react-aria-components'; + +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 { Select as MySelect }; + +export function SelectItem(props: ListBoxItemProps) { + return ; +} + +export { SelectItem as MyItem }; 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..15d7b19ba5f --- /dev/null +++ b/starters/docs/src/Table.css @@ -0,0 +1,269 @@ +@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 { + .sort-indicator { + padding: 0 2px; + } + + &: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..36819d24758 --- /dev/null +++ b/starters/docs/src/Table.tsx @@ -0,0 +1,98 @@ +'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 {MyCheckbox} from './Checkbox'; + +import './Table.css'; + +export function Table(props: TableProps) { + return ; +} + +export { Table as MyTable }; + +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 { TableHeader as MyTableHeader }; + +export function Row( + { id, columns, children, ...otherProps }: RowProps +) { + let { selectionBehavior, allowsDragging } = useTableOptions(); + + return ( + ( + + {allowsDragging && ( + + + + )} + {selectionBehavior === 'toggle' && ( + + + + )} + + {children} + + + ) + ); +} + +export { Row as MyRow }; 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..a653b2afec9 --- /dev/null +++ b/starters/docs/src/TagGroup.css @@ -0,0 +1,99 @@ +@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: none; + padding: 0; + margin-left: 2px; + color: var(--text-color-base); + transition: color 200ms; + outline: none; + font-size: 0.95em; + border-radius: 100%; + aspect-ratio: 1/1; + height: 100%; + + &[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..b9a2d3ab802 --- /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 './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..8727ce0aabd --- /dev/null +++ b/starters/docs/src/ToggleButton.css @@ -0,0 +1,44 @@ +@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; + + &[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..9ea315227dc --- /dev/null +++ b/starters/docs/src/Tree.css @@ -0,0 +1,203 @@ +@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; + width: 12px; + height: 12px; + 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..6100f68256a --- /dev/null +++ b/starters/docs/src/Tree.tsx @@ -0,0 +1,64 @@ +'use client'; +import { + Button, + Tree as AriaTree, + TreeItem as AriaTreeItem, + TreeItemContent as AriaTreeItemContent, + TreeItemContentProps, + TreeItemContentRenderProps, + TreeItemProps as AriaTreeItemProps, + TreeProps +} from 'react-aria-components'; + +import {MyCheckbox} from './Checkbox'; + +import './Tree.css'; + +export function Tree(props: TreeProps) { + return ; +} + +export function TreeItemContent( + props: Omit & { children?: React.ReactNode } +) { + return ( + ( + + {( + { hasChildItems, 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..a157d8a8c9c --- /dev/null +++ b/starters/docs/stories/Autocomplete.stories.tsx @@ -0,0 +1,32 @@ +import {Autocomplete} from '../src/Autocomplete'; +import {MenuItem} from 'react-aria-components'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Autocomplete, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + 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..8c669c9fb34 --- /dev/null +++ b/starters/docs/stories/Breadcrumbs.stories.tsx @@ -0,0 +1,28 @@ +import {Breadcrumbs} from '../src/Breadcrumbs'; +import {Breadcrumb, Link} from 'react-aria-components'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Breadcrumbs, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + + 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..ae493195070 --- /dev/null +++ b/starters/docs/stories/Button.stories.tsx @@ -0,0 +1,19 @@ +import {Button} from '../src/Button'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Button, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ; + +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..c8ff950cd22 --- /dev/null +++ b/starters/docs/stories/Calendar.stories.tsx @@ -0,0 +1,17 @@ +import {Calendar} from '../src/Calendar'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Calendar, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + +); diff --git a/starters/docs/stories/Checkbox.stories.tsx b/starters/docs/stories/Checkbox.stories.tsx new file mode 100644 index 00000000000..626d27b0baa --- /dev/null +++ b/starters/docs/stories/Checkbox.stories.tsx @@ -0,0 +1,16 @@ +import {Checkbox} from '../src/Checkbox'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Checkbox, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => Unsubscribe +; diff --git a/starters/docs/stories/CheckboxGroup.stories.tsx b/starters/docs/stories/CheckboxGroup.stories.tsx new file mode 100644 index 00000000000..d0fc3b4ff4a --- /dev/null +++ b/starters/docs/stories/CheckboxGroup.stories.tsx @@ -0,0 +1,26 @@ +import {Checkbox} from '../src/Checkbox'; +import {CheckboxGroup} from '../src/CheckboxGroup'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: CheckboxGroup, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + 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..c51a1ce3eec --- /dev/null +++ b/starters/docs/stories/ColorArea.stories.tsx @@ -0,0 +1,19 @@ +import {ColorArea} from '../src/ColorArea'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: ColorArea, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ; + +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..7413037d604 --- /dev/null +++ b/starters/docs/stories/ColorField.stories.tsx @@ -0,0 +1,19 @@ +import {ColorField} from '../src/ColorField'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: ColorField, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ; + +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..9f87f524268 --- /dev/null +++ b/starters/docs/stories/ColorPicker.stories.tsx @@ -0,0 +1,20 @@ +import {ColorPicker} from '../src/ColorPicker'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: ColorPicker, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ; + +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..95810f339c8 --- /dev/null +++ b/starters/docs/stories/ColorSlider.stories.tsx @@ -0,0 +1,21 @@ +import {ColorSlider} from '../src/ColorSlider'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: ColorSlider, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ; + +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..9bba97407a6 --- /dev/null +++ b/starters/docs/stories/ColorSwatch.stories.tsx @@ -0,0 +1,19 @@ +import {ColorSwatch} from '../src/ColorSwatch'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: ColorSwatch, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ; + +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..d70243ff37a --- /dev/null +++ b/starters/docs/stories/ColorSwatchPicker.stories.tsx @@ -0,0 +1,27 @@ +import { + ColorSwatchPicker, + ColorSwatchPickerItem +} from '../src/ColorSwatchPicker'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: ColorSwatchPicker, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + + + + + + + +); diff --git a/starters/docs/stories/ColorWheel.stories.tsx b/starters/docs/stories/ColorWheel.stories.tsx new file mode 100644 index 00000000000..c4f7674a63e --- /dev/null +++ b/starters/docs/stories/ColorWheel.stories.tsx @@ -0,0 +1,19 @@ +import {ColorWheel} from '../src/ColorWheel'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: ColorWheel, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ; + +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..52ef8fdc72e --- /dev/null +++ b/starters/docs/stories/ComboBox.stories.tsx @@ -0,0 +1,26 @@ +import {ComboBox, ComboBoxItem} from '../src/ComboBox'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: ComboBox, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + 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..d6515c798d8 --- /dev/null +++ b/starters/docs/stories/DateField.stories.tsx @@ -0,0 +1,19 @@ +import {DateField} from '../src/DateField'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: DateField, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ; + +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..445bff49949 --- /dev/null +++ b/starters/docs/stories/DatePicker.stories.tsx @@ -0,0 +1,19 @@ +import {DatePicker} from '../src/DatePicker'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: DatePicker, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ; + +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..4b19bc7bf92 --- /dev/null +++ b/starters/docs/stories/DateRangePicker.stories.tsx @@ -0,0 +1,19 @@ +import {DateRangePicker} from '../src/DateRangePicker'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: DateRangePicker, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ; + +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..67ffa9bc6cd --- /dev/null +++ b/starters/docs/stories/Dialog.stories.tsx @@ -0,0 +1,46 @@ +import {Dialog} from '../src/Dialog'; +import { + Button, + DialogTrigger, + Heading, + Input, + Label, + Modal, + TextField +} from 'react-aria-components'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Dialog, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + + + +
+ Sign up + + + + + + + + + +
+
+
+
+); diff --git a/starters/docs/stories/Disclosure.stories.tsx b/starters/docs/stories/Disclosure.stories.tsx new file mode 100644 index 00000000000..3f06c697b95 --- /dev/null +++ b/starters/docs/stories/Disclosure.stories.tsx @@ -0,0 +1,21 @@ +import {Disclosure} from '../src/Disclosure'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Disclosure, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + 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..29d8d7d9878 --- /dev/null +++ b/starters/docs/stories/DisclosureGroup.stories.tsx @@ -0,0 +1,54 @@ +import {DisclosureGroup} from '../src/DisclosureGroup'; +import { + Button, + Disclosure, + DisclosurePanel, + Heading +} from 'react-aria-components'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: DisclosureGroup, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + + + + + +

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..c823e9d841b --- /dev/null +++ b/starters/docs/stories/Form.stories.tsx @@ -0,0 +1,31 @@ +import {Form} from '../src/Form'; +import { + Button, + FieldError, + Input, + Label, + TextField +} from 'react-aria-components'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Form, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( +
+ + + + + + +
+); diff --git a/starters/docs/stories/GridList.stories.tsx b/starters/docs/stories/GridList.stories.tsx new file mode 100644 index 00000000000..98782ab66fd --- /dev/null +++ b/starters/docs/stories/GridList.stories.tsx @@ -0,0 +1,27 @@ +import {GridList, GridListItem} from '../src/GridList'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: GridList, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + Chocolate + Mint + Strawberry + Vanilla + +); + +Example.args = { + onAction: null, + selectionMode: 'multiple' +}; diff --git a/starters/docs/stories/Link.stories.tsx b/starters/docs/stories/Link.stories.tsx new file mode 100644 index 00000000000..5e4aefe44e4 --- /dev/null +++ b/starters/docs/stories/Link.stories.tsx @@ -0,0 +1,20 @@ +import {Link} from '../src/Link'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Link, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => 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..c9debc410ad --- /dev/null +++ b/starters/docs/stories/ListBox.stories.tsx @@ -0,0 +1,27 @@ +import {ListBox, ListBoxItem} from '../src/ListBox'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: ListBox, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + Chocolate + Mint + Strawberry + Vanilla + +); + +Example.args = { + onAction: null, + selectionMode: 'single' +}; diff --git a/starters/docs/stories/Menu.stories.tsx b/starters/docs/stories/Menu.stories.tsx new file mode 100644 index 00000000000..d7b4d7940ec --- /dev/null +++ b/starters/docs/stories/Menu.stories.tsx @@ -0,0 +1,22 @@ +import {MenuButton, MenuItem} from '../src/Menu'; +import {Menu} from 'react-aria-components'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Menu, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + Cut + Copy + Paste + +); diff --git a/starters/docs/stories/Meter.stories.tsx b/starters/docs/stories/Meter.stories.tsx new file mode 100644 index 00000000000..d6f2475b904 --- /dev/null +++ b/starters/docs/stories/Meter.stories.tsx @@ -0,0 +1,20 @@ +import {Meter} from '../src/Meter'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Meter, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ; + +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..c3b00b63a57 --- /dev/null +++ b/starters/docs/stories/Modal.stories.tsx @@ -0,0 +1,46 @@ +import {Modal} from '../src/Modal'; +import { + Button, + Dialog, + DialogTrigger, + Heading, + Input, + Label, + TextField +} from 'react-aria-components'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Modal, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + + + +
+ Sign up + + + + + + + + + +
+
+
+
+); diff --git a/starters/docs/stories/NumberField.stories.tsx b/starters/docs/stories/NumberField.stories.tsx new file mode 100644 index 00000000000..759dcc1129d --- /dev/null +++ b/starters/docs/stories/NumberField.stories.tsx @@ -0,0 +1,19 @@ +import {NumberField} from '../src/NumberField'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: NumberField, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ; + +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..9c5633334a2 --- /dev/null +++ b/starters/docs/stories/Popover.stories.tsx @@ -0,0 +1,24 @@ +import {Popover} from '../src/Popover'; +import {Button, DialogTrigger, Heading} from 'react-aria-components'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Popover, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + + + 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..740970334f6 --- /dev/null +++ b/starters/docs/stories/ProgressBar.stories.tsx @@ -0,0 +1,20 @@ +import {ProgressBar} from '../src/ProgressBar'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: ProgressBar, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ; + +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..175d9c09852 --- /dev/null +++ b/starters/docs/stories/RadioGroup.stories.tsx @@ -0,0 +1,26 @@ +import {RadioGroup} from '../src/RadioGroup'; +import {Radio} from 'react-aria-components'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: RadioGroup, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + 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..ea4fb9b1ba0 --- /dev/null +++ b/starters/docs/stories/RangeCalendar.stories.tsx @@ -0,0 +1,17 @@ +import {RangeCalendar} from '../src/RangeCalendar'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: RangeCalendar, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + +); diff --git a/starters/docs/stories/SearchField.stories.tsx b/starters/docs/stories/SearchField.stories.tsx new file mode 100644 index 00000000000..38a507a79e4 --- /dev/null +++ b/starters/docs/stories/SearchField.stories.tsx @@ -0,0 +1,19 @@ +import {SearchField} from '../src/SearchField'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: SearchField, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ; + +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..357a5c1f989 --- /dev/null +++ b/starters/docs/stories/Select.stories.tsx @@ -0,0 +1,26 @@ +import {Select, SelectItem} from '../src/Select'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Select, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + +); + +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..b2819763590 --- /dev/null +++ b/starters/docs/stories/Slider.stories.tsx @@ -0,0 +1,21 @@ +import {Slider} from '../src/Slider'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Slider, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ; + +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..46bdabab4e5 --- /dev/null +++ b/starters/docs/stories/Switch.stories.tsx @@ -0,0 +1,15 @@ +import {Switch} from '../src/Switch'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Switch, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => Wi-Fi; diff --git a/starters/docs/stories/Table.stories.tsx b/starters/docs/stories/Table.stories.tsx new file mode 100644 index 00000000000..01545d2edcc --- /dev/null +++ b/starters/docs/stories/Table.stories.tsx @@ -0,0 +1,46 @@ +import {Column, Row, Table, TableHeader} from '../src/Table'; +import {Cell, TableBody} from 'react-aria-components'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Table, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + + 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: null, + selectionMode: 'multiple' +}; diff --git a/starters/docs/stories/Tabs.stories.tsx b/starters/docs/stories/Tabs.stories.tsx new file mode 100644 index 00000000000..3de1c1f00f1 --- /dev/null +++ b/starters/docs/stories/Tabs.stories.tsx @@ -0,0 +1,33 @@ +import {Tabs} from '../src/Tabs'; +import {Tab, TabList, TabPanel} from 'react-aria-components'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Tabs, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + + 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..6fd93418f02 --- /dev/null +++ b/starters/docs/stories/TagGroup.stories.tsx @@ -0,0 +1,27 @@ +import {Tag, TagGroup} from '../src/TagGroup'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: TagGroup, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + 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..630a78d1c1c --- /dev/null +++ b/starters/docs/stories/TextField.stories.tsx @@ -0,0 +1,19 @@ +import {TextField} from '../src/TextField'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: TextField, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ; + +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..d32d80af230 --- /dev/null +++ b/starters/docs/stories/TimeField.stories.tsx @@ -0,0 +1,19 @@ +import {TimeField} from '../src/TimeField'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: TimeField, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ; + +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..5c95df9e8d3 --- /dev/null +++ b/starters/docs/stories/ToggleButton.stories.tsx @@ -0,0 +1,16 @@ +import {ToggleButton} from '../src/ToggleButton'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: ToggleButton, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => Pin +; diff --git a/starters/docs/stories/ToggleButtonGroup.stories.tsx b/starters/docs/stories/ToggleButtonGroup.stories.tsx new file mode 100644 index 00000000000..eb9f525be8e --- /dev/null +++ b/starters/docs/stories/ToggleButtonGroup.stories.tsx @@ -0,0 +1,22 @@ +import {ToggleButtonGroup} from '../src/ToggleButtonGroup'; +import {ToggleButton} from 'react-aria-components'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: ToggleButtonGroup, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + 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..8ab7a179a7e --- /dev/null +++ b/starters/docs/stories/Toolbar.stories.tsx @@ -0,0 +1,51 @@ +import {Toolbar} from '../src/Toolbar'; +import { + Button, + Checkbox, + Group, + Separator, + ToggleButton +} from 'react-aria-components'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Toolbar, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + + + 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..49e04319395 --- /dev/null +++ b/starters/docs/stories/Tooltip.stories.tsx @@ -0,0 +1,21 @@ +import {Tooltip} from '../src/Tooltip'; +import {Button, TooltipTrigger} from 'react-aria-components'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Tooltip, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + + Save + +); diff --git a/starters/docs/stories/Tree.stories.tsx b/starters/docs/stories/Tree.stories.tsx new file mode 100644 index 00000000000..4fb14e98ca1 --- /dev/null +++ b/starters/docs/stories/Tree.stories.tsx @@ -0,0 +1,32 @@ +import {Tree, TreeItem} from '../src/Tree'; + +import type {Meta} from '@storybook/react'; + +const meta: Meta = { + component: Tree, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + + + + + + + + + + +); + +Example.args = { + style: { height: '300px' }, + defaultExpandedKeys: ['documents', 'photos', 'project'] +}; From 6bd8d8b74b56eda3f19e27476688e45643fbbbb1 Mon Sep 17 00:00:00 2001 From: Robert Snow Date: Wed, 2 Jul 2025 13:34:44 +1000 Subject: [PATCH 02/15] use react 19 and runtime automatic --- starters/docs/.babelrc.json | 4 +++- starters/docs/.storybook/main.js | 1 + starters/docs/package.json | 7 +++++-- starters/docs/stories/Autocomplete.stories.tsx | 5 +++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/starters/docs/.babelrc.json b/starters/docs/.babelrc.json index 5f2aa6e1dba..6e4b5111428 100644 --- a/starters/docs/.babelrc.json +++ b/starters/docs/.babelrc.json @@ -1,7 +1,9 @@ { "sourceType": "unambiguous", "presets": [ - "@babel/preset-react", + ["@babel/preset-react", { + "runtime": "automatic" + }], "@babel/preset-typescript" ], "plugins": [] diff --git a/starters/docs/.storybook/main.js b/starters/docs/.storybook/main.js index a33ea3cd782..735ef0ad8c1 100644 --- a/starters/docs/.storybook/main.js +++ b/starters/docs/.storybook/main.js @@ -26,6 +26,7 @@ const excludedProps = new Set([ const config = { stories: ["../stories/*.stories.@(js|jsx|mjs|ts|tsx)"], addons: [ + getAbsolutePath("@storybook/addon-essentials"), getAbsolutePath("@storybook/addon-links"), getAbsolutePath("@storybook/addon-onboarding"), getAbsolutePath("@storybook/addon-interactions"), diff --git a/starters/docs/package.json b/starters/docs/package.json index 94b6ab27857..757503d9598 100644 --- a/starters/docs/package.json +++ b/starters/docs/package.json @@ -5,6 +5,7 @@ }, "devDependencies": { "@babel/preset-react": "^7.24.1", + "@storybook/addon-essentials": "^8.6.14", "@storybook/addon-interactions": "^8.6.14", "@storybook/addon-links": "^8.6.14", "@storybook/addon-onboarding": "^1.0.8", @@ -17,14 +18,16 @@ "@types/react-dom": "^18.3.0", "lightningcss-loader": "^2.1.0", "prop-types": "^15.8.1", - "react": "^18.2.0", + "react": "^19.1.0", "react-aria-components": "latest", - "react-dom": "^18.2.0", + "react-dom": "^19.1.0", "storybook": "^8.6.14", "storybook-dark-mode": "^4.0.2", "typescript": "5.3.3" }, "resolutions": { + "react": "19.1.0", + "react-dom": "19.1.0", "@types/mime": "3.0.4", "jackspeak": "2.1.1" } diff --git a/starters/docs/stories/Autocomplete.stories.tsx b/starters/docs/stories/Autocomplete.stories.tsx index a157d8a8c9c..50be16b1693 100644 --- a/starters/docs/stories/Autocomplete.stories.tsx +++ b/starters/docs/stories/Autocomplete.stories.tsx @@ -1,7 +1,7 @@ import {Autocomplete} from '../src/Autocomplete'; import {MenuItem} from 'react-aria-components'; -import type {Meta} from '@storybook/react'; +import type {Meta, StoryFn} from '@storybook/react'; const meta: Meta = { component: Autocomplete, @@ -12,8 +12,9 @@ const meta: Meta = { }; export default meta; +type Story = StoryFn; -export const Example = (args: any) => ( +export const Example: Story = (args) => ( Create new file... Create new folder... From c45fd917fb2ad3a9cafcf029a30b41b8ece070f0 Mon Sep 17 00:00:00 2001 From: Robert Snow Date: Wed, 2 Jul 2025 14:28:49 +1000 Subject: [PATCH 03/15] use story types and remove extraneous My* exports --- starters/docs/.gitignore | 2 + starters/docs/src/Checkbox.tsx | 2 - starters/docs/src/CheckboxGroup.tsx | 1 - starters/docs/src/ColorArea.tsx | 2 - starters/docs/src/ColorField.tsx | 2 - starters/docs/src/ColorPicker.tsx | 16 +- starters/docs/src/ColorSlider.tsx | 2 - starters/docs/src/ColorSwatch.tsx | 2 - starters/docs/src/ColorSwatchPicker.tsx | 8 +- starters/docs/src/ColorWheel.tsx | 2 - starters/docs/src/GridList.tsx | 8 +- starters/docs/src/Menu.tsx | 2 - starters/docs/src/SearchField.tsx | 2 - starters/docs/src/Select.tsx | 4 - starters/docs/src/Table.tsx | 12 +- starters/docs/src/Tree.tsx | 6 +- starters/docs/stories/Breadcrumbs.stories.tsx | 5 +- starters/docs/stories/Button.stories.tsx | 5 +- starters/docs/stories/Calendar.stories.tsx | 5 +- starters/docs/stories/Checkbox.stories.tsx | 5 +- .../docs/stories/CheckboxGroup.stories.tsx | 5 +- starters/docs/stories/ColorArea.stories.tsx | 5 +- starters/docs/stories/ColorField.stories.tsx | 5 +- starters/docs/stories/ColorPicker.stories.tsx | 5 +- starters/docs/stories/ColorSlider.stories.tsx | 5 +- starters/docs/stories/ColorSwatch.stories.tsx | 5 +- .../stories/ColorSwatchPicker.stories.tsx | 5 +- starters/docs/stories/ColorWheel.stories.tsx | 5 +- starters/docs/stories/ComboBox.stories.tsx | 5 +- starters/docs/stories/DateField.stories.tsx | 5 +- starters/docs/stories/DatePicker.stories.tsx | 5 +- .../docs/stories/DateRangePicker.stories.tsx | 5 +- starters/docs/stories/Dialog.stories.tsx | 5 +- starters/docs/stories/Disclosure.stories.tsx | 5 +- .../docs/stories/DisclosureGroup.stories.tsx | 5 +- starters/docs/stories/Form.stories.tsx | 5 +- starters/docs/stories/GridList.stories.tsx | 7 +- starters/docs/stories/Link.stories.tsx | 5 +- starters/docs/stories/ListBox.stories.tsx | 7 +- starters/docs/stories/Menu.stories.tsx | 7 +- starters/docs/stories/Meter.stories.tsx | 5 +- starters/docs/stories/Modal.stories.tsx | 5 +- starters/docs/stories/NumberField.stories.tsx | 5 +- starters/docs/stories/Popover.stories.tsx | 5 +- starters/docs/stories/ProgressBar.stories.tsx | 5 +- starters/docs/stories/RadioGroup.stories.tsx | 6 +- .../docs/stories/RangeCalendar.stories.tsx | 6 +- starters/docs/stories/SearchField.stories.tsx | 6 +- starters/docs/stories/Select.stories.tsx | 6 +- starters/docs/stories/Slider.stories.tsx | 6 +- starters/docs/stories/Switch.stories.tsx | 5 +- starters/docs/stories/Table.stories.tsx | 8 +- starters/docs/stories/Tabs.stories.tsx | 6 +- starters/docs/stories/TagGroup.stories.tsx | 6 +- starters/docs/stories/TextField.stories.tsx | 6 +- starters/docs/stories/TimeField.stories.tsx | 6 +- .../docs/stories/ToggleButton.stories.tsx | 6 +- .../stories/ToggleButtonGroup.stories.tsx | 6 +- starters/docs/stories/Toolbar.stories.tsx | 6 +- starters/docs/stories/Tooltip.stories.tsx | 6 +- starters/docs/stories/Tree.stories.tsx | 6 +- starters/docs/yarn.lock | 8178 +++++++++++++++++ 62 files changed, 8352 insertions(+), 147 deletions(-) diff --git a/starters/docs/.gitignore b/starters/docs/.gitignore index 20687473be0..47a805e9ea8 100644 --- a/starters/docs/.gitignore +++ b/starters/docs/.gitignore @@ -1 +1,3 @@ storybook-static +yarn.lock +.yarn/install-state.gz diff --git a/starters/docs/src/Checkbox.tsx b/starters/docs/src/Checkbox.tsx index 1c50c83b124..01ece6db704 100644 --- a/starters/docs/src/Checkbox.tsx +++ b/starters/docs/src/Checkbox.tsx @@ -27,5 +27,3 @@ export function Checkbox( ) ); } - -export { Checkbox as MyCheckbox }; diff --git a/starters/docs/src/CheckboxGroup.tsx b/starters/docs/src/CheckboxGroup.tsx index adb1e044670..3b92109e20a 100644 --- a/starters/docs/src/CheckboxGroup.tsx +++ b/starters/docs/src/CheckboxGroup.tsx @@ -2,7 +2,6 @@ import { CheckboxGroup as AriaCheckboxGroup, CheckboxGroupProps as AriaCheckboxGroupProps, - CheckboxProps as AriaCheckboxProps, FieldError, Label, Text, diff --git a/starters/docs/src/ColorArea.tsx b/starters/docs/src/ColorArea.tsx index f53bb349706..0bae4afc0aa 100644 --- a/starters/docs/src/ColorArea.tsx +++ b/starters/docs/src/ColorArea.tsx @@ -16,5 +16,3 @@ export function ColorArea(props: ColorAreaProps) { ) ); } - -export { ColorArea as MyColorArea }; diff --git a/starters/docs/src/ColorField.tsx b/starters/docs/src/ColorField.tsx index 5b4fb4a3c99..26701d9be48 100644 --- a/starters/docs/src/ColorField.tsx +++ b/starters/docs/src/ColorField.tsx @@ -31,5 +31,3 @@ export function ColorField( ) ); } - -export { ColorField as MyColorField }; diff --git a/starters/docs/src/ColorPicker.tsx b/starters/docs/src/ColorPicker.tsx index 6e1f6e9a53d..5e82cb49737 100644 --- a/starters/docs/src/ColorPicker.tsx +++ b/starters/docs/src/ColorPicker.tsx @@ -7,10 +7,10 @@ import { DialogTrigger, Popover } from 'react-aria-components'; -import {MyColorSwatch} from './ColorSwatch'; -import {MyColorSlider} from './ColorSlider'; -import {MyColorArea} from './ColorArea'; -import {MyColorField} from './ColorField'; +import {ColorSwatch} from './ColorSwatch'; +import {ColorSlider} from './ColorSlider'; +import {ColorArea} from './ColorArea'; +import {ColorField} from './ColorField'; import './ColorPicker.css'; @@ -25,20 +25,20 @@ export function ColorPicker({ label, children, ...props }: ColorPickerProps) { {children || ( <> - - - + + )} diff --git a/starters/docs/src/ColorSlider.tsx b/starters/docs/src/ColorSlider.tsx index 0dce899e145..7c6c3fc53bd 100644 --- a/starters/docs/src/ColorSlider.tsx +++ b/starters/docs/src/ColorSlider.tsx @@ -32,5 +32,3 @@ export function ColorSlider({ label, ...props }: ColorSliderProps) { ) ); } - -export { ColorSlider as MyColorSlider }; diff --git a/starters/docs/src/ColorSwatch.tsx b/starters/docs/src/ColorSwatch.tsx index 1944d8a79a3..bb434cc7cc1 100644 --- a/starters/docs/src/ColorSwatch.tsx +++ b/starters/docs/src/ColorSwatch.tsx @@ -19,5 +19,3 @@ export function ColorSwatch(props: ColorSwatchProps) { ) ); } - -export { ColorSwatch as MyColorSwatch }; diff --git a/starters/docs/src/ColorSwatchPicker.tsx b/starters/docs/src/ColorSwatchPicker.tsx index 43d5721348c..34b62a5e106 100644 --- a/starters/docs/src/ColorSwatchPicker.tsx +++ b/starters/docs/src/ColorSwatchPicker.tsx @@ -6,7 +6,7 @@ import { ColorSwatchPickerProps } from 'react-aria-components'; -import {MyColorSwatch} from './ColorSwatch'; +import {ColorSwatch} from './ColorSwatch'; import './ColorSwatchPicker.css'; @@ -22,16 +22,12 @@ export function ColorSwatchPicker( ); } -export { ColorSwatchPicker as MyColorSwatchPicker }; - export function ColorSwatchPickerItem(props: ColorSwatchPickerItemProps) { return ( ( - + ) ); } - -export { ColorSwatchPickerItem as MyColorSwatchPickerItem }; diff --git a/starters/docs/src/ColorWheel.tsx b/starters/docs/src/ColorWheel.tsx index f3251d2f92c..d5965505a77 100644 --- a/starters/docs/src/ColorWheel.tsx +++ b/starters/docs/src/ColorWheel.tsx @@ -20,5 +20,3 @@ export function ColorWheel(props: ColorWheelProps) { ) ); } - -export { ColorWheel as MyColorWheel }; diff --git a/starters/docs/src/GridList.tsx b/starters/docs/src/GridList.tsx index 7e405e5b82f..61c5d8ea6e7 100644 --- a/starters/docs/src/GridList.tsx +++ b/starters/docs/src/GridList.tsx @@ -6,7 +6,7 @@ import { GridListItemProps, GridListProps } from 'react-aria-components'; -import {MyCheckbox} from './Checkbox'; +import {Checkbox} from './Checkbox'; import './GridList.css'; @@ -22,8 +22,6 @@ export function GridList( ); } -export { GridList as MyGridList }; - export function GridListItem( { children, ...props }: Omit & { children?: React.ReactNode; @@ -38,7 +36,7 @@ export function GridListItem( {/* Add elements for drag and drop and selection. */} {allowsDragging && } {selectionMode === 'multiple' && selectionBehavior === 'toggle' && ( - + )} {children} @@ -47,5 +45,3 @@ export function GridListItem( ) ); } - -export { GridListItem as MyItem }; diff --git a/starters/docs/src/Menu.tsx b/starters/docs/src/Menu.tsx index 7a7c37916d9..ad564594c9b 100644 --- a/starters/docs/src/Menu.tsx +++ b/starters/docs/src/Menu.tsx @@ -54,5 +54,3 @@ export function MenuItem( ) ); } - -export { MenuItem as MyItem }; diff --git a/starters/docs/src/SearchField.tsx b/starters/docs/src/SearchField.tsx index a49dcda31ed..79ceb6e3005 100644 --- a/starters/docs/src/SearchField.tsx +++ b/starters/docs/src/SearchField.tsx @@ -34,5 +34,3 @@ export function SearchField( ) ); } - -export { SearchField as MySearchField }; diff --git a/starters/docs/src/Select.tsx b/starters/docs/src/Select.tsx index 4b5c20383bf..f66b2071a5e 100644 --- a/starters/docs/src/Select.tsx +++ b/starters/docs/src/Select.tsx @@ -50,10 +50,6 @@ export function Select( ); } -export { Select as MySelect }; - export function SelectItem(props: ListBoxItemProps) { return ; } - -export { SelectItem as MyItem }; diff --git a/starters/docs/src/Table.tsx b/starters/docs/src/Table.tsx index 36819d24758..c33ae5ef3bd 100644 --- a/starters/docs/src/Table.tsx +++ b/starters/docs/src/Table.tsx @@ -13,7 +13,7 @@ import { TableProps, useTableOptions } from 'react-aria-components'; -import {MyCheckbox} from './Checkbox'; +import {Checkbox} from './Checkbox'; import './Table.css'; @@ -21,8 +21,6 @@ export function Table(props: TableProps) { return ; } -export { Table as MyTable }; - export function Column( props: Omit & { children?: React.ReactNode } ) { @@ -56,7 +54,7 @@ export function TableHeader( {allowsDragging && } {selectionBehavior === 'toggle' && ( - {selectionMode === 'multiple' && } + {selectionMode === 'multiple' && } )} @@ -67,8 +65,6 @@ export function TableHeader( ); } -export { TableHeader as MyTableHeader }; - export function Row( { id, columns, children, ...otherProps }: RowProps ) { @@ -84,7 +80,7 @@ export function Row( )} {selectionBehavior === 'toggle' && ( - + )} @@ -94,5 +90,3 @@ export function Row( ) ); } - -export { Row as MyRow }; diff --git a/starters/docs/src/Tree.tsx b/starters/docs/src/Tree.tsx index 6100f68256a..0ba304da735 100644 --- a/starters/docs/src/Tree.tsx +++ b/starters/docs/src/Tree.tsx @@ -10,7 +10,7 @@ import { TreeProps } from 'react-aria-components'; -import {MyCheckbox} from './Checkbox'; +import {Checkbox} from './Checkbox'; import './Tree.css'; @@ -25,13 +25,13 @@ export function TreeItemContent( ( {( - { hasChildItems, selectionBehavior, selectionMode, allowsDragging }: + { selectionBehavior, selectionMode, allowsDragging }: TreeItemContentRenderProps ) => ( <> {allowsDragging && } {selectionBehavior === 'toggle' && selectionMode !== 'none' && ( - + )}