altitude-design 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/guidance/components/AILabel/README.md +35 -0
- package/dist/guidance/components/Accordion/README.md +25 -0
- package/dist/guidance/components/AgentFleet/README.md +48 -1
- package/dist/guidance/components/AgentLanes/README.md +32 -0
- package/dist/guidance/components/AgentRequests/README.md +47 -0
- package/dist/guidance/components/Alert/README.md +49 -0
- package/dist/guidance/components/AltitudeProvider/README.md +63 -0
- package/dist/guidance/components/AppShell/README.md +70 -0
- package/dist/guidance/components/AreaChart/README.md +58 -0
- package/dist/guidance/components/Artifact/README.md +69 -0
- package/dist/guidance/components/Avatar/README.md +41 -0
- package/dist/guidance/components/Await/README.md +48 -0
- package/dist/guidance/components/Badge/README.md +44 -0
- package/dist/guidance/components/BarChart/README.md +62 -0
- package/dist/guidance/components/BarList/README.md +58 -0
- package/dist/guidance/components/Board/README.md +67 -0
- package/dist/guidance/components/Breadcrumb/README.md +31 -0
- package/dist/guidance/components/Button/README.md +41 -0
- package/dist/guidance/components/Card/README.md +38 -0
- package/dist/guidance/components/ChartBrush/README.md +57 -0
- package/dist/guidance/components/ChartContainer/README.md +77 -0
- package/dist/guidance/components/ChartLegend/README.md +43 -0
- package/dist/guidance/components/ChartReference/README.md +74 -0
- package/dist/guidance/components/ChartTooltip/README.md +63 -0
- package/dist/guidance/components/Checkbox/README.md +35 -0
- package/dist/guidance/components/Checklist/README.md +66 -0
- package/dist/guidance/components/CodeBlock/README.md +44 -0
- package/dist/guidance/components/ColorPicker/README.md +39 -0
- package/dist/guidance/components/Combobox/README.md +35 -0
- package/dist/guidance/components/CommandPalette/README.md +177 -0
- package/dist/guidance/components/CommentThread/README.md +51 -0
- package/dist/guidance/components/Confirmation/README.md +60 -0
- package/dist/guidance/components/ContextMenu/README.md +51 -0
- package/dist/guidance/components/Conversation/README.md +124 -0
- package/dist/guidance/components/CopyButton/README.md +31 -0
- package/dist/guidance/components/DataGrid/README.md +75 -0
- package/dist/guidance/components/DatePicker/README.md +49 -0
- package/dist/guidance/components/Dialog/README.md +175 -0
- package/dist/guidance/components/DiffView/README.md +77 -0
- package/dist/guidance/components/DotPlot/README.md +44 -0
- package/dist/guidance/components/Drilldown/README.md +28 -0
- package/dist/guidance/components/DropZone/README.md +50 -0
- package/dist/guidance/components/DropdownMenu/README.md +44 -0
- package/dist/guidance/components/EditRegion/README.md +159 -0
- package/dist/guidance/components/EmptyState/README.md +49 -0
- package/dist/guidance/components/FeatureGate/README.md +39 -0
- package/dist/guidance/components/Field/README.md +36 -0
- package/dist/guidance/components/FilterBar/README.md +63 -0
- package/dist/guidance/components/FormattedNumber/README.md +44 -0
- package/dist/guidance/components/Heatmap/README.md +71 -0
- package/dist/guidance/components/HoldButton/README.md +32 -0
- package/dist/guidance/components/HoverCard/README.md +53 -0
- package/dist/guidance/components/InlineCitation/README.md +32 -0
- package/dist/guidance/components/InlineEdit/README.md +247 -1
- package/dist/guidance/components/Input/README.md +36 -0
- package/dist/guidance/components/InputOTP/README.md +46 -0
- package/dist/guidance/components/ItemGroup/README.md +100 -0
- package/dist/guidance/components/Kbd/README.md +27 -0
- package/dist/guidance/components/LineChart/README.md +62 -0
- package/dist/guidance/components/Link/README.md +41 -0
- package/dist/guidance/components/Markdown/README.md +81 -0
- package/dist/guidance/components/Meter/README.md +52 -0
- package/dist/guidance/components/NotificationCenter/README.md +39 -0
- package/dist/guidance/components/NumberInput/README.md +37 -0
- package/dist/guidance/components/PageHeader/README.md +45 -0
- package/dist/guidance/components/Pagination/README.md +33 -0
- package/dist/guidance/components/PasswordInput/README.md +36 -0
- package/dist/guidance/components/PermissionMatrix/README.md +40 -0
- package/dist/guidance/components/Pile/README.md +42 -0
- package/dist/guidance/components/PlanPicker/README.md +37 -0
- package/dist/guidance/components/Popover/README.md +36 -0
- package/dist/guidance/components/Progress/README.md +31 -0
- package/dist/guidance/components/PromptInput/README.md +98 -0
- package/dist/guidance/components/PropertyList/README.md +80 -0
- package/dist/guidance/components/Question/README.md +54 -0
- package/dist/guidance/components/RadioGroup/README.md +37 -0
- package/dist/guidance/components/RelativeTime/README.md +47 -0
- package/dist/guidance/components/RemovalReceipt/README.md +77 -0
- package/dist/guidance/components/Resizable/README.md +29 -0
- package/dist/guidance/components/SaveBar/README.md +38 -0
- package/dist/guidance/components/SavedViews/README.md +58 -0
- package/dist/guidance/components/ScatterChart/README.md +63 -0
- package/dist/guidance/components/Schedule/README.md +66 -0
- package/dist/guidance/components/ScrollArea/README.md +28 -0
- package/dist/guidance/components/SecretField/README.md +46 -0
- package/dist/guidance/components/Select/README.md +70 -0
- package/dist/guidance/components/Separator/README.md +44 -0
- package/dist/guidance/components/Sheet/README.md +94 -0
- package/dist/guidance/components/Skeleton/README.md +36 -0
- package/dist/guidance/components/Slider/README.md +35 -0
- package/dist/guidance/components/SortableList/README.md +34 -0
- package/dist/guidance/components/Sparkline/README.md +58 -0
- package/dist/guidance/components/SplitButton/README.md +39 -0
- package/dist/guidance/components/Stat/README.md +61 -0
- package/dist/guidance/components/Stepper/README.md +61 -0
- package/dist/guidance/components/Suggestions/README.md +58 -0
- package/dist/guidance/components/Surface/README.md +28 -0
- package/dist/guidance/components/Switch/README.md +29 -0
- package/dist/guidance/components/SyncIndicator/README.md +54 -0
- package/dist/guidance/components/Table/README.md +62 -0
- package/dist/guidance/components/Tabs/README.md +55 -0
- package/dist/guidance/components/TagInput/README.md +50 -0
- package/dist/guidance/components/Terminal/README.md +42 -0
- package/dist/guidance/components/ThreadList/README.md +73 -0
- package/dist/guidance/components/TimeInput/README.md +37 -0
- package/dist/guidance/components/Timeline/README.md +55 -0
- package/dist/guidance/components/Toaster/README.md +38 -0
- package/dist/guidance/components/Toggle/README.md +52 -0
- package/dist/guidance/components/Toolbar/README.md +69 -0
- package/dist/guidance/components/Tooltip/README.md +29 -0
- package/dist/guidance/components/Tour/README.md +44 -0
- package/dist/guidance/components/Trace/README.md +92 -0
- package/dist/guidance/components/Tree/README.md +85 -0
- package/dist/guidance/index.json +117 -117
- package/dist/guidance/llms.txt +1 -1
- package/dist/guidance/manifest.json +120 -120
- package/dist/guidance/visual/ActionsInPlace.md +5 -5
- package/dist/guidance/visual/Distinctiveness.md +6 -2
- package/dist/guidance/visual/Research.md +14 -2
- package/dist/guidance/visual/Theming.md +1 -1
- package/dist/types/agent-fleet.d.ts +28 -0
- package/dist/types/ai-label.d.ts +7 -0
- package/dist/types/app-shell.d.ts +23 -0
- package/dist/types/area-chart.d.ts +8 -0
- package/dist/types/artifact.d.ts +14 -0
- package/dist/types/bar-chart.d.ts +6 -0
- package/dist/types/bar-list.d.ts +6 -0
- package/dist/types/breadcrumb.d.ts +6 -0
- package/dist/types/chart-brush.d.ts +6 -0
- package/dist/types/chart.d.ts +29 -1
- package/dist/types/checklist.d.ts +6 -0
- package/dist/types/code-block.d.ts +6 -0
- package/dist/types/collections.d.ts +15 -0
- package/dist/types/color-picker.d.ts +6 -0
- package/dist/types/command.d.ts +13 -0
- package/dist/types/comment-thread.d.ts +11 -0
- package/dist/types/confirmation.d.ts +8 -0
- package/dist/types/context-menu.d.ts +11 -0
- package/dist/types/controls.d.ts +174 -10
- package/dist/types/conversation.d.ts +21 -0
- package/dist/types/copy-button.d.ts +6 -0
- package/dist/types/core.d.ts +31 -0
- package/dist/types/data-grid.d.ts +25 -0
- package/dist/types/date-picker.d.ts +6 -0
- package/dist/types/diff-view.d.ts +8 -0
- package/dist/types/dot-plot.d.ts +6 -0
- package/dist/types/edit-region.d.ts +23 -0
- package/dist/types/empty.d.ts +9 -0
- package/dist/types/feature-gate.d.ts +8 -0
- package/dist/types/filter-bar.d.ts +8 -0
- package/dist/types/formatted-number.d.ts +6 -0
- package/dist/types/heatmap.d.ts +6 -0
- package/dist/types/inline-citation.d.ts +6 -0
- package/dist/types/inline-edit.d.ts +7 -0
- package/dist/types/input-otp.d.ts +11 -0
- package/dist/types/item-group.d.ts +29 -0
- package/dist/types/line-chart.d.ts +6 -0
- package/dist/types/link.d.ts +8 -1
- package/dist/types/markdown.d.ts +6 -0
- package/dist/types/meter.d.ts +6 -0
- package/dist/types/nav.d.ts +6 -0
- package/dist/types/notifications.d.ts +6 -0
- package/dist/types/number-input.d.ts +6 -0
- package/dist/types/overlays.d.ts +61 -0
- package/dist/types/page-header.d.ts +14 -0
- package/dist/types/pagination.d.ts +6 -0
- package/dist/types/password-input.d.ts +6 -0
- package/dist/types/permission-matrix.d.ts +7 -0
- package/dist/types/pile.d.ts +6 -0
- package/dist/types/plan-picker.d.ts +6 -0
- package/dist/types/prompt-input.d.ts +11 -0
- package/dist/types/property-list.d.ts +17 -0
- package/dist/types/question.d.ts +11 -0
- package/dist/types/relative-time.d.ts +8 -1
- package/dist/types/removal-receipt.d.ts +18 -1
- package/dist/types/save-bar.d.ts +6 -0
- package/dist/types/saved-views.d.ts +12 -0
- package/dist/types/scatter-chart.d.ts +8 -0
- package/dist/types/schedule.d.ts +21 -0
- package/dist/types/secret-field.d.ts +6 -0
- package/dist/types/select.d.ts +12 -0
- package/dist/types/separator.d.ts +8 -1
- package/dist/types/sheet.d.ts +19 -0
- package/dist/types/sparkline.d.ts +6 -0
- package/dist/types/split-button.d.ts +7 -0
- package/dist/types/stat.d.ts +7 -0
- package/dist/types/stepper.d.ts +12 -0
- package/dist/types/suggestions.d.ts +10 -0
- package/dist/types/sync-indicator.d.ts +8 -0
- package/dist/types/tag-input.d.ts +8 -0
- package/dist/types/terminal.d.ts +6 -0
- package/dist/types/thread-list.d.ts +16 -0
- package/dist/types/time-input.d.ts +6 -0
- package/dist/types/timeline.d.ts +9 -0
- package/dist/types/toggle.d.ts +6 -0
- package/dist/types/toolbar.d.ts +20 -0
- package/dist/types/tour.d.ts +10 -0
- package/dist/types/trace.d.ts +16 -0
- package/dist/types/tree.d.ts +21 -0
- package/dist/types-cjs/agent-fleet.d.cts +28 -0
- package/dist/types-cjs/ai-label.d.cts +7 -0
- package/dist/types-cjs/app-shell.d.cts +23 -0
- package/dist/types-cjs/area-chart.d.cts +8 -0
- package/dist/types-cjs/artifact.d.cts +14 -0
- package/dist/types-cjs/bar-chart.d.cts +6 -0
- package/dist/types-cjs/bar-list.d.cts +6 -0
- package/dist/types-cjs/breadcrumb.d.cts +6 -0
- package/dist/types-cjs/chart-brush.d.cts +6 -0
- package/dist/types-cjs/chart.d.cts +29 -1
- package/dist/types-cjs/checklist.d.cts +6 -0
- package/dist/types-cjs/code-block.d.cts +6 -0
- package/dist/types-cjs/collections.d.cts +15 -0
- package/dist/types-cjs/color-picker.d.cts +6 -0
- package/dist/types-cjs/command.d.cts +13 -0
- package/dist/types-cjs/comment-thread.d.cts +11 -0
- package/dist/types-cjs/confirmation.d.cts +8 -0
- package/dist/types-cjs/context-menu.d.cts +11 -0
- package/dist/types-cjs/controls.d.cts +174 -10
- package/dist/types-cjs/conversation.d.cts +21 -0
- package/dist/types-cjs/copy-button.d.cts +6 -0
- package/dist/types-cjs/core.d.cts +31 -0
- package/dist/types-cjs/data-grid.d.cts +25 -0
- package/dist/types-cjs/date-picker.d.cts +6 -0
- package/dist/types-cjs/diff-view.d.cts +8 -0
- package/dist/types-cjs/dot-plot.d.cts +6 -0
- package/dist/types-cjs/edit-region.d.cts +23 -0
- package/dist/types-cjs/empty.d.cts +9 -0
- package/dist/types-cjs/feature-gate.d.cts +8 -0
- package/dist/types-cjs/filter-bar.d.cts +8 -0
- package/dist/types-cjs/formatted-number.d.cts +6 -0
- package/dist/types-cjs/heatmap.d.cts +6 -0
- package/dist/types-cjs/inline-citation.d.cts +6 -0
- package/dist/types-cjs/inline-edit.d.cts +7 -0
- package/dist/types-cjs/input-otp.d.cts +11 -0
- package/dist/types-cjs/item-group.d.cts +29 -0
- package/dist/types-cjs/line-chart.d.cts +6 -0
- package/dist/types-cjs/link.d.cts +8 -1
- package/dist/types-cjs/markdown.d.cts +6 -0
- package/dist/types-cjs/meter.d.cts +6 -0
- package/dist/types-cjs/nav.d.cts +6 -0
- package/dist/types-cjs/notifications.d.cts +6 -0
- package/dist/types-cjs/number-input.d.cts +6 -0
- package/dist/types-cjs/overlays.d.cts +61 -0
- package/dist/types-cjs/page-header.d.cts +14 -0
- package/dist/types-cjs/pagination.d.cts +6 -0
- package/dist/types-cjs/password-input.d.cts +6 -0
- package/dist/types-cjs/permission-matrix.d.cts +7 -0
- package/dist/types-cjs/pile.d.cts +6 -0
- package/dist/types-cjs/plan-picker.d.cts +6 -0
- package/dist/types-cjs/prompt-input.d.cts +11 -0
- package/dist/types-cjs/property-list.d.cts +17 -0
- package/dist/types-cjs/question.d.cts +11 -0
- package/dist/types-cjs/relative-time.d.cts +8 -1
- package/dist/types-cjs/removal-receipt.d.cts +18 -1
- package/dist/types-cjs/save-bar.d.cts +6 -0
- package/dist/types-cjs/saved-views.d.cts +12 -0
- package/dist/types-cjs/scatter-chart.d.cts +8 -0
- package/dist/types-cjs/schedule.d.cts +21 -0
- package/dist/types-cjs/secret-field.d.cts +6 -0
- package/dist/types-cjs/select.d.cts +12 -0
- package/dist/types-cjs/separator.d.cts +8 -1
- package/dist/types-cjs/sheet.d.cts +19 -0
- package/dist/types-cjs/sparkline.d.cts +6 -0
- package/dist/types-cjs/split-button.d.cts +7 -0
- package/dist/types-cjs/stat.d.cts +7 -0
- package/dist/types-cjs/stepper.d.cts +12 -0
- package/dist/types-cjs/suggestions.d.cts +10 -0
- package/dist/types-cjs/sync-indicator.d.cts +8 -0
- package/dist/types-cjs/tag-input.d.cts +8 -0
- package/dist/types-cjs/terminal.d.cts +6 -0
- package/dist/types-cjs/thread-list.d.cts +16 -0
- package/dist/types-cjs/time-input.d.cts +6 -0
- package/dist/types-cjs/timeline.d.cts +9 -0
- package/dist/types-cjs/toggle.d.cts +6 -0
- package/dist/types-cjs/toolbar.d.cts +20 -0
- package/dist/types-cjs/tour.d.cts +10 -0
- package/dist/types-cjs/trace.d.cts +16 -0
- package/dist/types-cjs/tree.d.cts +21 -0
- package/package.json +1 -1
|
@@ -91,3 +91,103 @@ The `ItemGroup` section of `build/previews.txt` shows webhooks (edit, add, reord
|
|
|
91
91
|
It also renders and 320/960px layouts in both schemes under reduced motion, forced colours and `contrast="more"`.
|
|
92
92
|
**Narrow.** When the group is under 480px wide, `ItemActions` moves to its own row under the content, so the title keeps its width; `narrow="inline"` keeps the actions beside it. `ItemDescription truncate` (or a number of lines) clamps a long description.
|
|
93
93
|
|
|
94
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
95
|
+
## Example
|
|
96
|
+
|
|
97
|
+
`ItemGroup` as first used in its preview:
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import { Button, Field, Input, Item, ItemActions, ItemContent, ItemDescription, ItemGroup, ItemMedia, ItemTitle } from 'altitude-design';
|
|
101
|
+
|
|
102
|
+
<ItemGroup aria-labelledby="ig-hooks" items={hooks} getKey={h => h.id} getLabel={h => h.name}
|
|
103
|
+
renderItem={(h, { editProps, removeProps }) => <Item>
|
|
104
|
+
<ItemMedia><HookIcon /></ItemMedia>
|
|
105
|
+
<ItemContent><ItemTitle>{h.name}</ItemTitle><ItemDescription className="ig-url">{h.url}</ItemDescription></ItemContent>
|
|
106
|
+
<ItemActions><Button size="sm" variant="ghost" {...editProps}>Edit</Button><Button size="sm" variant="ghost" {...removeProps}>Remove</Button></ItemActions>
|
|
107
|
+
</Item>}
|
|
108
|
+
renderEditor={field => <>
|
|
109
|
+
<Field label="Name" required error={field.error('name')}><Input {...field('name')} /></Field>
|
|
110
|
+
<Field label="Endpoint URL" required error={field.error('url')}><Input {...field('url')} type="url" /></Field>
|
|
111
|
+
</>}
|
|
112
|
+
onSave={async (draft, { previousValue }) => {
|
|
113
|
+
const errors = validate(draft); if (Object.keys(errors).length) return { ok: false, error: 'Check the highlighted fields.', fieldErrors: errors };
|
|
114
|
+
await igWait(300); return { ok: true, value: { ...draft, name: draft.name.trim(), revision: previousValue.revision + 1 } };
|
|
115
|
+
}}
|
|
116
|
+
onItemChange={h => setHooks(list => list.map(x => x.id === h.id ? h : x))}
|
|
117
|
+
newItem={() => ({ id: '', name: '', url: 'https://', revision: 0 })}
|
|
118
|
+
onCreate={async draft => {
|
|
119
|
+
const errors = validate(draft); if (Object.keys(errors).length) return { ok: false, error: 'Check the highlighted fields.', fieldErrors: errors };
|
|
120
|
+
await igWait(300); const hook = { ...draft, id: 'n' + ++seq.current, name: draft.name.trim(), revision: 1 }; setHooks(list => [...list, hook]); return { ok: true, value: hook };
|
|
121
|
+
}}
|
|
122
|
+
addLabel="Add webhook"
|
|
123
|
+
onReorder={async next => { await igWait(200); setHooks(next); }}
|
|
124
|
+
onRemove={async h => { await igWait(200); trash.current.set(h.id, hooks.indexOf(h)); setHooks(list => list.filter(x => x.id !== h.id)); }}
|
|
125
|
+
removal={{ kind: 'recoverable', undo: h => setHooks(list => { const next = [...list]; next.splice(trash.current.get(h.id) ?? next.length, 0, h); return next; }) }} />
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The full preview, as rendered in Storybook and checked in CI (`ig-demo`, `ig-url` are the preview page's own layout classes, not Altitude's):
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
import * as React from 'react';
|
|
132
|
+
import { Button, EmptyState, Field, Input, Item, ItemActions, ItemContent, ItemDescription, ItemGroup, ItemMedia, ItemTitle, Toaster } from 'altitude-design';
|
|
133
|
+
|
|
134
|
+
const IG_HOOKS = [
|
|
135
|
+
{ id: 'h1', name: 'Production', url: 'https://api.northwind.example/hooks/prod', revision: 1 },
|
|
136
|
+
{ id: 'h2', name: 'Staging', url: 'https://staging.northwind.example/hooks', revision: 1 },
|
|
137
|
+
{ id: 'h3', name: 'Billing events', url: 'https://billing.northwind.example/in', revision: 1 },
|
|
138
|
+
];
|
|
139
|
+
const igWait = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
140
|
+
const HookIcon = () => <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><path d="M6 4.5a2.5 2.5 0 1 1 3 2.4L7 11" /><path d="M4 12a2.5 2.5 0 1 1 2-4" /><path d="M12 12.5H8" /><circle cx="12" cy="12.5" r="1.5" /></svg>;
|
|
141
|
+
export default function App() {
|
|
142
|
+
const [hooks, setHooks] = React.useState(IG_HOOKS);
|
|
143
|
+
const [options, setOptions] = React.useState([]);
|
|
144
|
+
const trash = React.useRef(new Map());
|
|
145
|
+
const seq = React.useRef(0);
|
|
146
|
+
const validate = draft => ({ ...(draft.name.trim() ? {} : { name: 'Enter a name.' }), ...(/^https:\/\/[^\s/]+\.[^\s]+$/.test(draft.url) ? {} : { url: 'Use an https:// address.' }) });
|
|
147
|
+
return <div className="ig-demo">
|
|
148
|
+
<section>
|
|
149
|
+
<h3 id="ig-hooks">Webhooks</h3>
|
|
150
|
+
<ItemGroup aria-labelledby="ig-hooks" items={hooks} getKey={h => h.id} getLabel={h => h.name}
|
|
151
|
+
renderItem={(h, { editProps, removeProps }) => <Item>
|
|
152
|
+
<ItemMedia><HookIcon /></ItemMedia>
|
|
153
|
+
<ItemContent><ItemTitle>{h.name}</ItemTitle><ItemDescription className="ig-url">{h.url}</ItemDescription></ItemContent>
|
|
154
|
+
<ItemActions><Button size="sm" variant="ghost" {...editProps}>Edit</Button><Button size="sm" variant="ghost" {...removeProps}>Remove</Button></ItemActions>
|
|
155
|
+
</Item>}
|
|
156
|
+
renderEditor={field => <>
|
|
157
|
+
<Field label="Name" required error={field.error('name')}><Input {...field('name')} /></Field>
|
|
158
|
+
<Field label="Endpoint URL" required error={field.error('url')}><Input {...field('url')} type="url" /></Field>
|
|
159
|
+
</>}
|
|
160
|
+
onSave={async (draft, { previousValue }) => {
|
|
161
|
+
const errors = validate(draft); if (Object.keys(errors).length) return { ok: false, error: 'Check the highlighted fields.', fieldErrors: errors };
|
|
162
|
+
await igWait(300); return { ok: true, value: { ...draft, name: draft.name.trim(), revision: previousValue.revision + 1 } };
|
|
163
|
+
}}
|
|
164
|
+
onItemChange={h => setHooks(list => list.map(x => x.id === h.id ? h : x))}
|
|
165
|
+
newItem={() => ({ id: '', name: '', url: 'https://', revision: 0 })}
|
|
166
|
+
onCreate={async draft => {
|
|
167
|
+
const errors = validate(draft); if (Object.keys(errors).length) return { ok: false, error: 'Check the highlighted fields.', fieldErrors: errors };
|
|
168
|
+
await igWait(300); const hook = { ...draft, id: 'n' + ++seq.current, name: draft.name.trim(), revision: 1 }; setHooks(list => [...list, hook]); return { ok: true, value: hook };
|
|
169
|
+
}}
|
|
170
|
+
addLabel="Add webhook"
|
|
171
|
+
onReorder={async next => { await igWait(200); setHooks(next); }}
|
|
172
|
+
onRemove={async h => { await igWait(200); trash.current.set(h.id, hooks.indexOf(h)); setHooks(list => list.filter(x => x.id !== h.id)); }}
|
|
173
|
+
removal={{ kind: 'recoverable', undo: h => setHooks(list => { const next = [...list]; next.splice(trash.current.get(h.id) ?? next.length, 0, h); return next; }) }} />
|
|
174
|
+
<Toaster />
|
|
175
|
+
</section>
|
|
176
|
+
<section>
|
|
177
|
+
<h3 id="ig-options">Answer options</h3>
|
|
178
|
+
<ItemGroup aria-labelledby="ig-options" items={options} getKey={o => o.id} getLabel={o => o.label}
|
|
179
|
+
renderItem={(o, { editProps, removeProps }) => <Item><ItemContent><ItemTitle>{o.label}</ItemTitle></ItemContent>
|
|
180
|
+
<ItemActions><Button size="sm" variant="ghost" {...editProps}>Edit</Button><Button size="sm" variant="ghost" {...removeProps}>Delete</Button></ItemActions></Item>}
|
|
181
|
+
renderEditor={field => <Field label="Option" required error={field.error('label')}><Input {...field('label')} /></Field>}
|
|
182
|
+
onItemChange={o => setOptions(list => list.map(x => x.id === o.id ? o : x))}
|
|
183
|
+
newItem={() => ({ id: '', label: '' })}
|
|
184
|
+
onCreate={draft => { if (!draft.label.trim()) return { ok: false, error: 'Enter an option.', fieldErrors: { label: 'Enter an option.' } }; const o = { id: 'o' + ++seq.current, label: draft.label.trim() }; setOptions(list => [...list, o]); return { ok: true, value: o }; }}
|
|
185
|
+
addLabel="Add option" onReorder={setOptions}
|
|
186
|
+
onRemove={o => setOptions(list => list.filter(x => x.id !== o.id))}
|
|
187
|
+
removal={{ kind: 'confirm', title: o => `Delete “${o.label}” permanently?`, description: () => 'Responses that chose it keep their answer, but it can’t be chosen again.', confirmLabel: 'Delete option' }}
|
|
188
|
+
empty={<EmptyState size="sm" title="No options yet" description="Add the answers people can choose from." />} />
|
|
189
|
+
</section>
|
|
190
|
+
</div>;
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
<!-- /example -->
|
|
@@ -7,3 +7,30 @@ Keys sit at level 1 in the mono family with `wall-1` under them. With `listen`,
|
|
|
7
7
|
**On touch.** `hideOnTouch` hides a key cap on a touch-only device (no hover, a coarse pointer) until a key is pressed in the session, for a hint that only makes sense with a keyboard. It's off by default, because an app may be documenting its shortcuts on purpose; Altitude's own hints (menu shortcuts, CommandPalette's keys, InlineEdit's save hint) already do this. `useKeyboardLikely()` gives the same answer to your own components.
|
|
8
8
|
|
|
9
9
|
**You provide:** the key's glyph as children (one `Kbd` per key: `<Kbd>⌘</Kbd><Kbd>K</Kbd>`), and `listen` when the shortcut is live on this page.
|
|
10
|
+
|
|
11
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
12
|
+
## Example
|
|
13
|
+
|
|
14
|
+
`Kbd` as first used in its preview:
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { Kbd } from 'altitude-design';
|
|
18
|
+
|
|
19
|
+
<Kbd listen="Meta">⌘</Kbd>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The full preview, as rendered in Storybook and checked in CI (`col`, `row`, `body`, `cap` are the preview page's own layout classes, not Altitude's):
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { Kbd } from 'altitude-design';
|
|
26
|
+
|
|
27
|
+
export default function App() {
|
|
28
|
+
return (
|
|
29
|
+
<div className="col">
|
|
30
|
+
<div className="row"><span className="body">Open the command menu</span><span><Kbd listen="Meta">⌘</Kbd><Kbd listen="k">K</Kbd></span></div>
|
|
31
|
+
<p className="cap">Click into this card and press ⌘K, or hold K.</p>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
<!-- /example -->
|
|
@@ -45,3 +45,65 @@ Change over time: latency over a day, revenue over a year, one series or a few c
|
|
|
45
45
|
- Do use `monotone` for smooth measures, and `linear` when each point is a discrete count.
|
|
46
46
|
- Don't fill the area under a line to make it look richer; that's `AreaChart`, for a total and its parts.
|
|
47
47
|
- Don't put two measures of different scale on one plot; that's two charts.
|
|
48
|
+
|
|
49
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
50
|
+
## Example
|
|
51
|
+
|
|
52
|
+
`LineChart` as first used in its preview:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { LineChart } from 'altitude-design';
|
|
56
|
+
|
|
57
|
+
<LineChart data={latency} x="t" y={{ format: (ms) => `${ms} ms` }} />
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The full preview, as rendered in Storybook and checked in CI (`lc` is the preview page's own layout class, not Altitude's):
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle, ChartContainer, LineChart } from 'altitude-design';
|
|
64
|
+
|
|
65
|
+
const t0 = Date.UTC(2026, 2, 14, 0, 0);
|
|
66
|
+
const latency = Array.from({ length: 49 }, (_, i) => ({ t: t0 + i * 30 * 60e3, p50: 92 + Math.round(18 * Math.sin(i / 6) + (i % 5)), p95: 168 + Math.round(30 * Math.sin(i / 6 + 0.6) + (i % 7) * 3) }));
|
|
67
|
+
const revenue = ['2025-07-01', '2025-08-01', '2025-09-01', '2025-10-01', '2025-11-01', '2025-12-01', '2026-01-01', '2026-02-01', '2026-03-01', '2026-04-01', '2026-05-01', '2026-06-01']
|
|
68
|
+
.map((month, i) => ({ month, revenue: [61, 64, 63, 70, 74, 72, 79, 85, 83, 92, 98, 104][i] * 1000 }));
|
|
69
|
+
const signups = Array.from({ length: 14 }, (_, i) => ({ day: `2026-04-${String(i + 1).padStart(2, '0')}`, signups: [42, 48, 51, null, null, 55, 61, 58, 63, null, 66, 70, 68, 74][i] }));
|
|
70
|
+
const signupsBefore = [38, 41, 40, 44, 47, 45, 49, 52, 50, 53, 55, 54, 57, 58].map((signups) => ({ signups }));
|
|
71
|
+
const plans = ['2026-01-01', '2026-02-01', '2026-03-01', '2026-04-01', '2026-05-01', '2026-06-01'].map((month, i) => ({ month, free: 40 + i * 2, team: 34 + i * 3, business: 30 + i * 2.6, enterprise: 12 + i }));
|
|
72
|
+
const usd = new Intl.NumberFormat('en', { style: 'currency', currency: 'USD', notation: 'compact' });
|
|
73
|
+
function Chart({ id, title, sub, children, ...props }) {
|
|
74
|
+
// With a takeaway the figure names itself: the title becomes the label over the takeaway, the heading.
|
|
75
|
+
if (props.takeaway) return (
|
|
76
|
+
<Card>
|
|
77
|
+
<CardContent style={{ paddingTop: 'var(--space-5)' }}><ChartContainer title={title} description={sub} aspect={1.9} {...props}>{children}</ChartContainer></CardContent>
|
|
78
|
+
</Card>
|
|
79
|
+
);
|
|
80
|
+
return (
|
|
81
|
+
<Card>
|
|
82
|
+
<CardHeader><CardTitle id={id}>{title}</CardTitle><CardDescription>{sub}</CardDescription></CardHeader>
|
|
83
|
+
<CardContent><ChartContainer aria-labelledby={id} aspect={1.9} {...props}>{children}</ChartContainer></CardContent>
|
|
84
|
+
</Card>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
export default function App() {
|
|
88
|
+
return (
|
|
89
|
+
<div className="lc">
|
|
90
|
+
<Chart id="l1" title="Response time" sub='14 Mar, every 30 minutes; yLabels="inline"' yLabels="inline" config={{ p50: { label: 'p50' }, p95: { label: 'p95' } }}>
|
|
91
|
+
<LineChart data={latency} x="t" y={{ format: (ms) => `${ms} ms` }} />
|
|
92
|
+
</Chart>
|
|
93
|
+
<Chart id="l2" title="Monthly revenue" takeaway="Revenue rose 70% over the year" sub='curve="monotone": no overshoot' config={{ revenue: { label: 'Revenue', format: usd.format } }} summary="Revenue rose 70% over the year, with small dips in September, December and March.">
|
|
94
|
+
<LineChart data={revenue} x="month" curve="monotone" />
|
|
95
|
+
</Chart>
|
|
96
|
+
<Chart id="l3" title="Daily signups" sub="Gaps stay gaps; read against the two weeks before" config={{ signups: { label: 'Signups' } }}>
|
|
97
|
+
<LineChart data={signups} x="day" compare={{ data: signupsBefore, label: '2 weeks before' }} />
|
|
98
|
+
</Chart>
|
|
99
|
+
<Chart id="l4" title="Seats by plan" sub="Four series, focus on Team: the rest in data-context" config={{ free: { label: 'Free' }, team: { label: 'Team' }, business: { label: 'Business' }, enterprise: { label: 'Enterprise' } }}>
|
|
100
|
+
<LineChart data={plans} x="month" focus={['team']} />
|
|
101
|
+
</Chart>
|
|
102
|
+
<Chart id="l5" title="Monthly revenue, banked" sub='aspect="banked": the segments average 45°' aspect="banked" config={{ revenue: { label: 'Revenue', format: usd.format } }}>
|
|
103
|
+
<LineChart data={revenue} x="month" curve="monotone" />
|
|
104
|
+
</Chart>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
<!-- /example -->
|
|
@@ -26,3 +26,44 @@ Navigation: a link in prose, "Learn more" after a hint, "View all invoices →"
|
|
|
26
26
|
- Do use `external` for anything that leaves your app.
|
|
27
27
|
- Don't use a link for an action (Save, Delete); that's a `Button`.
|
|
28
28
|
- Don't colour a link yourself, or take its underline away inside prose.
|
|
29
|
+
|
|
30
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
31
|
+
## Example
|
|
32
|
+
|
|
33
|
+
`Link` as first used in its preview:
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { Link } from 'altitude-design';
|
|
37
|
+
|
|
38
|
+
<Link href="#webhooks">Read about webhooks</Link>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The full preview, as rendered in Storybook and checked in CI (`lk`, `col`, `cap` are the preview page's own layout classes, not Altitude's):
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import * as React from 'react';
|
|
45
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle, Link } from 'altitude-design';
|
|
46
|
+
|
|
47
|
+
// A router's link, to show asChild: Link styles it and it keeps its own behaviour.
|
|
48
|
+
const RouterLink = React.forwardRef(({ to, ...rest }, ref) => <a ref={ref} href={'#' + to} {...rest} />);
|
|
49
|
+
export default function App() {
|
|
50
|
+
return (
|
|
51
|
+
<div className="lk">
|
|
52
|
+
<div className="col">
|
|
53
|
+
<p>Webhooks send a signed POST for each event. <Link href="#webhooks">Read about webhooks</Link> before you add an endpoint, or check the <Link href="https://status.example.com" external>status page</Link> if deliveries stop.</p>
|
|
54
|
+
<p className="cap">In prose, the underline thickens on hover. Nothing lifts, so the line of text stays put.</p>
|
|
55
|
+
</div>
|
|
56
|
+
<Card>
|
|
57
|
+
<CardHeader><CardTitle>Invoices</CardTitle><CardDescription>Your last three invoices</CardDescription></CardHeader>
|
|
58
|
+
<CardContent>
|
|
59
|
+
<div className="col">
|
|
60
|
+
<span>INV-0042 · €49.00 · Paid</span>
|
|
61
|
+
<Link asChild><RouterLink to="/billing/invoices">View all invoices →</RouterLink></Link>
|
|
62
|
+
</div>
|
|
63
|
+
</CardContent>
|
|
64
|
+
</Card>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
<!-- /example -->
|
|
@@ -30,3 +30,84 @@ An answer's Markdown, set as Altitude prose, as it streams. Models answer in Mar
|
|
|
30
30
|
- Do pass `streaming` while the answer arrives, so half-received constructs don't flash as raw text.
|
|
31
31
|
- Do give each answer its own `idPrefix` when several cite sources on one page.
|
|
32
32
|
- Don't turn on `allowHtml` for model output or anything else you don't control.
|
|
33
|
+
|
|
34
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
35
|
+
## Example
|
|
36
|
+
|
|
37
|
+
`Markdown` as first used in its preview:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { Markdown } from 'altitude-design';
|
|
41
|
+
|
|
42
|
+
<Markdown citations={sources}>{done}</Markdown>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The full preview, as rendered in Storybook and checked in CI (`md-demo`, `lbl`, `col` are the preview page's own layout classes, not Altitude's):
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { Markdown } from 'altitude-design';
|
|
49
|
+
|
|
50
|
+
const sources = [
|
|
51
|
+
{ title: 'Billing FAQ', url: 'https://help.example.com/billing/faq', excerpt: 'Annual plans can be refunded in full within 30 days of purchase.' },
|
|
52
|
+
{ title: 'Plans and pricing', url: 'https://example.com/pricing' },
|
|
53
|
+
];
|
|
54
|
+
const done = [
|
|
55
|
+
'# Refunds on annual plans',
|
|
56
|
+
'',
|
|
57
|
+
'Annual plans are refunded **in full** within 30 days [1]; after that, the unused months are credited as *account balance*, not cash [2].',
|
|
58
|
+
'',
|
|
59
|
+
'## What to check first',
|
|
60
|
+
'',
|
|
61
|
+
'- [x] The plan was bought less than 30 days ago',
|
|
62
|
+
'- [ ] No seats were added since',
|
|
63
|
+
'- [ ] The invoice is `paid`, not ~~pending~~',
|
|
64
|
+
'',
|
|
65
|
+
'| Plan | Window | Refund |',
|
|
66
|
+
'| --- | :---: | ---: |',
|
|
67
|
+
'| Monthly | none | $0 |',
|
|
68
|
+
'| Annual | 30 days | 100% |',
|
|
69
|
+
'',
|
|
70
|
+
'> Credits never expire, but they can\'t be moved to another workspace.',
|
|
71
|
+
'',
|
|
72
|
+
'Issue it from the console:',
|
|
73
|
+
'',
|
|
74
|
+
'```sh',
|
|
75
|
+
'altitude billing refund INV-0042 --full',
|
|
76
|
+
'```',
|
|
77
|
+
'',
|
|
78
|
+
'See [the billing guide](https://help.example.com/billing) for partial refunds.',
|
|
79
|
+
].join('\n');
|
|
80
|
+
const streaming = [
|
|
81
|
+
'## Steps',
|
|
82
|
+
'',
|
|
83
|
+
'1. Find the invoice',
|
|
84
|
+
'2. Check the **refund window',
|
|
85
|
+
'',
|
|
86
|
+
'| Invoice | Amount | Status |',
|
|
87
|
+
'| --- | ---: | --- |',
|
|
88
|
+
'| INV-0042 | $480 | paid |',
|
|
89
|
+
'| INV-0043 | $1',
|
|
90
|
+
].join('\n') + '';
|
|
91
|
+
const streamingCode = ['Run this:', '', '```ts', 'const refund = await billing.refund({', " invoice: 'INV-0042',"].join('\n');
|
|
92
|
+
export default function App() {
|
|
93
|
+
return (
|
|
94
|
+
<div className="md-demo">
|
|
95
|
+
<div>
|
|
96
|
+
<p className="lbl">Complete</p>
|
|
97
|
+
<Markdown citations={sources}>{done}</Markdown>
|
|
98
|
+
</div>
|
|
99
|
+
<div className="col">
|
|
100
|
+
<div>
|
|
101
|
+
<p className="lbl">Mid-stream: an unclosed bold and a half-received row</p>
|
|
102
|
+
<Markdown streaming>{streaming}</Markdown>
|
|
103
|
+
</div>
|
|
104
|
+
<div>
|
|
105
|
+
<p className="lbl">Mid-stream: an open fence</p>
|
|
106
|
+
<Markdown streaming>{streamingCode}</Markdown>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
<!-- /example -->
|
|
@@ -27,3 +27,55 @@ Where a reading sits in a known range: storage used of a quota, seats used of a
|
|
|
27
27
|
- Do use segments for parts of a whole instead of a pie or donut.
|
|
28
28
|
- Don't give it more than six segments; fold the rest into "Other".
|
|
29
29
|
- Don't colour a reading caution or critical yourself; set `high` and `critical`, so the words come with the colour.
|
|
30
|
+
|
|
31
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
32
|
+
## Example
|
|
33
|
+
|
|
34
|
+
`Meter` as first used in its preview:
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { Meter } from 'altitude-design';
|
|
38
|
+
|
|
39
|
+
<Meter label="Storage" value={used} max={100} high={80} format={gb} />
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The full preview, as rendered in Storybook and checked in CI (`mt`, `stack`, `row` are the preview page's own layout classes, not Altitude's):
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
import * as React from 'react';
|
|
46
|
+
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Meter } from 'altitude-design';
|
|
47
|
+
|
|
48
|
+
const gb = (n) => `${n} GB`;
|
|
49
|
+
export default function App() {
|
|
50
|
+
const [used, setUsed] = React.useState(62);
|
|
51
|
+
const [cpu, setCpu] = React.useState(undefined);
|
|
52
|
+
React.useEffect(() => { const t = setTimeout(() => setCpu(37), 2400); return () => clearTimeout(t); }, []);
|
|
53
|
+
return (
|
|
54
|
+
<div className="mt">
|
|
55
|
+
<Card>
|
|
56
|
+
<CardHeader><CardTitle>Plan usage</CardTitle><CardDescription>Thresholds at 80% and the limit</CardDescription></CardHeader>
|
|
57
|
+
<CardContent className="stack">
|
|
58
|
+
<Meter label="Storage" value={used} max={100} high={80} format={gb} />
|
|
59
|
+
<Meter label="Seats" value={24} max={25} high={20} format={(n) => `${n}`} />
|
|
60
|
+
<Meter label="API calls today" value={11800} max={10000} high={8000} format={(n) => n.toLocaleString('en')} />
|
|
61
|
+
<Meter label="CPU" value={cpu} format={(n) => `${n}%`} />
|
|
62
|
+
<div className="row">
|
|
63
|
+
<Button size="sm" variant="secondary" onClick={() => setUsed((u) => Math.max(0, u - 15))}>Free 15 GB</Button>
|
|
64
|
+
<Button size="sm" variant="secondary" onClick={() => setUsed((u) => u + 15)}>Use 15 GB</Button>
|
|
65
|
+
</div>
|
|
66
|
+
</CardContent>
|
|
67
|
+
</Card>
|
|
68
|
+
<Card>
|
|
69
|
+
<CardHeader><CardTitle>Storage by type</CardTitle><CardDescription>Parts of a whole, instead of a donut</CardDescription></CardHeader>
|
|
70
|
+
<CardContent className="stack">
|
|
71
|
+
<Meter label="Storage" max={100} format={gb}
|
|
72
|
+
segments={[{ key: 'media', label: 'Media', value: 38 }, { key: 'docs', label: 'Documents', value: 14 }, { key: 'mail', label: 'Mail', value: 6 }, { key: 'other', label: 'Other', value: 4 }]} />
|
|
73
|
+
<Meter label="Team seats" max={50} high={45} format={(n) => `${n}`}
|
|
74
|
+
segments={[{ key: 'admin', label: 'Admins', value: 4 }, { key: 'members', label: 'Members', value: 31 }, { key: 'guests', label: 'Guests', value: 12 }]} />
|
|
75
|
+
</CardContent>
|
|
76
|
+
</Card>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
<!-- /example -->
|
|
@@ -26,3 +26,42 @@ An inbox of what happened: mentions, assignments, approvals waiting, failed jobs
|
|
|
26
26
|
- Do keep an action on the item when it's waiting for someone ("Approve").
|
|
27
27
|
- Don't raise or tint unread items.
|
|
28
28
|
- Don't put momentary confirmations here; that's a toast.
|
|
29
|
+
|
|
30
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
31
|
+
## Example
|
|
32
|
+
|
|
33
|
+
`NotificationCenter` as first used in its preview:
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { NotificationCenter } from 'altitude-design';
|
|
37
|
+
|
|
38
|
+
<NotificationCenter items={items} onMarkRead={markRead} onMarkAllRead={() => setItems((xs) => xs.map((x) => ({ ...x, read: true })))} preferencesHref="#/settings/notifications" />
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The full preview, as rendered in Storybook and checked in CI (`col`, `nc`, `cap` are the preview page's own layout classes, not Altitude's):
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import * as React from 'react';
|
|
45
|
+
import { Button, NotificationCenter } from 'altitude-design';
|
|
46
|
+
|
|
47
|
+
const ago = (m) => new Date(Date.now() - m * 60000);
|
|
48
|
+
export default function App() {
|
|
49
|
+
const [items, setItems] = React.useState([
|
|
50
|
+
{ id: 'n1', title: 'Maya mentioned you in Q4 planning', body: '“@you can you take the pricing section?”', createdAt: ago(5), read: false, href: '#/docs/q4' },
|
|
51
|
+
{ id: 'n2', title: 'Theo asked to approve an expense', body: '€1,240 · Conference travel', createdAt: ago(48), read: false, actions: <Button size="sm" variant="secondary">Approve</Button> },
|
|
52
|
+
{ id: 'n3', title: 'Export of 812 invoices is ready', createdAt: ago(60 * 3), read: true, href: '#/exports/7' },
|
|
53
|
+
{ id: 'n4', title: 'Nightly sync failed for Salesforce', body: 'The token expired. Reconnect to resume.', createdAt: ago(60 * 27), read: true, href: '#/integrations' },
|
|
54
|
+
]);
|
|
55
|
+
const markRead = (ids) => setItems((xs) => xs.map((x) => (ids.includes(x.id) ? { ...x, read: true } : x)));
|
|
56
|
+
return (
|
|
57
|
+
<div className="col">
|
|
58
|
+
<div className="nc">
|
|
59
|
+
<Button size="sm" variant="secondary" onClick={() => setItems((xs) => [{ id: 'n' + Date.now(), title: 'Ada assigned you “Billing page”', createdAt: new Date(), read: false, href: '#/issues/212' }, ...xs])}>Simulate a new notification</Button>
|
|
60
|
+
<NotificationCenter items={items} onMarkRead={markRead} onMarkAllRead={() => setItems((xs) => xs.map((x) => ({ ...x, read: true })))} preferencesHref="#/settings/notifications" />
|
|
61
|
+
</div>
|
|
62
|
+
<p className="cap">Unread is a dot and a bolder title, never a height. A new notification arrives lifted and settles, and is announced even while the panel is closed.</p>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
<!-- /example -->
|
|
@@ -27,3 +27,40 @@ A number field: seats on a plan, rate limits, retention days, prices, quantities
|
|
|
27
27
|
- Do put the unit in `format`, not in the label, so it's spoken with the value.
|
|
28
28
|
- Don't use it for identifiers that happen to be digits (a ZIP code, a card number); that's an `Input`.
|
|
29
29
|
- Don't use it to choose roughly within a range; that's a `Slider`.
|
|
30
|
+
|
|
31
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
32
|
+
## Example
|
|
33
|
+
|
|
34
|
+
`NumberInput` as first used in its preview:
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { NumberInput } from 'altitude-design';
|
|
38
|
+
|
|
39
|
+
<NumberInput value={seats} onValueChange={setSeats} min={1} max={500} incrementLabel="Increase seats" decrementLabel="Decrease seats" />
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The full preview, as rendered in Storybook and checked in CI (`col`, `ni`, `cap` are the preview page's own layout classes, not Altitude's):
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
import * as React from 'react';
|
|
46
|
+
import { Field, NumberInput } from 'altitude-design';
|
|
47
|
+
|
|
48
|
+
export default function App() {
|
|
49
|
+
const [seats, setSeats] = React.useState(12);
|
|
50
|
+
const [days, setDays] = React.useState(30);
|
|
51
|
+
const [price, setPrice] = React.useState(1250.5);
|
|
52
|
+
const [rate, setRate] = React.useState(null);
|
|
53
|
+
return (
|
|
54
|
+
<div className="col">
|
|
55
|
+
<div className="ni">
|
|
56
|
+
<Field label="Seats" hint="1 to 500"><NumberInput value={seats} onValueChange={setSeats} min={1} max={500} incrementLabel="Increase seats" decrementLabel="Decrease seats" /></Field>
|
|
57
|
+
<Field label="Keep logs for"><NumberInput value={days} onValueChange={setDays} min={1} max={365} format={{ style: 'unit', unit: 'day', unitDisplay: 'long' }} /></Field>
|
|
58
|
+
<Field label="Preis pro Monat" hint="German: type 1.250,50"><NumberInput value={price} onValueChange={setPrice} min={0} step={0.5} locale="de-DE" format={{ style: 'currency', currency: 'EUR' }} /></Field>
|
|
59
|
+
<Field label="Sampling rate"><NumberInput value={rate} onValueChange={setRate} min={0} max={1} step={0.05} format={{ style: 'percent' }} placeholder="10" /></Field>
|
|
60
|
+
</div>
|
|
61
|
+
<p className="cap">Arrow keys step (Shift for ten); Home and End go to the limits; a stepper at its limit lies flat. Values clamp to their range when you leave the field.</p>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
<!-- /example -->
|
|
@@ -27,3 +27,48 @@ The block every page opens with: breadcrumb, title, a line of description, the p
|
|
|
27
27
|
- Do keep the description to one line about what the page is for.
|
|
28
28
|
- Don't put a PageHeader inside a `Card`; that's `CardHeader`.
|
|
29
29
|
- Don't make the whole header a raised card. It lies on the ground, and only rises while content passes under it.
|
|
30
|
+
|
|
31
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
32
|
+
## Example
|
|
33
|
+
|
|
34
|
+
`PageHeader` as first used in its preview:
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { Breadcrumb, Button, PageHeader, PageHeaderDescription, PageHeaderTitle, Tabs, TabsList, TabsTrigger } from 'altitude-design';
|
|
38
|
+
|
|
39
|
+
<PageHeader sticky
|
|
40
|
+
breadcrumb={<Breadcrumb items={[{ label: 'Settings', href: '#/settings' }, { label: 'Members' }]} />}
|
|
41
|
+
primary={<Button variant="primary">Invite members</Button>}
|
|
42
|
+
actions={<Button variant="secondary">Export list</Button>}
|
|
43
|
+
overflow={[{ label: 'Download audit log' }, { label: 'Members settings' }]}
|
|
44
|
+
tabs={<Tabs variant="segmented" value={tab} onValueChange={setTab}><TabsList aria-label="Members sections"><TabsTrigger value="members">Members</TabsTrigger><TabsTrigger value="invites">Invitations</TabsTrigger><TabsTrigger value="roles">Roles</TabsTrigger></TabsList></Tabs>}>
|
|
45
|
+
<PageHeaderTitle>Members</PageHeaderTitle>
|
|
46
|
+
<PageHeaderDescription>People who can see and edit this workspace.</PageHeaderDescription>
|
|
47
|
+
</PageHeader>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The full preview, as rendered in Storybook and checked in CI (`ph`, `rows` are the preview page's own layout classes, not Altitude's):
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import * as React from 'react';
|
|
54
|
+
import { Breadcrumb, Button, PageHeader, PageHeaderDescription, PageHeaderTitle, Tabs, TabsList, TabsTrigger } from 'altitude-design';
|
|
55
|
+
|
|
56
|
+
export default function App() {
|
|
57
|
+
const [tab, setTab] = React.useState('members');
|
|
58
|
+
return (
|
|
59
|
+
<div className="ph">
|
|
60
|
+
<PageHeader sticky
|
|
61
|
+
breadcrumb={<Breadcrumb items={[{ label: 'Settings', href: '#/settings' }, { label: 'Members' }]} />}
|
|
62
|
+
primary={<Button variant="primary">Invite members</Button>}
|
|
63
|
+
actions={<Button variant="secondary">Export list</Button>}
|
|
64
|
+
overflow={[{ label: 'Download audit log' }, { label: 'Members settings' }]}
|
|
65
|
+
tabs={<Tabs variant="segmented" value={tab} onValueChange={setTab}><TabsList aria-label="Members sections"><TabsTrigger value="members">Members</TabsTrigger><TabsTrigger value="invites">Invitations</TabsTrigger><TabsTrigger value="roles">Roles</TabsTrigger></TabsList></Tabs>}>
|
|
66
|
+
<PageHeaderTitle>Members</PageHeaderTitle>
|
|
67
|
+
<PageHeaderDescription>People who can see and edit this workspace.</PageHeaderDescription>
|
|
68
|
+
</PageHeader>
|
|
69
|
+
<div className="rows">{Array.from({ length: 10 }, (_, i) => <div key={i} />)}</div>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
<!-- /example -->
|
|
@@ -23,3 +23,36 @@ Moves through pages of a collection the server pages: invoices, members, audit e
|
|
|
23
23
|
- Do show the summary; "Page 3 of 11" says less than "41–60 of 214".
|
|
24
24
|
- Don't page short lists; show them whole.
|
|
25
25
|
- Don't use it for steps in a process; that's a `Stepper`.
|
|
26
|
+
|
|
27
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
28
|
+
## Example
|
|
29
|
+
|
|
30
|
+
`Pagination` as first used in its preview:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { Pagination } from 'altitude-design';
|
|
34
|
+
|
|
35
|
+
<Pagination page={page} pageCount={11} onPageChange={go} total={214} pageSize={20} noun="invoices" loading={loading} />
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The full preview, as rendered in Storybook and checked in CI (`col`, `cap` are the preview page's own layout classes, not Altitude's):
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import * as React from 'react';
|
|
42
|
+
import { Pagination } from 'altitude-design';
|
|
43
|
+
|
|
44
|
+
export default function App() {
|
|
45
|
+
const [page, setPage] = React.useState(3);
|
|
46
|
+
const [loading, setLoading] = React.useState(false);
|
|
47
|
+
const [cursor, setCursor] = React.useState(1);
|
|
48
|
+
const go = (p) => { setPage(p); setLoading(true); setTimeout(() => setLoading(false), 500); };
|
|
49
|
+
return (
|
|
50
|
+
<div className="col">
|
|
51
|
+
<Pagination page={page} pageCount={11} onPageChange={go} total={214} pageSize={20} noun="invoices" loading={loading} />
|
|
52
|
+
<Pagination page={cursor} onPageChange={setCursor} hasNext={cursor < 4} aria-label="Audit log pages" />
|
|
53
|
+
<p className="cap">The current page is a raised thumb in the trench; it's held pressed while the next page loads. Without a page count it's Previous, Next and "Page 3".</p>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
<!-- /example -->
|
|
@@ -32,3 +32,39 @@ A password field: signing in, signing up, a reset, "change password" in settings
|
|
|
32
32
|
- Do say your rules before someone breaks them, as requirements.
|
|
33
33
|
- Don't draw strength as a progress bar.
|
|
34
34
|
- Don't show a strength meter when signing in; it's for creating a password.
|
|
35
|
+
|
|
36
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
37
|
+
## Example
|
|
38
|
+
|
|
39
|
+
`PasswordInput` as first used in its preview:
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { PasswordInput } from 'altitude-design';
|
|
43
|
+
|
|
44
|
+
<PasswordInput defaultValue="correct horse" />
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The full preview, as rendered in Storybook and checked in CI (`pw` is the preview page's own layout class, not Altitude's):
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { Field, PasswordInput } from 'altitude-design';
|
|
51
|
+
|
|
52
|
+
// A stand-in score: in an app this would be zxcvbn or your own rules.
|
|
53
|
+
const score = (v) => Math.min(4, [v.length >= 8, v.length >= 12, /[A-Z]/.test(v) && /[a-z]/.test(v), /\d/.test(v), /[^\w\s]/.test(v)].filter(Boolean).length - (v.length < 8 ? 1 : 0));
|
|
54
|
+
const common = ['password', 'password1', '12345678', 'qwerty123'];
|
|
55
|
+
export default function App() {
|
|
56
|
+
return (
|
|
57
|
+
<div className="pw">
|
|
58
|
+
<Field label="Password"><PasswordInput defaultValue="correct horse" /></Field>
|
|
59
|
+
<Field label="New password">
|
|
60
|
+
<PasswordInput defaultValue="Glacier7" strength={score}
|
|
61
|
+
requirements={[
|
|
62
|
+
{ label: 'At least 12 characters', test: (v) => v.length >= 12 },
|
|
63
|
+
{ label: 'Not a common password', test: (v) => v.length > 0 && !common.includes(v.toLowerCase()) },
|
|
64
|
+
]} />
|
|
65
|
+
</Field>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
<!-- /example -->
|