diff --git a/apps/storybook/src/patterns/LayoutPatterns.stories.tsx b/apps/storybook/src/patterns/LayoutPatterns.stories.tsx index 292fbc731..dda7c42bf 100644 --- a/apps/storybook/src/patterns/LayoutPatterns.stories.tsx +++ b/apps/storybook/src/patterns/LayoutPatterns.stories.tsx @@ -351,7 +351,7 @@ function DapValueField({ {label} {required && } - + void }) { - + @@ -3663,7 +3710,12 @@ function InventorySubContainer({
- +
@@ -3782,6 +3834,8 @@ function CompositionFieldDragOverlay({ field }: { field: CompositionFieldItem }) } function PanelUIInventoryStory() { + const [inventoryTab, setInventoryTab] = useState('layout'); + const [inventorySection, setInventorySection] = useState(null); const [enabled, setEnabled] = useState(true); const [checked, setChecked] = useState(true); const [notesVisible, setNotesVisible] = useState(true); @@ -3808,6 +3862,51 @@ function PanelUIInventoryStory() { const allSubContainerSections = ['text-fields', 'choices', 'advanced']; const [expandedSections, setExpandedSections] = useState([]); const [expandedSubContainerSections, setExpandedSubContainerSections] = useState([]); + + useEffect(() => { + const syncFromHash = () => { + let decodedHash = ''; + try { + decodedHash = decodeURIComponent(window.location.hash.slice(1)); + } catch { + return; + } + + const match = decodedHash.match(/^ui-inventory\/([^/]+)(?:\/(.+))?$/); + if (!match) return; + + const [, tab, section] = match; + if (!['layout', 'states', 'actions', 'composition'].includes(tab)) return; + setInventoryTab(tab); + setInventorySection(section ?? null); + if (tab === 'layout' && section && ['text-fields', 'choices', 'advanced'].includes(section)) { + setExpandedSections((current) => + current.includes(section) ? current : [...current, section] + ); + } + }; + + syncFromHash(); + window.addEventListener('hashchange', syncFromHash); + return () => window.removeEventListener('hashchange', syncFromHash); + }, []); + + useEffect(() => { + if (!inventorySection) return; + + const timeoutId = window.setTimeout(() => { + document.getElementById(`ui-inventory-${inventoryTab}-${inventorySection}`)?.scrollIntoView({ + block: 'start', + }); + }, 0); + return () => window.clearTimeout(timeoutId); + }, [inventorySection, inventoryTab]); + + const handleInventoryTabChange = (tab: string) => { + setInventoryTab(tab); + setInventorySection(null); + window.history.replaceState(null, '', `#ui-inventory/${tab}`); + }; const allSectionsExpanded = expandedSections.length === allInventorySections.length && expandedSubContainerSections.length === allSubContainerSections.length; @@ -3843,22 +3942,42 @@ function PanelUIInventoryStory() { className="h-[720px]" > - +
- + Layout - + States - + Actions - + Composition @@ -3887,8 +4006,8 @@ function PanelUIInventoryStory() {
-
- +
+ A simple, always-visible layout for short configurations that do not need collapsible sections or nested containers. @@ -3917,12 +4036,15 @@ function PanelUIInventoryStory() { Run this node in the workflow.

- +
-
- +
+ Full-width sections that reveal or hide related fields without adding nested container chrome. @@ -3932,7 +4054,11 @@ function PanelUIInventoryStory() { value={expandedSections} onValueChange={setExpandedSections} > - + Text and numeric fields @@ -3952,12 +4078,16 @@ function PanelUIInventoryStory() { - + - + Selection controls @@ -4010,6 +4140,7 @@ function PanelUIInventoryStory() {
@@ -4020,7 +4151,11 @@ function PanelUIInventoryStory() { - + Advanced options @@ -4033,8 +4168,11 @@ function PanelUIInventoryStory() { -
- +
+ Dense, collapsible cards for related configuration when stronger visual grouping is useful. @@ -4047,8 +4185,12 @@ function PanelUIInventoryStory() {
-
- +
+ Persistent feedback summarizes a panel-level result and provides the next action when one is needed. @@ -4074,8 +4216,15 @@ function PanelUIInventoryStory() { />
-
- +
+ Error counts on tabs reveal where unresolved issues live, including problems in sections that are not currently visible. @@ -4097,8 +4246,15 @@ function PanelUIInventoryStory() {
-
- +
+ Field-specific feedback stays beside the control so the issue and resolution are clear in context. @@ -4122,8 +4278,15 @@ function PanelUIInventoryStory() {
-
- +
+ Toasts confirm the result of a user action without interrupting the task. Keep actionable errors visible in the panel instead. @@ -4171,8 +4334,15 @@ function PanelUIInventoryStory() {
-
- +
+ Short labels communicate passive field or setting states without interrupting the task. @@ -4187,8 +4357,12 @@ function PanelUIInventoryStory() {
-
- +
+ Use one primary action per context, with secondary, tertiary, and destructive styles reflecting lower emphasis or greater consequence. @@ -4200,8 +4374,15 @@ function PanelUIInventoryStory() {
-
- +
+ Reserve the panel header for high-frequency node-level commands such as running or debugging. Keep the set small so the primary task remains clear. @@ -4211,8 +4392,15 @@ function PanelUIInventoryStory() {
-
- + -
- +
-
- +
+ Use a secondary button when the action opens a separate configuration surface for the field or section it affects. @@ -4244,8 +4446,11 @@ function PanelUIInventoryStory() {
-
- +
+ Use the lightweight plus link when adding another item to a repeatable list. Keep it separate from manage actions so the two intents are easy to scan. @@ -4254,8 +4459,15 @@ function PanelUIInventoryStory() {
-
- +
+ Use compact icon actions for familiar utilities when space is limited. Always provide a tooltip and accessible name. @@ -4291,8 +4503,12 @@ function PanelUIInventoryStory() { className="mt-0 min-h-0 flex-1 overflow-y-auto p-3.5" >
-
- +
+ Use reorder, add, and remove controls when users build a variable-length set of related fields. @@ -4361,8 +4577,15 @@ function PanelUIInventoryStory() {
-
- +
+ Switch between a guided interface and a source representation without changing the underlying configuration. @@ -4394,8 +4617,15 @@ function PanelUIInventoryStory() { )}
-
- +
+ Combines field type, required state, AI assistance, variable insertion, and fixed or expression values in one reusable Flow control. @@ -4413,7 +4643,7 @@ function PanelUIInventoryStory() { headerActions={ } diff --git a/packages/apollo-react/src/canvas/stories/templates/Flow.stories.tsx b/packages/apollo-react/src/canvas/stories/templates/Flow.stories.tsx index aa10a8ec8..a5e9af93a 100644 --- a/packages/apollo-react/src/canvas/stories/templates/Flow.stories.tsx +++ b/packages/apollo-react/src/canvas/stories/templates/Flow.stories.tsx @@ -3775,6 +3775,7 @@ function DapValueField({ } function DapPanel({ onClose }: { onClose: () => void }) { + const [connection, setConnection] = useState('gmail-finance'); const [subject, setSubject] = useState('Invoice approval required'); const [recipient, setRecipient] = useState('$vars.approverEmail'); const [body, setBody] = useState( @@ -3852,30 +3853,29 @@ function DapPanel({ onClose }: { onClose: () => void }) {

-
- - -
- + + Connection + + } + fieldType="single-select" + value={connection} + onValueChange={setConnection} + locked={false} + showLock={false} + options={[ + { value: 'gmail-finance', label: 'Gmail · Finance operations' }, + { value: 'gmail-personal', label: 'Gmail · Personal' }, + ]} + more={{ + onClear: () => setConnection(''), + onRefresh: () => setConnection('gmail-finance'), + }} + showFieldActions={false} + className="gap-1.5" + /> Schema is current. Nine configurable message properties are available. @@ -3921,7 +3921,7 @@ function DapPanel({ onClose }: { onClose: () => void }) { -
+