diff --git a/.circleci/config.yml b/.circleci/config.yml index 26b0f100c9c..c2b8ad22988 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,14 +16,14 @@ parameters: executors: rsp: docker: - - image: cimg/node:22.13.1 + - image: cimg/node:22.21.1 environment: CACHE_VERSION: v1 working_directory: ~/react-spectrum rsp-large: docker: - - image: cimg/node:22.13.1 + - image: cimg/node:22.21.1 resource_class: large environment: CACHE_VERSION: v1 @@ -31,7 +31,7 @@ executors: rsp-xlarge: docker: - - image: cimg/node:22.13.1 + - image: cimg/node:22.21.1 resource_class: xlarge environment: CACHE_VERSION: v1 @@ -39,7 +39,7 @@ executors: rsp-2xlarge: docker: - - image: cimg/node:22.13.1 + - image: cimg/node:22.21.1 resource_class: 2xlarge environment: CACHE_VERSION: v1 diff --git a/packages/@react-spectrum/s2/src/Tabs.tsx b/packages/@react-spectrum/s2/src/Tabs.tsx index e3af2c8fb9b..f6646dcc4aa 100644 --- a/packages/@react-spectrum/s2/src/Tabs.tsx +++ b/packages/@react-spectrum/s2/src/Tabs.tsx @@ -448,7 +448,11 @@ const tabPanel = style({ marginTop: 4, color: 'gray-800', flexGrow: 1, - minHeight: 0 + minHeight: 0, + display: { + default: 'block', + isInert: 'none' + } }, getAllowedOverrides({height: true})); export function TabPanel(props: TabPanelProps): ReactNode | null { diff --git a/packages/dev/s2-docs/pages/react-aria/Calendar.mdx b/packages/dev/s2-docs/pages/react-aria/Calendar.mdx index 4325cdc604c..37de09881a0 100644 --- a/packages/dev/s2-docs/pages/react-aria/Calendar.mdx +++ b/packages/dev/s2-docs/pages/react-aria/Calendar.mdx @@ -85,13 +85,15 @@ import type {AnyCalendarDate} from '@internationalized/date'; import {CalendarDate, startOfWeek, toCalendar, GregorianCalendar} from '@internationalized/date'; import {Calendar} from 'vanilla-starter/Calendar'; -export default ( - new Custom454()} /> - ///- end highlight -/// -); +export default function Example() { + return ( + new Custom454()} /> + ///- end highlight -/// + ); +} // See @internationalized/date docs linked above. ///- begin collapse -/// diff --git a/packages/dev/s2-docs/pages/react-aria/DropZone.mdx b/packages/dev/s2-docs/pages/react-aria/DropZone.mdx index bbba85ff6c2..74d53895a07 100644 --- a/packages/dev/s2-docs/pages/react-aria/DropZone.mdx +++ b/packages/dev/s2-docs/pages/react-aria/DropZone.mdx @@ -86,6 +86,7 @@ export const tags = ['file', 'drag', 'dnd', 'upload']; ); + } ``` diff --git a/packages/dev/s2-docs/pages/react-aria/RangeCalendar.mdx b/packages/dev/s2-docs/pages/react-aria/RangeCalendar.mdx index 7d746d4392b..b189c1b42d1 100644 --- a/packages/dev/s2-docs/pages/react-aria/RangeCalendar.mdx +++ b/packages/dev/s2-docs/pages/react-aria/RangeCalendar.mdx @@ -95,13 +95,15 @@ import type {AnyCalendarDate} from '@internationalized/date'; import {CalendarDate, startOfWeek, toCalendar, GregorianCalendar} from '@internationalized/date'; import {RangeCalendar} from 'vanilla-starter/RangeCalendar'; -export default ( - new Custom454()} /> - ///- end highlight -/// -); +export default function Example() { + return ( + new Custom454()} /> + ///- end highlight -/// + ); +} // See @internationalized/date docs linked above. ///- begin collapse -/// diff --git a/packages/dev/s2-docs/pages/react-aria/examples/EmojiPicker/EmojiPicker.css b/packages/dev/s2-docs/pages/react-aria/examples/EmojiPicker.css similarity index 100% rename from packages/dev/s2-docs/pages/react-aria/examples/EmojiPicker/EmojiPicker.css rename to packages/dev/s2-docs/pages/react-aria/examples/EmojiPicker.css diff --git a/packages/dev/s2-docs/pages/react-aria/examples/EmojiPicker/EmojiPicker.tsx b/packages/dev/s2-docs/pages/react-aria/examples/EmojiPicker/EmojiPicker.tsx deleted file mode 100644 index 9ee9068c68e..00000000000 --- a/packages/dev/s2-docs/pages/react-aria/examples/EmojiPicker/EmojiPicker.tsx +++ /dev/null @@ -1,50 +0,0 @@ -"use client"; -import {Autocomplete, GridLayout, ListBox, ListBoxItem, Select, SelectValue, Size, useFilter, Virtualizer} from 'react-aria-components'; -import {Button} from 'vanilla-starter/Button'; -import {Popover} from 'vanilla-starter/Popover'; -import {SearchField} from 'vanilla-starter/SearchField'; -import _emojis from 'emojibase-data/en/compact.json'; -import './EmojiPicker.css'; - -const emojis = _emojis.filter((e) => !e.label.startsWith('regional indicator')); - -export function EmojiPicker() { - let {contains} = useFilter({ sensitivity: 'base' }); - - return ( - - ); -} - -function EmojiItem({ id, item }: { id: string; item: (typeof emojis)[0] }) { - return ( - - {item.unicode} - - ); -} diff --git a/packages/dev/s2-docs/pages/react-aria/examples/tabs/Tab.tsx b/packages/dev/s2-docs/pages/react-aria/examples/Tab.tsx similarity index 100% rename from packages/dev/s2-docs/pages/react-aria/examples/tabs/Tab.tsx rename to packages/dev/s2-docs/pages/react-aria/examples/Tab.tsx diff --git a/packages/dev/s2-docs/pages/react-aria/examples/tabs/TabList.tsx b/packages/dev/s2-docs/pages/react-aria/examples/TabList.tsx similarity index 100% rename from packages/dev/s2-docs/pages/react-aria/examples/tabs/TabList.tsx rename to packages/dev/s2-docs/pages/react-aria/examples/TabList.tsx diff --git a/packages/dev/s2-docs/pages/react-aria/examples/tabs/TabPanel.tsx b/packages/dev/s2-docs/pages/react-aria/examples/TabPanel.tsx similarity index 100% rename from packages/dev/s2-docs/pages/react-aria/examples/tabs/TabPanel.tsx rename to packages/dev/s2-docs/pages/react-aria/examples/TabPanel.tsx diff --git a/packages/dev/s2-docs/pages/react-aria/examples/tabs/TabPanelCarousel.tsx b/packages/dev/s2-docs/pages/react-aria/examples/TabPanelCarousel.tsx similarity index 100% rename from packages/dev/s2-docs/pages/react-aria/examples/tabs/TabPanelCarousel.tsx rename to packages/dev/s2-docs/pages/react-aria/examples/TabPanelCarousel.tsx diff --git a/packages/dev/s2-docs/pages/react-aria/examples/tabs/TabSelectionIndicator.tsx b/packages/dev/s2-docs/pages/react-aria/examples/TabSelectionIndicator.tsx similarity index 100% rename from packages/dev/s2-docs/pages/react-aria/examples/tabs/TabSelectionIndicator.tsx rename to packages/dev/s2-docs/pages/react-aria/examples/TabSelectionIndicator.tsx diff --git a/packages/dev/s2-docs/pages/react-aria/examples/tabs/Tabs.tsx b/packages/dev/s2-docs/pages/react-aria/examples/Tabs.tsx similarity index 100% rename from packages/dev/s2-docs/pages/react-aria/examples/tabs/Tabs.tsx rename to packages/dev/s2-docs/pages/react-aria/examples/Tabs.tsx diff --git a/packages/dev/s2-docs/pages/react-aria/examples/crud.mdx b/packages/dev/s2-docs/pages/react-aria/examples/crud.mdx index 9f0ad710b22..a95ecbf1479 100644 --- a/packages/dev/s2-docs/pages/react-aria/examples/crud.mdx +++ b/packages/dev/s2-docs/pages/react-aria/examples/crud.mdx @@ -3,7 +3,7 @@ export default Layout; import '../../../tailwind/tailwind.css'; -import {App} from './plants/App'; +import App from './plants/App'; import {ExampleApp} from '../../../src/ExampleApp'; import {ComponentList} from '../../../src/ComponentCard'; @@ -18,7 +18,7 @@ export const description = 'Table with search, filters, column resizing, and for - + ## Components diff --git a/packages/dev/s2-docs/pages/react-aria/examples/emoji-picker.mdx b/packages/dev/s2-docs/pages/react-aria/examples/emoji-picker.mdx index a242b406b84..71651f9095f 100644 --- a/packages/dev/s2-docs/pages/react-aria/examples/emoji-picker.mdx +++ b/packages/dev/s2-docs/pages/react-aria/examples/emoji-picker.mdx @@ -12,11 +12,57 @@ export const description = 'With autocomplete, virtualized scrolling, and keyboa An emoji picker with autocomplete, virtualized scrolling, and arrow key navigation. -```tsx render files={['packages/dev/s2-docs/pages/react-aria/examples/EmojiPicker/EmojiPicker.tsx', 'packages/dev/s2-docs/pages/react-aria/examples/EmojiPicker/EmojiPicker.css']} type="vanilla" hideExampleCode expanded includeAllImports +```tsx render dir="react-aria/examples" files={['packages/dev/s2-docs/pages/react-aria/examples/EmojiPicker.css']} type="vanilla" expanded includeAllImports "use client"; -import {EmojiPicker} from './EmojiPicker/EmojiPicker'; +import {Autocomplete, GridLayout, ListBox, ListBoxItem, Select, SelectValue, Size, useFilter, Virtualizer} from 'react-aria-components'; +import {Button} from 'vanilla-starter/Button'; +import {Popover} from 'vanilla-starter/Popover'; +import {SearchField} from 'vanilla-starter/SearchField'; +import _emojis from 'emojibase-data/en/compact.json'; +import './EmojiPicker.css'; - +const emojis = _emojis.filter((e) => !e.label.startsWith('regional indicator')); + +export default function EmojiPicker() { + let {contains} = useFilter({ sensitivity: 'base' }); + + return ( + + ); +} + +function EmojiItem({ id, item }: { id: string; item: (typeof emojis)[0] }) { + return ( + + {item.unicode} + + ); +} ``` ## Components diff --git a/packages/dev/s2-docs/pages/react-aria/examples/photos.mdx b/packages/dev/s2-docs/pages/react-aria/examples/photos.mdx index 502bb85f7c6..c1c7a744713 100644 --- a/packages/dev/s2-docs/pages/react-aria/examples/photos.mdx +++ b/packages/dev/s2-docs/pages/react-aria/examples/photos.mdx @@ -1,7 +1,7 @@ import {Layout} from '../../../src/Layout'; export default Layout; -import {App} from './photos/App'; +import App from './photos/App'; import {ExampleApp} from '../../../src/ExampleApp'; import {ComponentList} from '../../../src/ComponentCard'; diff --git a/packages/dev/s2-docs/pages/react-aria/examples/photos/App.css b/packages/dev/s2-docs/pages/react-aria/examples/photos/App.css index 629b3d4d444..fa901e5dc7d 100644 --- a/packages/dev/s2-docs/pages/react-aria/examples/photos/App.css +++ b/packages/dev/s2-docs/pages/react-aria/examples/photos/App.css @@ -7,6 +7,7 @@ padding: 0; color-scheme: light dark; font: 1rem system-ui; + color: light-dark(black, white); background: light-dark(white, #111); border: 1px solid light-dark(#eee, #222); border-radius: 10px; diff --git a/packages/dev/s2-docs/pages/react-aria/examples/photos/App.tsx b/packages/dev/s2-docs/pages/react-aria/examples/photos/App.tsx index eada54fa1e9..77a6da3314e 100644 --- a/packages/dev/s2-docs/pages/react-aria/examples/photos/App.tsx +++ b/packages/dev/s2-docs/pages/react-aria/examples/photos/App.tsx @@ -8,7 +8,7 @@ import {PhotoDetail} from './PhotoDetail'; type Photo = typeof photos[0]; -export function App() { +export default function App() { let [album, setAlbum] = useState('library'); let [library, setLibrary] = useState(photos); let [isMobile, setMobile] = useState(false); diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/App.tsx b/packages/dev/s2-docs/pages/react-aria/examples/plants/App.tsx index b922516411d..e167647027c 100644 --- a/packages/dev/s2-docs/pages/react-aria/examples/plants/App.tsx +++ b/packages/dev/s2-docs/pages/react-aria/examples/plants/App.tsx @@ -7,7 +7,7 @@ import {DialogTrigger, Heading, Key, Selection, SortDescriptor, TooltipTrigger} import {Dialog} from 'tailwind-starter/Dialog'; import {Menu, MenuItem, MenuTrigger} from 'tailwind-starter/Menu'; import {Modal} from 'tailwind-starter/Modal'; -import plants, {Plant} from '@react-spectrum/docs/pages/react-aria/home/plants'; +import plants, {Plant} from './plants'; import {Popover} from 'tailwind-starter/Popover'; import React, {useState} from 'react'; import {SearchField} from 'tailwind-starter/SearchField'; @@ -19,7 +19,7 @@ import {PlantTable} from './PlantTable'; import {PlantDialog} from './PlantDialog'; import {PlantList} from './PlantList'; -export function App(): React.ReactNode { +export default function App(): React.ReactNode { let [allItems, setAllItems] = useState(() => plants.map(p => ({...p, isFavorite: false}))); let [sortDescriptor, setSortDescriptor] = useState({ column: 'common_name', diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/Labels.tsx b/packages/dev/s2-docs/pages/react-aria/examples/plants/Labels.tsx index 6a2e4e1fd6b..d04e39e7bb4 100644 --- a/packages/dev/s2-docs/pages/react-aria/examples/plants/Labels.tsx +++ b/packages/dev/s2-docs/pages/react-aria/examples/plants/Labels.tsx @@ -1,6 +1,6 @@ -import { Plant } from "@react-spectrum/docs/pages/react-aria/home/plants"; -import { CloudSun, Dessert, Droplet, Droplets, RefreshCw, Sun, SunDim } from "lucide-react"; -import { ReactElement } from "react"; +import { Plant } from './plants'; +import { CloudSun, Dessert, Droplet, Droplets, RefreshCw, Sun, SunDim } from 'lucide-react'; +import { ReactElement } from 'react'; const labelStyles = { gray: 'bg-gray-100 text-gray-600 border-gray-200 dark:bg-zinc-700 dark:text-zinc-300 dark:border-zinc-600', diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/PlantActionMenu.tsx b/packages/dev/s2-docs/pages/react-aria/examples/plants/PlantActionMenu.tsx index 5f1d74cf040..0488dfb4988 100644 --- a/packages/dev/s2-docs/pages/react-aria/examples/plants/PlantActionMenu.tsx +++ b/packages/dev/s2-docs/pages/react-aria/examples/plants/PlantActionMenu.tsx @@ -1,7 +1,7 @@ import {Button} from 'tailwind-starter/Button'; import {Mail, MoreHorizontal, PencilIcon, ShareIcon, StarIcon, TrashIcon, Twitter} from 'lucide-react'; import {Menu, MenuItem, MenuTrigger, SubmenuTrigger} from 'tailwind-starter/Menu'; -import { Plant } from '@react-spectrum/docs/pages/react-aria/home/plants'; +import {Plant} from './plants'; interface PlantActionMenuProps { item: Plant, diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/PlantDialog.tsx b/packages/dev/s2-docs/pages/react-aria/examples/plants/PlantDialog.tsx index f32eb413a3f..a98e9b72e7a 100644 --- a/packages/dev/s2-docs/pages/react-aria/examples/plants/PlantDialog.tsx +++ b/packages/dev/s2-docs/pages/react-aria/examples/plants/PlantDialog.tsx @@ -6,7 +6,7 @@ import {Dialog} from 'tailwind-starter/Dialog'; import {DropZone} from 'tailwind-starter/DropZone'; import {Form} from 'tailwind-starter/Form'; import {getLocalTimeZone, today} from '@internationalized/date'; -import plants, {Plant} from '@react-spectrum/docs/pages/react-aria/home/plants'; +import plants, {Plant} from './plants'; import {Select, SelectItem} from 'tailwind-starter/Select'; import {TextField} from 'tailwind-starter/TextField'; import {cycleIcon, getSunlight, sunIcons, wateringIcons} from './Labels'; diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/PlantList.tsx b/packages/dev/s2-docs/pages/react-aria/examples/plants/PlantList.tsx index 734a578e5c7..72aa99d0898 100644 --- a/packages/dev/s2-docs/pages/react-aria/examples/plants/PlantList.tsx +++ b/packages/dev/s2-docs/pages/react-aria/examples/plants/PlantList.tsx @@ -1,6 +1,6 @@ "use client"; import {GridList, GridListItem} from 'tailwind-starter/GridList'; -import {Plant} from '@react-spectrum/docs/pages/react-aria/home/plants'; +import {Plant} from './plants'; import {PlantActionMenu} from './PlantActionMenu'; interface PlantListProps { diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/PlantTable.tsx b/packages/dev/s2-docs/pages/react-aria/examples/plants/PlantTable.tsx index 2c24fd21663..7ff2789a8b4 100644 --- a/packages/dev/s2-docs/pages/react-aria/examples/plants/PlantTable.tsx +++ b/packages/dev/s2-docs/pages/react-aria/examples/plants/PlantTable.tsx @@ -2,7 +2,7 @@ import {Cell, Column, Row, Table, TableHeader, TableBody} from 'tailwind-starter import {StarIcon} from 'lucide-react'; import {ColumnProps, Key, SortDescriptor, ToggleButton, ToggleButtonProps, VisuallyHidden} from 'react-aria-components'; import {focusRing} from 'tailwind-starter/utils'; -import {Plant} from '@react-spectrum/docs/pages/react-aria/home/plants'; +import {Plant} from './plants'; import React, {useMemo} from 'react'; import {tv} from 'tailwind-variants'; import {CycleLabel, getSunlight, SunLabel, WateringLabel} from './Labels'; diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants.ts b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants.ts new file mode 100644 index 00000000000..d5d334c336b --- /dev/null +++ b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants.ts @@ -0,0 +1,256 @@ +import agapanthus from 'url:./plants/agapanthus.jpg'; +import aloe from 'url:./plants/aloe.jpg'; +import dracaena from 'url:./plants/dracaena.jpg'; +import fern from 'url:./plants/fern.jpg'; +import fig from 'url:./plants/fig.jpg'; +import gardenia from 'url:./plants/gardenia.jpg'; +import ivy from 'url:./plants/ivy.jpg'; +import jacaranda from 'url:./plants/jacaranda.jpg'; +import maidenhair from 'url:./plants/maidenhair.jpg'; +import money from 'url:./plants/money.jpg'; +import monstera from 'url:./plants/monstera.jpg'; +import morning from 'url:./plants/morning.jpg'; +import nasturtium from 'url:./plants/nasturtium.jpg'; +import oleander from 'url:./plants/oleander.jpg'; +import poplar from 'url:./plants/poplar.jpg'; +import spider from 'url:./plants/spider.jpg'; +import star from 'url:./plants/star.jpg'; +import tree_fern from 'url:./plants/tree_fern.jpg'; +import xmas from 'url:./plants/xmas.jpg'; +import zz from 'url:./plants/zz.jpg'; + +export interface Plant { + id: number, + common_name: string, + scientific_name: string[], + watering: string, + sunlight: string[], + cycle: string, + default_image: { + thumbnail: string + }, + isFavorite?: boolean +} + +export default [ + { + id: 1, + common_name: 'Aloe', + scientific_name: ['Aloe vera'], + watering: 'Minimum', + sunlight: ['full sun'], + cycle: 'Perennial', + default_image: { + thumbnail: aloe + } + }, + { + id: 2, + common_name: 'Blue Jacaranda', + scientific_name: ['Jacaranda mimosifolia'], + watering: 'Minimum', + sunlight: ['full sun'], + cycle: 'Perennial', + default_image: { + thumbnail: jacaranda + } + }, + { + id: 3, + common_name: 'Oleander', + scientific_name: ['Nerium oleander'], + watering: 'Minimum', + sunlight: ['full sun'], + cycle: 'Perennial', + default_image: { + thumbnail: oleander + } + }, + { + id: 4, + common_name: 'Poplar', + scientific_name: ['Populus'], + watering: 'Average', + sunlight: ['full sun'], + cycle: 'Perennial', + default_image: { + thumbnail: poplar + } + }, + { + id: 5, + common_name: 'Zanzibar Gem', + scientific_name: ['Zamioculcas'], + watering: 'Average', + sunlight: ['part sun'], + cycle: 'Perennial', + default_image: { + thumbnail: zz + } + }, + { + id: 6, + common_name: 'Morning Glory', + scientific_name: ['Ipomoea'], + watering: 'Frequent', + sunlight: ['full sun'], + cycle: 'Perennial', + default_image: { + thumbnail: morning + } + }, + { + id: 7, + common_name: 'Christmas Bush', + scientific_name: ['Ceratopetalum gummiferum'], + watering: 'Average', + sunlight: ['full sun'], + cycle: 'Perennial', + default_image: { + thumbnail: xmas + } + }, + { + id: 8, + common_name: 'Gardenia', + scientific_name: ['Gardenia jasminoides'], + watering: 'Average', + sunlight: ['part sun'], + cycle: 'Perennial', + default_image: { + thumbnail: gardenia + } + }, + { + id: 9, + common_name: 'Spider Plant', + scientific_name: ['Chlorophytum comosum'], + watering: 'Average', + sunlight: ['part sun'], + cycle: 'Perennial', + default_image: { + thumbnail: spider + } + }, + { + id: 10, + common_name: 'Chinese Money Plant', + scientific_name: ['Pilea peperomioides'], + watering: 'Average', + sunlight: ['part sun'], + cycle: 'Perennial', + default_image: { + thumbnail: money + } + }, + { + id: 11, + common_name: 'Fiddle Leaf Fig', + scientific_name: ['Ficus lyrata'], + watering: 'Average', + sunlight: ['full sun'], + cycle: 'Perennial', + default_image: { + thumbnail: fig + } + }, + { + id: 12, + common_name: 'Tuberous Sword Fern', + scientific_name: ['Nephrolepis cordifolia'], + watering: 'Frequent', + sunlight: ['part shade'], + cycle: 'Perennial', + default_image: { + thumbnail: fern + } + }, + { + id: 13, + common_name: 'Star Jasmine', + scientific_name: ['Trachelospermum jasminoides'], + watering: 'Frequent', + sunlight: ['full sun'], + cycle: 'Perennial', + default_image: { + thumbnail: star + } + }, + { + id: 14, + common_name: 'Split-leaf Philodendron', + scientific_name: ['Monstera deliciosa'], + watering: 'Frequent', + sunlight: ['part shade'], + cycle: 'Perennial', + default_image: { + thumbnail: monstera + } + }, + { + id: 15, + common_name: 'Agapanthus', + scientific_name: ['Agapanthus praecox'], + watering: 'Minimum', + sunlight: ['full sun'], + cycle: 'Perennial', + default_image: { + thumbnail: agapanthus + } + }, + { + id: 16, + common_name: 'Tree Fern', + scientific_name: ['Cyatheaceae'], + watering: 'Frequent', + sunlight: ['part sun'], + cycle: 'Annual', + default_image: { + thumbnail: tree_fern + } + }, + { + id: 17, + common_name: 'Striped Dracaena', + scientific_name: ['Asparagaceae'], + watering: 'Average', + sunlight: ['part sun'], + cycle: 'Perennial', + default_image: { + thumbnail: dracaena + } + }, + { + id: 18, + common_name: 'Delta Maidenhair Fern', + scientific_name: ['Adiantum raddianum'], + watering: 'Average', + sunlight: ['part shade'], + cycle: 'Perennial', + default_image: { + thumbnail: maidenhair + } + }, + { + id: 19, + common_name: 'Ivy', + scientific_name: ['Hedera'], + watering: 'Frequent', + sunlight: ['part sun'], + cycle: 'Perennial', + default_image: { + thumbnail: ivy + } + }, + { + id: 20, + common_name: 'Nasturtium', + scientific_name: ['Tropaeolum'], + watering: 'Average', + sunlight: ['full sun'], + cycle: 'Annual', + default_image: { + thumbnail: nasturtium + } + } +] as Plant[]; diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/agapanthus.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/agapanthus.jpg new file mode 100644 index 00000000000..30b911f42f4 Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/agapanthus.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/aloe.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/aloe.jpg new file mode 100644 index 00000000000..35916497f82 Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/aloe.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/dracaena.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/dracaena.jpg new file mode 100644 index 00000000000..d44c74f8b21 Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/dracaena.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/fern.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/fern.jpg new file mode 100644 index 00000000000..487b7e6ae79 Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/fern.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/fig.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/fig.jpg new file mode 100644 index 00000000000..0ca0302ab81 Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/fig.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/gardenia.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/gardenia.jpg new file mode 100644 index 00000000000..0327d32be0f Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/gardenia.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/ivy.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/ivy.jpg new file mode 100644 index 00000000000..0603f515c5f Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/ivy.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/jacaranda.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/jacaranda.jpg new file mode 100644 index 00000000000..3c7878ac890 Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/jacaranda.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/maidenhair.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/maidenhair.jpg new file mode 100644 index 00000000000..55926b4fd70 Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/maidenhair.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/money.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/money.jpg new file mode 100644 index 00000000000..d59c43b179d Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/money.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/monstera.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/monstera.jpg new file mode 100644 index 00000000000..513a4d90b63 Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/monstera.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/morning.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/morning.jpg new file mode 100644 index 00000000000..e22b6448119 Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/morning.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/nasturtium.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/nasturtium.jpg new file mode 100644 index 00000000000..8e85a82deec Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/nasturtium.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/oleander.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/oleander.jpg new file mode 100644 index 00000000000..41febb94e85 Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/oleander.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/poplar.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/poplar.jpg new file mode 100644 index 00000000000..28feab44d66 Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/poplar.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/spider.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/spider.jpg new file mode 100644 index 00000000000..207699c004a Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/spider.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/star.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/star.jpg new file mode 100644 index 00000000000..95e1f9808ba Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/star.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/tree_fern.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/tree_fern.jpg new file mode 100644 index 00000000000..61f2c99c533 Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/tree_fern.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/xmas.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/xmas.jpg new file mode 100644 index 00000000000..8c8260a14fa Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/xmas.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/zz.jpg b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/zz.jpg new file mode 100644 index 00000000000..d1af3ce1c8a Binary files /dev/null and b/packages/dev/s2-docs/pages/react-aria/examples/plants/plants/zz.jpg differ diff --git a/packages/dev/s2-docs/pages/react-aria/examples/ripple-button.mdx b/packages/dev/s2-docs/pages/react-aria/examples/ripple-button.mdx index ee3dc1766ab..a5f4cb02242 100644 --- a/packages/dev/s2-docs/pages/react-aria/examples/ripple-button.mdx +++ b/packages/dev/s2-docs/pages/react-aria/examples/ripple-button.mdx @@ -13,7 +13,7 @@ export const description = 'A button with an animated ripple effect styled with A [Button](../Button.html) with an animated ripple effect styled with [Tailwind CSS](https://tailwindcss.com/). -```tsx render type="tailwind" expanded files={['packages/dev/s2-docs/pages/react-aria/examples/ripple.css']} +```tsx render type="tailwind" expanded dir="react-aria/examples" files={['packages/dev/s2-docs/pages/react-aria/examples/ripple.css']} "use client"; import {Button} from 'react-aria-components'; import {useState} from 'react'; diff --git a/packages/dev/s2-docs/pages/react-aria/examples/swipeable-tabs.mdx b/packages/dev/s2-docs/pages/react-aria/examples/swipeable-tabs.mdx index aeb78739888..b0639e51ff4 100644 --- a/packages/dev/s2-docs/pages/react-aria/examples/swipeable-tabs.mdx +++ b/packages/dev/s2-docs/pages/react-aria/examples/swipeable-tabs.mdx @@ -13,14 +13,14 @@ export const description = 'With CSS scroll snapping, scroll-driven animations, A swipeable [Tabs](../Tabs.html) component with [CSS scroll snapping](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_scroll_snap), [scroll-driven animations](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_scroll-driven_animations), and [anchor positioning](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_anchor_positioning). -```tsx render files={['packages/dev/s2-docs/pages/react-aria/examples/tabs/TabSelectionIndicator.tsx', 'packages/dev/s2-docs/pages/react-aria/examples/tabs/TabPanelCarousel.tsx', 'packages/dev/s2-docs/pages/react-aria/examples/tabs/Tabs.tsx', 'packages/dev/s2-docs/pages/react-aria/examples/tabs/TabList.tsx', 'packages/dev/s2-docs/pages/react-aria/examples/tabs/Tab.tsx', 'packages/dev/s2-docs/pages/react-aria/examples/tabs/TabPanel.tsx']} type="tailwind" expanded +```tsx render dir="react-aria/examples" files={['packages/dev/s2-docs/pages/react-aria/examples/TabSelectionIndicator.tsx', 'packages/dev/s2-docs/pages/react-aria/examples/TabPanelCarousel.tsx', 'packages/dev/s2-docs/pages/react-aria/examples/Tabs.tsx', 'packages/dev/s2-docs/pages/react-aria/examples/TabList.tsx', 'packages/dev/s2-docs/pages/react-aria/examples/Tab.tsx', 'packages/dev/s2-docs/pages/react-aria/examples/TabPanel.tsx']} type="tailwind" expanded "use client"; -import {Tabs} from './tabs/Tabs'; -import {TabList} from './tabs/TabList'; -import {Tab} from './tabs/Tab'; -import {TabPanel} from './tabs/TabPanel'; -import {TabSelectionIndicator} from './tabs/TabSelectionIndicator'; -import {TabPanelCarousel} from './tabs/TabPanelCarousel'; +import {Tabs} from './Tabs'; +import {TabList} from './TabList'; +import {Tab} from './Tab'; +import {TabPanel} from './TabPanel'; +import {TabSelectionIndicator} from './TabSelectionIndicator'; +import {TabPanelCarousel} from './TabPanelCarousel'; diff --git a/packages/dev/s2-docs/pages/s2/Calendar.mdx b/packages/dev/s2-docs/pages/s2/Calendar.mdx index 23ec382c20c..d06db8c2ffb 100644 --- a/packages/dev/s2-docs/pages/s2/Calendar.mdx +++ b/packages/dev/s2-docs/pages/s2/Calendar.mdx @@ -77,13 +77,15 @@ import type {AnyCalendarDate} from '@internationalized/date'; import {CalendarDate, startOfWeek, toCalendar, GregorianCalendar} from '@internationalized/date'; import {Calendar} from '@react-spectrum/s2'; -export default ( - new Custom454()} /> - ///- end highlight -/// -); +export default function Example() { + return ( + new Custom454()} /> + ///- end highlight -/// + ); +} // See @internationalized/date docs linked above. ///- begin collapse -/// diff --git a/packages/dev/s2-docs/pages/s2/Menu.mdx b/packages/dev/s2-docs/pages/s2/Menu.mdx index 248d3471389..52a030e27bb 100644 --- a/packages/dev/s2-docs/pages/s2/Menu.mdx +++ b/packages/dev/s2-docs/pages/s2/Menu.mdx @@ -143,7 +143,7 @@ import Paste from '@react-spectrum/s2/icons/Paste'; ``` -```tsx render +```tsx render type="s2" "use client"; import {MenuTrigger, ActionButton, Menu, MenuItem, Text, Image} from '@react-spectrum/s2'; import normal from 'url:./assets/normal.png'; diff --git a/packages/dev/s2-docs/pages/s2/RangeCalendar.mdx b/packages/dev/s2-docs/pages/s2/RangeCalendar.mdx index 519bac96393..a9df14504c0 100644 --- a/packages/dev/s2-docs/pages/s2/RangeCalendar.mdx +++ b/packages/dev/s2-docs/pages/s2/RangeCalendar.mdx @@ -88,13 +88,15 @@ import type {AnyCalendarDate} from '@internationalized/date'; import {CalendarDate, startOfWeek, toCalendar, GregorianCalendar} from '@internationalized/date'; import {RangeCalendar} from '@react-spectrum/s2'; -export default ( - new Custom454()} /> - ///- end highlight -/// -); +export default function Example() { + return ( + new Custom454()} /> + ///- end highlight -/// + ); +} // See @internationalized/date docs linked above. ///- begin collapse -/// diff --git a/packages/dev/s2-docs/pages/s2/Skeleton.mdx b/packages/dev/s2-docs/pages/s2/Skeleton.mdx index 2f20dcbcde1..e82018cb1af 100644 --- a/packages/dev/s2-docs/pages/s2/Skeleton.mdx +++ b/packages/dev/s2-docs/pages/s2/Skeleton.mdx @@ -9,7 +9,7 @@ export const tags = ['loading', 'placeholder', 'shimmer', 'ghost']; {docs.exports.Skeleton.description} -```tsx render docs={docs.exports.Skeleton} links={docs.links} props={['isLoading']} initialProps={{isLoading: true}} +```tsx render docs={docs.exports.Skeleton} links={docs.links} props={['isLoading']} initialProps={{isLoading: true}} type="s2" "use client"; import {Skeleton, Image, Heading, Text} from '@react-spectrum/s2' import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; diff --git a/packages/dev/s2-docs/src/BundlerSwitcher.tsx b/packages/dev/s2-docs/src/BundlerSwitcher.tsx index ef8366b1a95..c9e9e8a1c86 100644 --- a/packages/dev/s2-docs/src/BundlerSwitcher.tsx +++ b/packages/dev/s2-docs/src/BundlerSwitcher.tsx @@ -1,9 +1,10 @@ 'use client'; import {Key} from 'react-aria-components'; -import React, {Children, ReactElement, ReactNode, useEffect, useMemo, useState} from 'react'; +import React, {Children, ReactElement, ReactNode, useMemo} from 'react'; import {SegmentedControl, SegmentedControlItem} from '@react-spectrum/s2'; import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; +import {useLocalStorage} from './useLocalStorage'; type SwitcherKey = string; @@ -42,7 +43,6 @@ export function BundlerSwitcherItem(_props: BundlerSwitcherItemProps) { } export function BundlerSwitcher({children}: BundlerSwitcherProps) { - const storageKey = 'bundler'; let items = useMemo(() => { let arr = Children.toArray(children) as ReactElement[]; return arr @@ -54,36 +54,14 @@ export function BundlerSwitcher({children}: BundlerSwitcherProps) { })); }, [children]); - let initial = useMemo(() => { - let stored: string | null = null; - if (storageKey && typeof window !== 'undefined') { - stored = localStorage.getItem(storageKey); - } - let storedValid = items.find(it => it.id === stored)?.id; - if (storedValid) {return storedValid;} - return items[0]?.id; - }, [items, storageKey]); - - let [selected, setSelected] = useState(initial); - - useEffect(() => { - // Update selected if items change and current is no longer valid - if (selected && !items.find(it => it.id === selected)) { - setSelected(items[0]?.id); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [items]); + let [bundler, setBundler] = useLocalStorage('bundler', items[0]?.id); + let active = items.find(it => it.id === bundler) ?? items[0]; let onSelectionChange = (key: Key) => { let value = String(key) as SwitcherKey; - setSelected(value); - if (storageKey && typeof window !== 'undefined') { - localStorage.setItem(storageKey, value); - } + setBundler(value); }; - let active = items.find(it => it.id === selected) ?? items[0]; - return (
diff --git a/packages/dev/s2-docs/src/CodeBlock.tsx b/packages/dev/s2-docs/src/CodeBlock.tsx index b8f766ecc9b..78cde6de64e 100644 --- a/packages/dev/s2-docs/src/CodeBlock.tsx +++ b/packages/dev/s2-docs/src/CodeBlock.tsx @@ -1,11 +1,14 @@ +// @ts-ignore +import assets from 'url:../pages/**/*.{png,jpg,svg}'; +import {cache, ReactNode} from 'react'; import {Code, ICodeProps} from './Code'; -import {CodePlatter, Pre} from './CodePlatter'; +import {CodePlatter, FileProvider, Pre} from './CodePlatter'; import {ExampleOutput} from './ExampleOutput'; import {ExpandableCode} from './ExpandableCode'; +import {findPackageJSON} from 'module'; import fs from 'fs'; import {highlight, Language} from 'tree-sitter-highlight'; import path from 'path'; -import {ReactNode} from 'react'; import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; import {Tab, TabList, TabPanel, Tabs} from '@react-spectrum/s2'; import {VisualExample, VisualExampleProps} from './VisualExample'; @@ -52,32 +55,35 @@ const standaloneCode = style({ interface CodeBlockProps extends VisualExampleProps { render?: ReactNode, children: string, + dir?: string, files?: string[], expanded?: boolean, hidden?: boolean, - hideExampleCode?: boolean, includeAllImports?: boolean, showCoachMark?: boolean } -export function CodeBlock({render, children, files, expanded, hidden, hideExampleCode, includeAllImports, ...props}: CodeBlockProps) { +export function CodeBlock({render, children, dir, files, expanded, hidden, includeAllImports, ...props}: CodeBlockProps) { if (hidden) { return null; } - children = children.replace(/(vanilla-starter|tailwind-starter)\//g, './'); + let displayCode = children.replace(/(vanilla-starter|tailwind-starter)\//g, './'); if (!render) { return (
-        {children}
+        {displayCode}
       
); } + let resolveFrom = path.resolve('pages', dir || (props.type === 's2' ? 's2' : 'react-aria'), 'index.tsx'); + let downloadFiles = getExampleFiles(resolveFrom, children, props.type); + let code = ( - {children} + {displayCode} ); @@ -87,17 +93,19 @@ export function CodeBlock({render, children, files, expanded, hidden, hideExampl {...props} component={render} files={files} + downloadFiles={downloadFiles} code={code} /> ); } - let content = hideExampleCode ? null : ( - - {code} - + let content = ( + + + {code} + + ); return ( @@ -106,9 +114,14 @@ export function CodeBlock({render, children, files, expanded, hidden, hideExampl component={render} align={props.align} />
- {files - ? {content} - : content} + {files ? + + {content} + + : content}
); @@ -148,84 +161,148 @@ function TruncatedCode({children, maxLines = 6, ...props}: TruncatedCodeProps) { ); } -export function Files({children, files, defaultSelected, maxLines}: {children?: ReactNode, files: string[], defaultSelected?: string, maxLines?: number}) { - let allFiles = getFiles(files); +export function Files({children, files, type, defaultSelected, maxLines}: {children?: ReactNode, files: string[], type?: 'vanilla' | 'tailwind' | 's2', defaultSelected?: string, maxLines?: number}) { return ( + density="compact" + data-files> {children && Example} {files.map(file => {path.basename(file)})} - {children && {children}} - {files.map(file => )} + {children && {children}} + {files.map(file => )} ); } -export function File({filename, maxLines, files}: {filename: string, maxLines?: number, files?: {[name: string]: string}}) { - let contents = fs.readFileSync(path.isAbsolute(filename) ? filename : '../../../' + filename, 'utf8').replace(/(vanilla-starter|tailwind-starter)\//g, './'); +const readFile = cache((file: string) => fs.readFileSync(file, 'utf8')); + +export function File({filename, maxLines, type}: {filename: string, maxLines?: number, type?: 'vanilla' | 'tailwind' | 's2'}) { + let contents = readFile(path.isAbsolute(filename) ? filename : path.resolve('../../../', filename)).replace(/(vanilla-starter|tailwind-starter)\//g, './'); return ( - + {contents} ); } // Reads files, parses imports, and loads recursively. -export function getFiles(files: string[]) { - files = findAllFiles(files); +export function getFiles(files: string[], type: string | undefined, npmDeps = {}) { let fileContents = {}; - for (let file of files) { + for (let file of findAllFiles(files, npmDeps)) { let name = path.basename(file); - let contents = fs.readFileSync(file, 'utf8'); - fileContents[name] = contents.replace(/(vanilla-starter|tailwind-starter)\//g, './'); + let contents = readFile(file); + fileContents[name] = contents + .replace(/(vanilla-starter|tailwind-starter)\//g, './') + .replace(/import (.*?) from ['"]url:(.*?)['"]/g, (_, name, specifier) => { + return `const ${name} = '${resolveUrl(specifier, file)}'`; + }); + } + + if (type === 'tailwind' && !fileContents['index.css']) { + fileContents['index.css'] = readFile(path.resolve('../../../starters/tailwind/src/index.css')); } - return fileContents; + return {files: fileContents, deps: npmDeps}; } -function findAllFiles(files: string[]) { +function findAllFiles(files: string[], npmDeps = {}) { + files = files.map(file => path.isAbsolute(file) ? file : path.resolve('../../../', file)); + let queue: string[] = [...files]; - let allFiles = new Map(); + let allFiles = new Set(); for (let i = 0; i < queue.length; i++) { - let file = path.isAbsolute(queue[i]) ? queue[i] : path.resolve('../../../' + queue[i]); - if (path.extname(file) === '') { - if (fs.existsSync(file + '.tsx')) { - file += '.tsx'; - } else if (fs.existsSync(file + '.ts')) { - file += '.ts'; + let file = queue[i]; + let contents = readFile(file); + allFiles.add(file); + + let deps = parseFile(file, contents, npmDeps); + for (let dep of deps) { + if (!allFiles.has(dep)) { + queue.push(dep); } } + } - let name = path.basename(file); - let contents = fs.readFileSync(file, 'utf8'); - allFiles.set(name, file); + let addedFiles = [...allFiles.values()].filter(f => !files.includes(f)).sort(); + return [...files, ...addedFiles]; +} - for (let [, specifier] of contents.matchAll(/import(?:.|\n)+?['"](.+)['"]/g)) { - specifier = specifier.replace(/(vanilla-starter|tailwind-starter)\//g, (m, s) => 'starters/' + (s === 'vanilla-starter' ? 'docs' : 'tailwind') + '/src/'); - if (!/^(\.|starters)/.test(specifier)) { - continue; - } +function parseFile(file: string, contents: string, npmDeps = {}, urls = {}) { + let deps = new Set(); + for (let [, specifier] of contents.matchAll(/import (?:.|\n)*?['"](.+?)['"]/g)) { + specifier = specifier.replace(/(vanilla-starter|tailwind-starter)\//g, (m, s) => 'starters/' + (s === 'vanilla-starter' ? 'docs' : 'tailwind') + '/src/'); + + if (specifier.startsWith('url:')) { + urls[specifier] = resolveUrl(specifier.slice(4), file); + continue; + } + + if (!/^(\.|starters)/.test(specifier)) { + let dep = specifier.startsWith('@') ? specifier.split('/').slice(0, 2).join('/') : specifier.split('/')[0]; + npmDeps[dep] ??= '^' + getPackageVersion(dep); + continue; + } - let resolved = specifier.startsWith('.') ? path.resolve(path.dirname(file), specifier) : specifier; - if (!allFiles.has(path.basename(resolved))) { - queue.push(resolved); + let resolved = specifier.startsWith('.') ? path.resolve(path.dirname(file), specifier) : path.resolve('../../../' + specifier); + if (path.extname(resolved) === '') { + if (fs.existsSync(resolved + '.tsx')) { + resolved += '.tsx'; + } else if (fs.existsSync(resolved + '.ts')) { + resolved += '.ts'; } } + + deps.add(resolved); } - let providedFiles = files.map(f => { - let name = path.basename(f); - let res = allFiles.get(name)!; - allFiles.delete(name); - return res; - }); + return deps; +} + +function getExampleFiles(file: string, contents: string, type: string | undefined) { + let npmDeps = {}; + let urls = {}; + let fileDeps = parseFile(file, contents, npmDeps, urls); + let {files} = getFiles([...fileDeps], type, npmDeps); + return {files, deps: npmDeps, urls}; +} - let addedFiles = [...allFiles.values()].sort(); +let packageVersionCache = new Map(); +function getPackageVersion(pkg: string) { + let version = packageVersionCache.get(pkg); + if (version) { + return version; + } + + let p = findPackageJSON(pkg, __filename); + if (p) { + let json = JSON.parse(fs.readFileSync(p, 'utf8')); + packageVersionCache.set(pkg, json.version); + return json.version; + } else { + throw new Error('Could not find package.json for ' + pkg); + } +} + +function resolveUrl(specifier: string, file: string) { + let relative = path.relative(path.resolve('pages'), path.dirname(file)).split(/[/\\]/); + let cur = assets; + for (let part of [...relative, ...specifier.slice(2).split('/')]) { + let p = part.split('.'); + cur = cur[p[0]]; + if (!cur) { + throw new Error('Could not resolve URL ' + specifier); + } + + if (p[1]) { + cur = cur[p[1]]; + } + } - return [...providedFiles, ...addedFiles]; + let publicUrl = process.env.PUBLIC_URL || 'http://localhost:1234'; + return publicUrl + cur; } diff --git a/packages/dev/s2-docs/src/CodeFold.tsx b/packages/dev/s2-docs/src/CodeFold.tsx index e1441445739..befa0424a86 100644 --- a/packages/dev/s2-docs/src/CodeFold.tsx +++ b/packages/dev/s2-docs/src/CodeFold.tsx @@ -43,7 +43,7 @@ const more = style({ height: 16, display: 'inline-flex', alignItems: 'center', - verticalAlign: 'top', + verticalAlign: 'middle', userSelect: 'none', '--iconPrimary': { type: 'fill', diff --git a/packages/dev/s2-docs/src/CodePlatter.tsx b/packages/dev/s2-docs/src/CodePlatter.tsx index 6369f95b2bd..0c720512b7d 100644 --- a/packages/dev/s2-docs/src/CodePlatter.tsx +++ b/packages/dev/s2-docs/src/CodePlatter.tsx @@ -11,7 +11,7 @@ import LinkIcon from '@react-spectrum/s2/icons/Link'; import OpenIn from '@react-spectrum/s2/icons/OpenIn'; import Polygon4 from '@react-spectrum/s2/icons/Polygon4'; import Prompt from '@react-spectrum/s2/icons/Prompt'; -import React, {createContext, ReactNode, useContext, useRef, useState} from 'react'; +import React, {createContext, ProviderProps, ReactNode, RefObject, useContext, useRef, useState} from 'react'; import {ShadcnCommand} from './ShadcnCommand'; import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; import {zip} from './zip'; @@ -36,10 +36,7 @@ const platterStyle = style({ interface CodePlatterProps { children: ReactNode, - shareUrl?: string, - files?: {[name: string]: string}, type?: 'vanilla' | 'tailwind' | 's2', - registryUrl?: string, showCoachMark?: boolean } @@ -52,7 +49,29 @@ export function CodePlatterProvider(props: CodePlatterContextValue & {children: return {props.children}; } -export function CodePlatter({children, shareUrl, files, type, registryUrl, showCoachMark}: CodePlatterProps) { +interface FileProviderContextValue { + files?: {[name: string]: string}, + deps?: {[name: string]: string}, + urls?: {[url: string]: string}, + entry?: string +} + +const FileProviderContext = createContext(null); +export function FileProvider(props: ProviderProps) { + return ; +} + +const ShadcnContext = createContext(null); +export function ShadcnProvider(props: ProviderProps) { + return ; +} + +const ShareContext = createContext(null); +export function ShareUrlProvider(props: ProviderProps) { + return ; +} + +export function CodePlatter({children, type, showCoachMark}: CodePlatterProps) { let codeRef = useRef(null); let [showShadcn, setShowShadcn] = useState(false); let getText = () => codeRef.current!.querySelector('pre')!.textContent!; @@ -65,6 +84,10 @@ export function CodePlatter({children, shareUrl, files, type, registryUrl, showC } } + let {files, deps = {}, urls = {}, entry} = useContext(FileProviderContext) ?? {}; + let registryUrl = useContext(ShadcnContext); + let shareUrl = useContext(ShareContext); + return (
@@ -74,7 +97,7 @@ export function CodePlatter({children, shareUrl, files, type, registryUrl, showC density="regular" size="S"> - {(shareUrl || files || type || registryUrl) && + {(shareUrl || files || registryUrl) && @@ -103,17 +126,13 @@ export function CodePlatter({children, shareUrl, files, type, registryUrl, showC Copy link } - {(files || type) && + {files && { - let code = codeRef.current!.querySelector('pre')!.textContent!; - let filesToDownload = getCodeSandboxFiles({ - ...files, - 'Example.tsx': transformExampleCode(code) - }, type); + let filesToDownload = getCodeSandboxFiles(getExampleFiles(codeRef, files, urls, entry), deps, type, entry); let filesToZip = {}; for (let key in filesToDownload) { - if (filesToDownload[key]) { + if (filesToDownload[key] && !key.startsWith('.codesandbox') && !key.startsWith('.devcontainer')) { filesToZip[key] = filesToDownload[key].content; } } @@ -138,27 +157,19 @@ export function CodePlatter({children, shareUrl, files, type, registryUrl, showC Install with shadcn } - {(files || type) && + {files && { - let code = codeRef.current!.querySelector('pre')!.textContent!; - createCodeSandbox({ - ...files, - 'Example.tsx': transformExampleCode(code) - }, type); + createCodeSandbox(getExampleFiles(codeRef, files, urls, entry), deps, type, entry); }}> Open in CodeSandbox } - {(files || type) && type !== 's2' && + {files && type !== 's2' && { - let code = codeRef.current!.querySelector('pre')!.textContent!; - createStackBlitz({ - ...files, - 'Example.tsx': transformExampleCode(code) - }, type); + createStackBlitz(getExampleFiles(codeRef, files, urls, entry), deps, type, entry); }}> Open in StackBlitz @@ -211,23 +222,47 @@ export function Pre({children}) { ); } -function transformExampleCode(code: string): string { - // Export the last function - code = code.replace(/\nfunction ([^(]+)((.|\n)+\n\}\n?)$/, '\nexport function Example$2'); +function getExampleFiles(codeRef: RefObject, files: {[name: string]: string}, urls: {[name: string]: string}, entry: string | undefined) { + if (!entry) { + return { + ...files, + 'Example.tsx': getExampleCode(codeRef, urls) + }; + } - // Add function wrapper around raw JSX in examples. - return code.replace(/\n<((?:.|\n)+)/, (_, code) => { - let res = '\nexport function Example() {\n return (\n <'; - let lines = code.split('\n'); - res += lines.shift(); + return files; +} - for (let line of lines) { - res += '\n ' + line; +function getExampleCode(codeRef: RefObject, urls: {[name: string]: string}) { + let code = codeRef.current!.querySelector('pre')!.textContent!; + let fileTabs = codeRef.current!.closest('[data-files]'); + if (fileTabs) { + let example = fileTabs.querySelector('[data-example] pre'); + if (example) { + code = example.textContent!; } + } + + return code + // Export the last function + .replace(/\nfunction ([^(]+)((.|\n)+\n\}\n?)$/, '\nexport default function Example$2') + // Add function wrapper around raw JSX in examples. + .replace(/\n<((?:.|\n)+)/, (_, code) => { + let res = '\nexport default function Example() {\n return (\n <'; + let lines = code.split('\n'); + res += lines.shift(); + + for (let line of lines) { + res += '\n ' + line; + } - res += '\n );\n}\n'; - return res; - }); + res += '\n );\n}\n'; + return res; + }) + // Resolve urls + .replace(/import (.*?) from ['"](url:.*?)['"]/g, (_, name, specifier) => { + return `const ${name} = '${urls[specifier]}'`; + }); } const V0 = createIcon(props => ( diff --git a/packages/dev/s2-docs/src/CodeSandbox.tsx b/packages/dev/s2-docs/src/CodeSandbox.tsx index be0b47c3204..9783bd0db39 100644 --- a/packages/dev/s2-docs/src/CodeSandbox.tsx +++ b/packages/dev/s2-docs/src/CodeSandbox.tsx @@ -1,6 +1,11 @@ import LZString from 'lz-string'; -export function createCodeSandbox(files: {[name: string]: string}, type: 'vanilla' | 'tailwind' | 's2' = 'vanilla') { +export function createCodeSandbox( + files: {[name: string]: string}, + deps: {[name: string]: string}, + type: 'vanilla' | 'tailwind' | 's2' = 'vanilla', + entry: string = 'Example' +) { let form = document.createElement('form'); form.hidden = true; form.method = 'POST'; @@ -24,7 +29,7 @@ export function createCodeSandbox(files: {[name: string]: string}, type: 'vanill input.name = 'parameters'; input.value = LZString.compressToBase64(JSON.stringify({ - files: getCodeSandboxFiles(files, type) + files: getCodeSandboxFiles(files, deps, type, entry) })); form.appendChild(input); @@ -33,29 +38,6 @@ export function createCodeSandbox(files: {[name: string]: string}, type: 'vanill form.remove(); } -const dependencies = { - vanilla: { - 'react-aria-components': '^1.10.0', - react: '^19', - 'react-dom': '^19', - 'lucide-react': '^0.514.0', - 'clsx': '^2.1.1' - }, - tailwind: { - 'react-aria-components': '^1.10.0', - react: '^19', - 'react-dom': '^19', - 'lucide-react': '^0.514.0', - 'tailwind-variants': '^0.3.1', - 'tailwind-merge': '^2.5.4' - }, - s2: { - '@react-spectrum/s2': 'latest', - react: '^19', - 'react-dom': '^19' - } -}; - const devDependencies = { vanilla: { '@types/react': '^19', @@ -82,7 +64,13 @@ const devDependencies = { } }; -export function getCodeSandboxFiles(files: {[name: string]: string}, type: 'vanilla' | 'tailwind' | 's2' = 'vanilla') { +export function getCodeSandboxFiles( + files: {[name: string]: string}, + deps: {[name: string]: string}, + type: 'vanilla' | 'tailwind' | 's2' = 'vanilla', + entry: string = 'Example' +) { + let entryName = entry.split('/').pop()!.split('.')[0]; return { '.codesandbox/tasks.json': { content: JSON.stringify({ @@ -125,7 +113,11 @@ export function getCodeSandboxFiles(files: {[name: string]: string}, type: 'vani start: 'parcel', build: 'parcel build' }, - dependencies: dependencies[type], + dependencies: { + react: '^19', + 'react-dom': '^19', + ...deps + }, devDependencies: devDependencies[type] }, null, 2) + '\n' }, @@ -153,9 +145,9 @@ export function getCodeSandboxFiles(files: {[name: string]: string}, type: 'vani }, 'src/index.tsx': { content: `import {createRoot} from 'react-dom/client'; -import {Example} from './Example';${type === 's2' ? "\nimport '@react-spectrum/s2/page.css';" : ''} +import ${entryName} from './${entryName}';${type === 's2' ? "\nimport '@react-spectrum/s2/page.css';" : ''} -createRoot(document.getElementById('root')!).render(); +createRoot(document.getElementById('root')!).render(<${entryName} />); ` }, 'tsconfig.json': { diff --git a/packages/dev/s2-docs/src/ComponentCardView.tsx b/packages/dev/s2-docs/src/ComponentCardView.tsx index d51b93d960a..1e860142961 100644 --- a/packages/dev/s2-docs/src/ComponentCardView.tsx +++ b/packages/dev/s2-docs/src/ComponentCardView.tsx @@ -1,11 +1,10 @@ -/* eslint-disable rulesdir/imports */ -/* eslint-disable monorepo/no-internal-import */ 'use client'; -import {CardView, Collection} from '@react-spectrum/s2'; import {ComponentCard} from './ComponentCard'; -import {Key} from 'react-aria-components'; +import {InternalCardViewContext} from '../../../@react-spectrum/s2/src/Card'; +import {Key, ListBox, ListBoxItem} from 'react-aria-components'; import React from 'react'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; export interface ComponentCardItem { id: string, @@ -19,16 +18,49 @@ interface ComponentCardGridProps { ariaLabel?: string, size?: 'S' | 'M' | 'L', onAction?: (key: Key) => void, - styles?: any, renderEmptyState?: () => React.ReactNode } -export function ComponentCardView({items, ariaLabel = 'Items', size = 'S', onAction, styles, renderEmptyState}: ComponentCardGridProps) { +export function ComponentCardView({items, ariaLabel = 'Items', size = 'S', onAction, renderEmptyState}: ComponentCardGridProps) { return ( - - + + {(item) => } - - + + ); } diff --git a/packages/dev/s2-docs/src/ExampleApp.tsx b/packages/dev/s2-docs/src/ExampleApp.tsx index e52256aae8f..3d813885942 100644 --- a/packages/dev/s2-docs/src/ExampleApp.tsx +++ b/packages/dev/s2-docs/src/ExampleApp.tsx @@ -1,13 +1,22 @@ -import {Files} from './CodeBlock'; +import {FileProvider} from './CodePlatter'; +import {Files, getFiles} from './CodeBlock'; import fs from 'fs/promises'; import path from 'path'; import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; -export async function ExampleApp({dir, defaultSelected}: {dir: string, defaultSelected?: string}) { - let files = await fs.readdir('../../../' + dir); +export async function ExampleApp({dir, defaultSelected, type}: {dir: string, defaultSelected?: string, type?: 'tailwind' | 'vanilla' | 's2'}) { + let files = (await fs.readdir('../../../' + dir, {withFileTypes: true})).filter(d => d.isFile()).map(d => path.join(dir, d.name)); + let {files: downloadFiles, deps} = getFiles(files, type); + return (
- path.join(dir, f))} defaultSelected={defaultSelected ? path.join(dir, defaultSelected) : undefined} maxLines={Infinity} /> + + +
); } diff --git a/packages/dev/s2-docs/src/ExampleSwitcher.tsx b/packages/dev/s2-docs/src/ExampleSwitcher.tsx index b956dae9067..6e68e531c03 100644 --- a/packages/dev/s2-docs/src/ExampleSwitcher.tsx +++ b/packages/dev/s2-docs/src/ExampleSwitcher.tsx @@ -5,6 +5,7 @@ import {createContext, useState} from 'react'; import {Key} from 'react-aria-components'; import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; import {useLayoutEffect} from '@react-aria/utils'; +import {useLocalStorage} from './useLocalStorage'; const exampleStyle = style({ backgroundColor: 'layer-1', @@ -59,66 +60,35 @@ const themePicker = style({ export const ExampleSwitcherContext = createContext(null); -const DEFAULT_EXAMPLES = ['Vanilla CSS', 'Tailwind'] as Key[]; +const DEFAULT_EXAMPLES = ['Vanilla CSS', 'Tailwind']; export function ExampleSwitcher({type = 'style', examples = DEFAULT_EXAMPLES, children}) { - let [selected, setSelected] = useState(examples[0]); - let [theme, setTheme] = useState('indigo'); + let [selected, setSelected] = useLocalStorage(type, examples[0]); + let [theme, setTheme] = useLocalStorage('theme', 'indigo'); + let [value, setValue] = useState(examples[0]); - useLayoutEffect(() => { - if (!type) { - return; - } - - let search = new URLSearchParams(location.search); - let exampleType = search.get(type) ?? localStorage.getItem(type); - if (exampleType && examples.includes(exampleType)) { - setSelected(exampleType); - } - - let theme = localStorage.getItem('theme'); - if (theme) { - setTheme(theme); - } - - let controller = new AbortController(); - window.addEventListener('storage', e => { - if (e.key === type && e.newValue && examples.includes(e.newValue)) { - setSelected(e.newValue); - } + if (!examples.includes(selected)) { + selected = examples[0]; + } - if (e.key === 'theme' && e.newValue) { - setTheme(e.newValue); - } - }, {signal: controller.signal}); - return () => controller.abort(); - }, [type, examples]); + if (!type) { + selected = value; + } useLayoutEffect(() => { document.documentElement.style.setProperty('--tint', `var(--${theme})`); }, [theme]); - let onSelectionChange = key => { - setSelected(key); - + let onSelectionChange = (key: Key) => { if (type) { - localStorage.setItem(type, key); - window.dispatchEvent(new StorageEvent('storage', { - key: type, - oldValue: String(selected), - newValue: String(key) - })); + setSelected(String(key)); + } else { + setValue(String(key)); } }; - let onThemeChange = key => { - setTheme(key); - localStorage.setItem('theme', key); - window.dispatchEvent(new StorageEvent('storage', { - key: 'theme', - oldValue: String(theme), - newValue: String(key) - })); + let onThemeChange = (key: Key | null) => { + setTheme(String(key)); }; return ( diff --git a/packages/dev/s2-docs/src/InstallCommand.tsx b/packages/dev/s2-docs/src/InstallCommand.tsx index 68edd4340f2..1112bf4e1d8 100644 --- a/packages/dev/s2-docs/src/InstallCommand.tsx +++ b/packages/dev/s2-docs/src/InstallCommand.tsx @@ -4,8 +4,9 @@ import {CopyButton} from './CopyButton'; import {iconStyle, style} from '@react-spectrum/s2/style' with {type: 'macro'}; import {Key} from 'react-aria-components'; import Prompt from '@react-spectrum/s2/icons/Prompt'; -import React, {useEffect, useMemo, useState} from 'react'; +import React, {useMemo} from 'react'; import {SegmentedControl, SegmentedControlItem} from '@react-spectrum/s2'; +import {useLocalStorage} from './useLocalStorage'; const container = style({ backgroundColor: 'layer-1', @@ -56,22 +57,11 @@ export interface InstallCommandProps { } export function InstallCommand({pkg, flags, label, isCommand}: InstallCommandProps) { - let [manager, setManager] = useState('yarn'); - - useEffect(() => { - if (isCommand) { - return; - } - let stored = localStorage.getItem('packageManager'); - if (stored === 'npm' || stored === 'pnpm' || stored === 'yarn') { - setManager(stored); - } - }, [isCommand]); + let [manager, setManager] = useLocalStorage('packageManager', 'npm'); let onSelectionChange = (key: Key) => { let value = String(key) as PkgManager; setManager(value); - localStorage.setItem('packageManager', value); }; let command = useMemo(() => { @@ -102,8 +92,8 @@ export function InstallCommand({pkg, flags, label, isCommand}: InstallCommandPro
{!isCommand && ( - yarn npm + yarn pnpm )} diff --git a/packages/dev/s2-docs/src/SearchMenu.tsx b/packages/dev/s2-docs/src/SearchMenu.tsx index 6e735004f03..82ee7c14fa6 100644 --- a/packages/dev/s2-docs/src/SearchMenu.tsx +++ b/packages/dev/s2-docs/src/SearchMenu.tsx @@ -331,24 +331,25 @@ export function SearchMenu(props: SearchMenuProps) { return ( -
- -
- - +
+
+ +
+ + -
{(tags.length > 0 || tabIconTag.length > 0) && ( -
+
-
+
{tags.length > 0 && ( ) : ( { - setSearchValue(''); - onClose(); - }} + onAction={onClose} items={selectedItems.map(item => ({ id: item.id, name: item.name, diff --git a/packages/dev/s2-docs/src/SearchMenuTrigger.tsx b/packages/dev/s2-docs/src/SearchMenuTrigger.tsx index cbc45a9b9e6..f5396d847af 100644 --- a/packages/dev/s2-docs/src/SearchMenuTrigger.tsx +++ b/packages/dev/s2-docs/src/SearchMenuTrigger.tsx @@ -2,6 +2,7 @@ import {Button, ButtonProps, Modal, ModalOverlay} from 'react-aria-components'; import {fontRelative, style} from '@react-spectrum/s2/style' with { type: 'macro' }; +import {getLibraryFromPage, getLibraryLabel} from './library'; import {Page} from '@parcel/rsc'; import React, {CSSProperties, lazy, useCallback, useEffect, useState} from 'react'; import Search from '@react-spectrum/s2/icons/Search'; @@ -138,7 +139,8 @@ export default function SearchMenuTrigger({onOpen, onClose, isSearchOpen, overla className={({isHovered, isFocusVisible}) => style({ height: 40, boxSizing: 'border-box', - paddingX: 'edge-to-text', + paddingStart: 'pill', + paddingEnd: 8, fontSize: 'ui-lg', borderRadius: 'full', borderWidth: 2, @@ -177,6 +179,7 @@ export default function SearchMenuTrigger({onOpen, onClose, isSearchOpen, overla '--iconPrimary': {type: 'fill', value: 'currentColor'}, flexShrink: 0 }))} /> + Search {getLibraryLabel(getLibraryFromPage(props.currentPage))} }) { - let [packageManager, setPackageManager] = useState('npm'); + let [packageManager, setPackageManager] = useLocalStorage('packageManager', 'npm'); let command = packageManager; if (packageManager === 'npm') { command = 'npx'; @@ -14,16 +15,8 @@ export function ShadcnCommand({registryUrl, preRef}: {registryUrl: string, preRe command = 'pnpm dlx'; } - useEffect(() => { - let value = localStorage.getItem('packageManager'); - if (value) { - setPackageManager(value); - } - }, []); - let onSelectionChange = (value: Key) => { - setPackageManager(value); - localStorage.setItem('packageManager', String(value)); + setPackageManager(String(value)); }; let cmd = `${command} shadcn@latest add ${process.env.REGISTRY_URL || 'http://localhost:8081'}/${registryUrl}`; diff --git a/packages/dev/s2-docs/src/StackBlitz.tsx b/packages/dev/s2-docs/src/StackBlitz.tsx index 2bf40edad7e..524c85823e8 100644 --- a/packages/dev/s2-docs/src/StackBlitz.tsx +++ b/packages/dev/s2-docs/src/StackBlitz.tsx @@ -1,4 +1,9 @@ -export function createStackBlitz(files: {[name: string]: string}, type: 'vanilla' | 'tailwind' | 's2' = 'vanilla') { +export function createStackBlitz( + files: {[name: string]: string}, + deps: {[name: string]: string}, + type: 'vanilla' | 'tailwind' | 's2' = 'vanilla', + entry: string = 'Example' +) { let form = document.createElement('form'); form.hidden = true; form.method = 'POST'; @@ -23,7 +28,7 @@ export function createStackBlitz(files: {[name: string]: string}, type: 'vanilla input.value = 'description'; form.appendChild(input); - let generatedFiles = getFiles(files, type); + let generatedFiles = getFiles(files, deps, type, entry); for (let name in generatedFiles) { input = document.createElement('input'); input.type = 'hidden'; @@ -37,7 +42,13 @@ export function createStackBlitz(files: {[name: string]: string}, type: 'vanilla form.remove(); } -function getFiles(files: {[name: string]: string}, type: 'vanilla' | 'tailwind' | 's2' = 'vanilla') { +function getFiles( + files: {[name: string]: string}, + deps: {[name: string]: string}, + type: 'vanilla' | 'tailwind' | 's2' = 'vanilla', + entry: string = 'Example' +) { + let entryName = entry.split('/').pop()!.split('.')[0]; return { 'package.json': JSON.stringify({ name: 'react-aria-starter', @@ -50,13 +61,9 @@ function getFiles(files: {[name: string]: string}, type: 'vanilla' | 'tailwind' preview: 'vite preview' }, dependencies: { - 'react-aria-components': '^1.10.0', react: '^19', 'react-dom': '^19', - 'lucide-react': '^0.514.0', - ...(type === 'tailwind' ? { - 'tailwind-variants': '^0.3.1' - } : {}) + ...deps }, devDependencies: { '@types/react': '^19', @@ -90,11 +97,10 @@ export default defineConfig({ `, - 'src/index.tsx': `import {StrictMode} from 'react'; -import {createRoot} from 'react-dom/client'; -import {Example} from './Example'; + 'src/index.tsx': `import {createRoot} from 'react-dom/client'; +import ${entryName} from './${entryName}'; -createRoot(document.getElementById('root')!).render(); +createRoot(document.getElementById('root')!).render(<${entryName} />); `, 'tsconfig.json': JSON.stringify({ compilerOptions: { diff --git a/packages/dev/s2-docs/src/StarterKits.tsx b/packages/dev/s2-docs/src/StarterKits.tsx index d05db85e817..b18f937274e 100644 --- a/packages/dev/s2-docs/src/StarterKits.tsx +++ b/packages/dev/s2-docs/src/StarterKits.tsx @@ -16,7 +16,7 @@ const preview = style({ export function StarterKits() { return ( -
+
diff --git a/packages/dev/s2-docs/src/Step.tsx b/packages/dev/s2-docs/src/Step.tsx index 4902cd5b361..ef59f8477fc 100644 --- a/packages/dev/s2-docs/src/Step.tsx +++ b/packages/dev/s2-docs/src/Step.tsx @@ -24,20 +24,6 @@ export function Step({children}) { listStyleType: 'none', position: 'relative' })}> - {/*
*)': 'none' - }, - position: 'absolute', - top: 'calc(1lh + 16px)', - left: 'calc(-1lh / 2 - 8px)', - bottom: -24, - width: 2, - borderRadius: 'full', - backgroundColor: 'gray-400' - })} /> */} {children} ); diff --git a/packages/dev/s2-docs/src/VisualExample.tsx b/packages/dev/s2-docs/src/VisualExample.tsx index 5da3e6752f3..0f32c3f5eeb 100644 --- a/packages/dev/s2-docs/src/VisualExample.tsx +++ b/packages/dev/s2-docs/src/VisualExample.tsx @@ -1,4 +1,5 @@ import {CodeOutput, Control, Output, VisualExampleClient} from './VisualExampleClient'; +import {FileProvider, ShadcnProvider} from './CodePlatter'; import {Files, getFiles} from './CodeBlock'; import json5 from 'json5'; import path from 'path'; @@ -86,6 +87,7 @@ export interface VisualExampleProps { importSource?: string, /** When provided, the source code for the listed filenames will be included as tabs. */ files?: string[], + downloadFiles?: {files?: {[name: string]: string}, deps?: {[name: string]: string}}, type?: 'vanilla' | 'tailwind' | 's2', code?: ReactNode, wide?: boolean, @@ -106,7 +108,7 @@ export interface PropControl extends Omit { /** * Displays a component example with controls for changing the props. */ -export function VisualExample({component, docs, links, importSource, props, initialProps, controlOptions, files, code, wide, slots, align, acceptOrientation, type, propsObject, showCoachMark}: VisualExampleProps) { +export function VisualExample({component, docs, links, importSource, props, initialProps, controlOptions, files, downloadFiles, code, wide, slots, align, acceptOrientation, type, propsObject, showCoachMark}: VisualExampleProps) { let componentProps = docs.type === 'interface' ? docs : docs.props; if (componentProps?.type !== 'interface') { return null; @@ -156,27 +158,38 @@ export function VisualExample({component, docs, links, importSource, props, init importSource = './' + path.basename(files[0], path.extname(files[0])); } + if (!downloadFiles) { + if (files) { + downloadFiles = getFiles(files, type); + } else { + downloadFiles = {}; + } + } + + let registryUrl = type === 's2' || docs.type !== 'component' ? null : `${type}/${docs.name}.json`; let output = ( ); // Render the corresponding client component to make the controls interactive. return ( -
- -
- {Object.keys(controls).map(control => )} -
-
- {files ? {output} : output} -
-
+ + +
+ +
+ {Object.keys(controls).map(control => )} +
+
+ {files ? {output} : output} +
+
+
+
); } diff --git a/packages/dev/s2-docs/src/VisualExampleClient.tsx b/packages/dev/s2-docs/src/VisualExampleClient.tsx index c748e9545b9..f44ce3361ae 100644 --- a/packages/dev/s2-docs/src/VisualExampleClient.tsx +++ b/packages/dev/s2-docs/src/VisualExampleClient.tsx @@ -3,7 +3,7 @@ import {ActionButton, Avatar, Collection, ComboBox, ComboBoxItem, Content, ContextualHelp, Footer, Header, Heading, NotificationBadge, NumberField, Picker, PickerItem, PickerSection, RangeSlider, Slider, Switch, Text, TextField, ToggleButton, ToggleButtonGroup} from '@react-spectrum/s2'; import AddCircle from '@react-spectrum/s2/icons/AddCircle'; import {baseColor, focusRing, style, StyleString} from '@react-spectrum/s2/style' with { type: 'macro' }; -import {CodePlatter, Pre} from './CodePlatter'; +import {CodePlatter, Pre, ShareUrlProvider} from './CodePlatter'; import {ExampleOutput} from './ExampleOutput'; import {ExampleSwitcherContext} from './ExampleSwitcher'; import {flushSync} from 'react-dom'; @@ -96,10 +96,31 @@ export function VisualExampleClient({component, name, importSource, controls, ch // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + let searchParams = new URLSearchParams(); + let exampleType = useContext(ExampleSwitcherContext); + if (exampleType) { + searchParams.set('exampleType', String(exampleType)); + } + + for (let prop in props) { + let value = props[prop]; + if ( + value != null && + controls[prop] != null && + (controls[prop].default == null || value !== controls[prop].default) + ) { + searchParams.set(prop, JSON.stringify(value)); + } + } + + let url = '?' + searchParams.toString(); + return (