From a9a263114b7f1aee704f5c637577b1d97dc54ec8 Mon Sep 17 00:00:00 2001 From: Cindy Zhang Date: Fri, 7 Aug 2026 16:03:15 -0700 Subject: [PATCH 1/2] docs(docsite): add ComplexSelector showcase and examples Adds the missing docsite showcase and example blocks for ComplexSelector. The docsite builds both from CLI template blocks (blocks with isShowcase feed showcaseRegistry, the rest feed exampleRegistry keyed on exampleFor), so all four live under packages/cli/assets/templates/blocks/components/. - Showcase: a project-visibility picker whose popup content is a RadioList. - Folder Tree: a TreeList destination picker, so the hierarchy keeps its own tree keyboard navigation. - Multi-select Filter: CheckboxList content that stays open across several toggles and closes only when the apply button calls close(). - Async Assignment: changeAction driving the optimistic value, busy spinner, and a settled validation status. Also references the new block directory from the component's SYNC block, which scripts/check-sync.js requires once a showcase dir exists. --- .../ComplexSelectorAsyncAssign.doc.mjs | 14 ++++ .../ComplexSelectorAsyncAssign.tsx | 83 +++++++++++++++++++ .../ComplexSelectorFolderTree.doc.mjs | 14 ++++ .../ComplexSelectorFolderTree.tsx | 71 ++++++++++++++++ .../ComplexSelectorShowcase.doc.mjs | 15 ++++ .../ComplexSelectorShowcase.tsx | 62 ++++++++++++++ .../ComplexSelectorStageFilter.doc.mjs | 14 ++++ .../ComplexSelectorStageFilter.tsx | 54 ++++++++++++ .../src/ComplexSelector/ComplexSelector.tsx | 1 + 9 files changed, 328 insertions(+) create mode 100644 packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorAsyncAssign.doc.mjs create mode 100644 packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorAsyncAssign.tsx create mode 100644 packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorFolderTree.doc.mjs create mode 100644 packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorFolderTree.tsx create mode 100644 packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorShowcase.doc.mjs create mode 100644 packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorShowcase.tsx create mode 100644 packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorStageFilter.doc.mjs create mode 100644 packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorStageFilter.tsx diff --git a/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorAsyncAssign.doc.mjs b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorAsyncAssign.doc.mjs new file mode 100644 index 000000000000..19dc0ac61f93 --- /dev/null +++ b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorAsyncAssign.doc.mjs @@ -0,0 +1,14 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. + +/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */ +export const doc = { + type: 'block', + exampleFor: 'ComplexSelector', + name: 'Complex Selector — Async Assignment', + displayName: 'Complex Selector — Async Assignment', + description: + 'A reviewer picker using changeAction, so the trigger shows the new value optimistically with a busy spinner while the save settles into a validation status.', + isReady: true, + aspectRatio: 4 / 3, + componentsUsed: ['ComplexSelector', 'RadioList'], +}; diff --git a/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorAsyncAssign.tsx b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorAsyncAssign.tsx new file mode 100644 index 000000000000..9ad04d867c5b --- /dev/null +++ b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorAsyncAssign.tsx @@ -0,0 +1,83 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. + +'use client'; + +import {useState} from 'react'; +import { + ComplexSelector, + type ComplexSelectorStatus, +} from '@astryxdesign/core/ComplexSelector'; +import {RadioList, RadioListItem} from '@astryxdesign/core/RadioList'; + +const REVIEWERS = [ + { + value: 'sarah-chen', + label: 'Sarah Chen', + description: 'Design systems · 2 open reviews', + }, + { + value: 'marcus-webb', + label: 'Marcus Webb', + description: 'Accessibility · 5 open reviews', + }, + { + value: 'priya-raman', + label: 'Priya Raman', + description: 'Platform · 1 open review', + }, +]; + +export default function ComplexSelectorAsyncAssign() { + const [reviewer, setReviewer] = useState(''); + const [isSaved, setIsSaved] = useState(false); + const selected = REVIEWERS.find(option => option.value === reviewer); + + let status: ComplexSelectorStatus | undefined; + if (selected == null) { + status = {type: 'error', message: 'Every draft needs a reviewer'}; + } else if (isSaved) { + status = {type: 'success', message: `Assigned to ${selected.label}`}; + } + + return ( + { + setReviewer(value); + setIsSaved(false); + }} + changeAction={async () => { + await new Promise(resolve => { + setTimeout(resolve, 900); + }); + setIsSaved(true); + }} + placeholder="Unassigned" + triggerLabel={selected?.label} + status={status}> + {(value, onChange, close) => ( + { + onChange(nextValue); + close(); + }}> + {REVIEWERS.map(option => ( + + ))} + + )} + + ); +} diff --git a/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorFolderTree.doc.mjs b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorFolderTree.doc.mjs new file mode 100644 index 000000000000..4cd738dbc824 --- /dev/null +++ b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorFolderTree.doc.mjs @@ -0,0 +1,14 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. + +/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */ +export const doc = { + type: 'block', + exampleFor: 'ComplexSelector', + name: 'Complex Selector — Folder Tree', + displayName: 'Complex Selector — Folder Tree', + description: + 'A destination picker whose popup content is a TreeList, so the hierarchy keeps its own tree keyboard navigation.', + isReady: true, + aspectRatio: 4 / 3, + componentsUsed: ['ComplexSelector', 'TreeList'], +}; diff --git a/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorFolderTree.tsx b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorFolderTree.tsx new file mode 100644 index 000000000000..d175a591e9ea --- /dev/null +++ b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorFolderTree.tsx @@ -0,0 +1,71 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. + +'use client'; + +import {useState} from 'react'; +import {ComplexSelector} from '@astryxdesign/core/ComplexSelector'; +import {TreeList} from '@astryxdesign/core/TreeList'; + +interface Destination { + id: string; + path: string; +} + +const FOLDERS = [ + { + id: 'design-systems', + label: 'Design systems', + children: [ + {id: 'components', label: 'Components'}, + {id: 'accessibility', label: 'Accessibility'}, + {id: 'tokens', label: 'Tokens'}, + ], + }, + { + id: 'research', + label: 'Research', + children: [ + {id: 'interviews', label: 'Interviews'}, + {id: 'field-notes', label: 'Field notes'}, + ], + }, +]; + +export default function ComplexSelectorFolderTree() { + const [destination, setDestination] = useState({ + id: 'components', + path: 'Design systems / Components', + }); + + return ( + + label="Move to folder" + description="Where this document should live" + width={320} + value={destination} + onChange={setDestination} + triggerLabel={destination.path}> + {(value, onChange, close) => ( + ({ + id: folder.id, + label: folder.label, + isExpanded: true, + children: folder.children.map(child => ({ + id: child.id, + label: child.label, + isSelected: child.id === value.id, + onClick: () => { + onChange({ + id: child.id, + path: `${folder.label} / ${child.label}`, + }); + close(); + }, + })), + }))} + /> + )} + + ); +} diff --git a/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorShowcase.doc.mjs b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorShowcase.doc.mjs new file mode 100644 index 000000000000..6a3ef301960a --- /dev/null +++ b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorShowcase.doc.mjs @@ -0,0 +1,15 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. + +/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */ +export const doc = { + type: 'block', + exampleFor: 'ComplexSelector', + name: 'Complex Selector', + displayName: 'Complex Selector', + description: + 'A visibility selector whose popup content is a RadioList; picking an option commits the value and closes the popup.', + isReady: true, + aspectRatio: 4 / 3, + isShowcase: true, + componentsUsed: ['ComplexSelector', 'RadioList'], +}; diff --git a/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorShowcase.tsx b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorShowcase.tsx new file mode 100644 index 000000000000..f35e6eca30c2 --- /dev/null +++ b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorShowcase.tsx @@ -0,0 +1,62 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. + +'use client'; + +import {useState} from 'react'; +import {ComplexSelector} from '@astryxdesign/core/ComplexSelector'; +import {RadioList, RadioListItem} from '@astryxdesign/core/RadioList'; + +const VISIBILITY_OPTIONS = [ + { + value: 'private', + label: 'Private', + description: 'Only you can open this project', + }, + { + value: 'team', + label: 'Design systems team', + description: 'All 14 teammates can edit', + }, + { + value: 'company', + label: 'Everyone at Northwind', + description: 'Anyone signed in can view', + }, +]; + +export default function ComplexSelectorShowcase() { + const [visibility, setVisibility] = useState('team'); + const selected = VISIBILITY_OPTIONS.find( + option => option.value === visibility, + ); + + return ( + + {(value, onChange, close) => ( + { + onChange(nextValue); + close(); + }}> + {VISIBILITY_OPTIONS.map(option => ( + + ))} + + )} + + ); +} diff --git a/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorStageFilter.doc.mjs b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorStageFilter.doc.mjs new file mode 100644 index 000000000000..50f49f9fff73 --- /dev/null +++ b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorStageFilter.doc.mjs @@ -0,0 +1,14 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. + +/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */ +export const doc = { + type: 'block', + exampleFor: 'ComplexSelector', + name: 'Complex Selector — Multi-select Filter', + displayName: 'Complex Selector — Multi-select Filter', + description: + 'A stage filter that keeps the popup open while several checkboxes are toggled and closes only when the apply button calls close().', + isReady: true, + aspectRatio: 4 / 3, + componentsUsed: ['ComplexSelector', 'CheckboxList', 'Button', 'VStack'], +}; diff --git a/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorStageFilter.tsx b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorStageFilter.tsx new file mode 100644 index 000000000000..9cbe55ae70d0 --- /dev/null +++ b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorStageFilter.tsx @@ -0,0 +1,54 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. + +'use client'; + +import {useState} from 'react'; +import {Button} from '@astryxdesign/core/Button'; +import {CheckboxList, CheckboxListItem} from '@astryxdesign/core/CheckboxList'; +import {ComplexSelector} from '@astryxdesign/core/ComplexSelector'; +import {VStack} from '@astryxdesign/core/Layout'; + +const STAGES = [ + {value: 'discovery', label: 'Discovery'}, + {value: 'in-review', label: 'In review'}, + {value: 'approved', label: 'Approved'}, + {value: 'shipped', label: 'Shipped'}, +]; + +export default function ComplexSelectorStageFilter() { + const [stages, setStages] = useState(['in-review', 'approved']); + + return ( + + label="Pipeline stages" + description="Filter the roadmap by delivery stage" + width={320} + value={stages} + onChange={setStages} + placeholder="All stages" + triggerLabel={ + stages.length > 0 + ? `${stages.length} of ${STAGES.length} stages` + : undefined + }> + {(value, onChange, close) => ( + + + {STAGES.map(stage => ( + + ))} + +