altitude-design 0.4.0 → 0.4.1
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 +115 -115
- 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 +1 -1
- package/dist/guidance/visual/Research.md +1 -1
- 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
|
@@ -34,3 +34,32 @@ Collapsing a pane changes its size on `ease-glide`, not its level.
|
|
|
34
34
|
- Do set a `minSize` that keeps a pane usable.
|
|
35
35
|
- Don't use it for a task that sits on top of the page; that's a `Sheet`.
|
|
36
36
|
- Don't raise a pane into a card.
|
|
37
|
+
|
|
38
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
39
|
+
## Example
|
|
40
|
+
|
|
41
|
+
The full preview, as rendered in Storybook and checked in CI (`rz`, `detail`, `cap` are the preview page's own layout classes, not Altitude's):
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import * as React from 'react';
|
|
45
|
+
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from 'altitude-design';
|
|
46
|
+
|
|
47
|
+
const threads = ['Invoice INV-0042 failed', 'Welcome to the beta', 'Your export is ready', 'Q4 planning notes', 'Security alert: new sign-in'];
|
|
48
|
+
export default function App() {
|
|
49
|
+
const [open, setOpen] = React.useState(0);
|
|
50
|
+
return (
|
|
51
|
+
<div className="rz">
|
|
52
|
+
<ResizablePanelGroup direction="horizontal">
|
|
53
|
+
<ResizablePanel defaultSize={35} minSize={20} maxSize={60} collapsible aria-label="Conversations">
|
|
54
|
+
<ul>{threads.map((t, i) => <li key={t}><button aria-current={i === open || undefined} onClick={() => setOpen(i)}>{t}</button></li>)}</ul>
|
|
55
|
+
</ResizablePanel>
|
|
56
|
+
<ResizableHandle />
|
|
57
|
+
<ResizablePanel aria-label="Conversation">
|
|
58
|
+
<div className="detail"><h2>{threads[open]}</h2><p className="cap">Drag the grip, or focus it and use the arrow keys; Enter collapses the list. The panes stay on the ground; only the grip lifts, and floats while held.</p></div>
|
|
59
|
+
</ResizablePanel>
|
|
60
|
+
</ResizablePanelGroup>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
<!-- /example -->
|
|
@@ -25,3 +25,41 @@ const record = useEditRecord({ initialValue: settings, validate, validationMode:
|
|
|
25
25
|
- Do keep the draft when a save fails.
|
|
26
26
|
- Don't use it beside an `EditRegion`, which has its own Save.
|
|
27
27
|
- Don't show it on the first keystroke.
|
|
28
|
+
|
|
29
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
30
|
+
## Example
|
|
31
|
+
|
|
32
|
+
`SaveBar` as first used in its preview:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { SaveBar } from 'altitude-design';
|
|
36
|
+
|
|
37
|
+
<SaveBar record={record} />
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The full preview, as rendered in Storybook and checked in CI (`col`, `sb`, `cap` are the preview page's own layout classes, not Altitude's):
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import * as React from 'react';
|
|
44
|
+
import { Field, Input, SaveBar, Switch, useEditRecord } from 'altitude-design';
|
|
45
|
+
|
|
46
|
+
export default function App() {
|
|
47
|
+
const [failNext, setFailNext] = React.useState(false);
|
|
48
|
+
const fail = React.useRef(false); fail.current = failNext;
|
|
49
|
+
const record = useEditRecord({
|
|
50
|
+
initialValue: { name: 'Acme Inc.', domain: 'acme.dev', seats: '25', region: 'Frankfurt' }, validationMode: 'submit',
|
|
51
|
+
validate: (d) => (d.name.trim() ? {} : { name: 'Enter a workspace name' }),
|
|
52
|
+
save: (draft) => new Promise((r) => setTimeout(() => r(fail.current ? { ok: false, error: "We couldn't reach the server." } : { ok: true, value: draft }), 900)),
|
|
53
|
+
});
|
|
54
|
+
const f = (key, label) => <Field label={label} error={record.errors[key]}><Input value={record.draft[key]} onChange={(e) => record.setDraft({ ...record.draft, [key]: e.target.value })} /></Field>;
|
|
55
|
+
return (
|
|
56
|
+
<div className="col">
|
|
57
|
+
<div className="sb">{f('name', 'Workspace name')}{f('domain', 'Domain')}{f('seats', 'Seats')}{f('region', 'Region')}</div>
|
|
58
|
+
<Switch label="Make the next save fail" checked={failNext} onCheckedChange={setFailNext} />
|
|
59
|
+
<p className="cap">Change a field: once the change settles, the bar rises from the bottom edge. ⌘S or Save hovers while saving and lowers away when saved; a failed save drops into a trench and keeps your changes.</p>
|
|
60
|
+
<SaveBar record={record} />
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
<!-- /example -->
|
|
@@ -22,3 +22,61 @@ const views = useSavedViews({ views: saved, onSave, onCreate });
|
|
|
22
22
|
- Do keep the default view ("All deals") unfiltered.
|
|
23
23
|
- Don't save a view on every change; a draft is saved when someone says so.
|
|
24
24
|
- Don't use segmented tabs; the views own the table beneath them, so they're folder tabs.
|
|
25
|
+
|
|
26
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
27
|
+
## Example
|
|
28
|
+
|
|
29
|
+
`SavedViews` as first used in its preview:
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { DataTable, FilterBar, FormattedNumber, SavedViews } from 'altitude-design';
|
|
33
|
+
|
|
34
|
+
<SavedViews views={views} aria-label="Deal views">
|
|
35
|
+
<FilterBar fields={fields} {...views.filterBarProps} aria-label="Filter deals" />
|
|
36
|
+
<DataTable aria-label="Deals" rows={deals} getRowId={(r) => r.id} {...views.tableProps} columnsMenu density="compact"
|
|
37
|
+
relevance={(r) => (value.some((f) => f.value !== undefined) ? (matches(r, value, match) ? 'match' : 'miss') : undefined)}
|
|
38
|
+
columns={[{ key: 'name', header: 'Deal', sortValue: (r) => r.name }, { key: 'stage', header: 'Stage', sortValue: (r) => r.stage }, { key: 'owner', header: 'Owner', sortValue: (r) => r.owner },
|
|
39
|
+
{ key: 'value', header: 'Value', align: 'end', sortValue: (r) => r.value, cell: (r) => <FormattedNumber value={r.value} format="currency" currency="USD" minorUnits={false} maximumFractionDigits={0} /> }]} />
|
|
40
|
+
</SavedViews>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The full preview, as rendered in Storybook and checked in CI:
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import * as React from 'react';
|
|
47
|
+
import { DataTable, FilterBar, FormattedNumber, SavedViews, useSavedViews } from 'altitude-design';
|
|
48
|
+
|
|
49
|
+
const deals = [
|
|
50
|
+
{ id: 'd1', name: 'Northwind renewal', stage: 'Negotiation', owner: 'Maya', value: 48000 },
|
|
51
|
+
{ id: 'd2', name: 'Globex expansion', stage: 'Proposal', owner: 'Theo', value: 120000 },
|
|
52
|
+
{ id: 'd3', name: 'Initech pilot', stage: 'Discovery', owner: 'Maya', value: 9000 },
|
|
53
|
+
{ id: 'd4', name: 'Umbrella security', stage: 'Negotiation', owner: 'Ada', value: 76000 },
|
|
54
|
+
{ id: 'd5', name: 'Hooli seats', stage: 'Closed won', owner: 'Theo', value: 31000 },
|
|
55
|
+
];
|
|
56
|
+
const fields = [
|
|
57
|
+
{ key: 'stage', label: 'Stage', type: 'enum', options: ['Discovery', 'Proposal', 'Negotiation', 'Closed won'].map((x) => ({ value: x, label: x })) },
|
|
58
|
+
{ key: 'owner', label: 'Owner', type: 'enum', options: ['Maya', 'Theo', 'Ada'].map((x) => ({ value: x, label: x })) },
|
|
59
|
+
];
|
|
60
|
+
const matches = (r, filters, match) => { const t = filters.filter((f) => f.value !== undefined).map((f) => (f.operator === 'is' ? r[f.field] === f.value : r[f.field] !== f.value)); return !t.length || (match === 'any' ? t.some(Boolean) : t.every(Boolean)); };
|
|
61
|
+
export default function App() {
|
|
62
|
+
const [saved, setSaved] = React.useState([
|
|
63
|
+
{ id: 'all', name: 'All deals', sort: { key: 'value', direction: 'descending' } },
|
|
64
|
+
{ id: 'mine', name: 'My open deals', filters: [{ id: 'f1', field: 'owner', operator: 'is', value: 'Maya' }, { id: 'f2', field: 'stage', operator: 'is not', value: 'Closed won' }] },
|
|
65
|
+
{ id: 'late', name: 'In negotiation', filters: [{ id: 'f3', field: 'stage', operator: 'is', value: 'Negotiation' }], columnVisibility: { owner: false } },
|
|
66
|
+
]);
|
|
67
|
+
const views = useSavedViews({ views: saved,
|
|
68
|
+
onSave: (v) => setSaved((xs) => xs.map((x) => (x.id === v.id ? v : x))),
|
|
69
|
+
onCreate: (v) => setSaved((xs) => [...xs, { ...v, id: 'v' + xs.length }]) });
|
|
70
|
+
const { value, match } = views.filterBarProps;
|
|
71
|
+
return (
|
|
72
|
+
<SavedViews views={views} aria-label="Deal views">
|
|
73
|
+
<FilterBar fields={fields} {...views.filterBarProps} aria-label="Filter deals" />
|
|
74
|
+
<DataTable aria-label="Deals" rows={deals} getRowId={(r) => r.id} {...views.tableProps} columnsMenu density="compact"
|
|
75
|
+
relevance={(r) => (value.some((f) => f.value !== undefined) ? (matches(r, value, match) ? 'match' : 'miss') : undefined)}
|
|
76
|
+
columns={[{ key: 'name', header: 'Deal', sortValue: (r) => r.name }, { key: 'stage', header: 'Stage', sortValue: (r) => r.stage }, { key: 'owner', header: 'Owner', sortValue: (r) => r.owner },
|
|
77
|
+
{ key: 'value', header: 'Value', align: 'end', sortValue: (r) => r.value, cell: (r) => <FormattedNumber value={r.value} format="currency" currency="USD" minorUnits={false} maximumFractionDigits={0} /> }]} />
|
|
78
|
+
</SavedViews>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
<!-- /example -->
|
|
@@ -39,3 +39,66 @@ Two measures per record, to see whether they move together and to spot the ones
|
|
|
39
39
|
- Do say what a correlation means in the `summary`; don't let a trend line stand in for a cause.
|
|
40
40
|
- Don't use more than three groups; split into small multiples.
|
|
41
41
|
- Don't size dots by a third measure; it's two measures per record, read by position.
|
|
42
|
+
|
|
43
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
44
|
+
## Example
|
|
45
|
+
|
|
46
|
+
`ScatterChart` as first used in its preview:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { ChartLegend, ChartLegendContent, ScatterChart } from 'altitude-design';
|
|
50
|
+
|
|
51
|
+
<ScatterChart data={endpoints} x="kb" y="ms" group="service" id="name" name="name" label={(d) => (d.ms > 250 && d.kb < 300 ? d.name : null)} trend>
|
|
52
|
+
<ChartLegend content={<ChartLegendContent toggle label="Services" />} />
|
|
53
|
+
</ScatterChart>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The full preview, as rendered in Storybook and checked in CI (`sc`, `sc-foot` are the preview page's own layout classes, not Altitude's):
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import * as React from 'react';
|
|
60
|
+
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, ChartContainer, ChartLegend, ChartLegendContent, ScatterChart } from 'altitude-design';
|
|
61
|
+
|
|
62
|
+
const svc = ['Checkout', 'Search', 'Media'];
|
|
63
|
+
// p95 latency against payload per endpoint: latency climbs with payload, Media's big payloads most of all.
|
|
64
|
+
const endpoints = Array.from({ length: 30 }, (_, i) => {
|
|
65
|
+
const service = svc[i % 3], kb = Math.round(8 + ((i * 53) % 97) * (service === 'Media' ? 4.1 : 2.2));
|
|
66
|
+
const ms = Math.round(70 + kb * (service === 'Media' ? 0.62 : 0.48) + ((i * 37) % 41) - 20 + (i === 13 ? 190 : 0));
|
|
67
|
+
return { name: `${service} ${['list', 'get', 'create', 'update', 'search', 'export', 'upload', 'sync', 'batch', 'stream'][Math.floor(i / 3)]}`, service, kb, ms };
|
|
68
|
+
});
|
|
69
|
+
const kb = (v) => `${Math.round(v)} KB`, ms = (v) => `${Math.round(v)} ms`;
|
|
70
|
+
const usd = new Intl.NumberFormat('en', { style: 'currency', currency: 'USD', notation: 'compact' });
|
|
71
|
+
const month = (m) => Array.from({ length: 16 }, (_, i) => {
|
|
72
|
+
const seats = 20 + ((i * 29) % 140) + m * (i % 4) * 6;
|
|
73
|
+
return { id: 'c' + i, name: ['Acme', 'Globex', 'Initech', 'Umbrella', 'Hooli', 'Vandelay', 'Stark', 'Wayne', 'Wonka', 'Tyrell', 'Cyberdyne', 'Soylent', 'Aperture', 'Massive', 'Oscorp', 'Gringotts'][i], seats, spend: Math.round(seats * (38 + ((i * 17) % 23)) + m * 300) };
|
|
74
|
+
});
|
|
75
|
+
export default function App() {
|
|
76
|
+
const [m, setM] = React.useState(0);
|
|
77
|
+
const [picked, setPicked] = React.useState(null);
|
|
78
|
+
const customers = React.useMemo(() => month(m), [m]);
|
|
79
|
+
return (
|
|
80
|
+
<div className="sc">
|
|
81
|
+
<Card>
|
|
82
|
+
<CardHeader><CardTitle id="sc1">Latency against payload</CardTitle><CardDescription>p95 per endpoint, by service, with the trend</CardDescription></CardHeader>
|
|
83
|
+
<CardContent>
|
|
84
|
+
<ChartContainer aria-labelledby="sc1" aspect={1.5} config={{ kb: { label: 'Payload', format: kb }, ms: { label: 'p95', format: ms } }}>
|
|
85
|
+
<ScatterChart data={endpoints} x="kb" y="ms" group="service" id="name" name="name" label={(d) => (d.ms > 250 && d.kb < 300 ? d.name : null)} trend>
|
|
86
|
+
<ChartLegend content={<ChartLegendContent toggle label="Services" />} />
|
|
87
|
+
</ScatterChart>
|
|
88
|
+
</ChartContainer>
|
|
89
|
+
</CardContent>
|
|
90
|
+
</Card>
|
|
91
|
+
<Card>
|
|
92
|
+
<CardHeader><CardTitle id="sc2">Spend against seats</CardTitle><CardDescription>{picked ? `${picked.name} selected: the rest recede` : 'Per customer. Select one; dots glide by id when the month changes'}</CardDescription></CardHeader>
|
|
93
|
+
<CardContent>
|
|
94
|
+
<ChartContainer aria-labelledby="sc2" aspect={1.5} views config={{ seats: { label: 'Seats' }, spend: { label: 'Spend', format: usd.format } }}>
|
|
95
|
+
<ScatterChart data={customers} x="seats" y="spend" id="id" name="name" selected={picked ? picked.id : null} onSelect={setPicked} />
|
|
96
|
+
</ChartContainer>
|
|
97
|
+
<div className="sc-foot"><Button size="sm" variant="secondary" onClick={() => setM((n) => (n + 1) % 3)}>Next month</Button><span>Month {m + 1} of 3</span></div>
|
|
98
|
+
</CardContent>
|
|
99
|
+
</Card>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
<!-- /example -->
|
|
@@ -74,3 +74,69 @@ The events and their writes (with concurrency guards), time zones, and the detai
|
|
|
74
74
|
The `Schedule` section of `build/previews.txt` is a room-booking work week (26–30 Oct 2026): the service refuses Wednesday mornings, rooms close at 17:00 on Friday, and Ana holds the Retro. `node checks/schedule.mjs` (from `build/`; `ALT_FIXTURE_ROOT` selects React 19 fixtures) covers the grid keys and names, creating, carrying, resizing and cancelling without a pointer, a refused move with Retry, unavailable time, a collaborator's hold, rename, remove, the next week, pointer carry, drawing and the resize handle, and 320/960px layouts in both schemes under reduced motion, forced colours and `contrast="more"`.
|
|
75
75
|
**Narrow and on touch.** Under 480px of its own width the schedule shows one day at a time (`narrowView="day"`, the default; `false` keeps the week), paged by the header's previous and next. Under a coarse pointer an hour is 96px unless you set `hourHeight`, so a 15-minute slot is 24px tall, and a tap opens an event.
|
|
76
76
|
|
|
77
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
78
|
+
## Example
|
|
79
|
+
|
|
80
|
+
`Schedule` as first used in its preview:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import { Schedule } from 'altitude-design';
|
|
84
|
+
|
|
85
|
+
<Schedule aria-label="Room bookings" view="workweek" date={date} onDateChange={setDate} now="2026-10-27T10:20"
|
|
86
|
+
events={events} getKey={e => e.id} getStart={e => e.start} getEnd={e => e.end} getLabel={e => e.title}
|
|
87
|
+
dayStart="08:00" dayEnd="18:00"
|
|
88
|
+
renderEvent={e => <><strong>{e.title}</strong><small className="sch-room">{e.start.slice(11)}–{e.end.slice(11)} · {e.room}</small></>}
|
|
89
|
+
heldBy={e => e.id === 'e6' ? { name: 'Ana', presence: 2 } : null}
|
|
90
|
+
// Rooms close at 17:00 on Fridays.
|
|
91
|
+
isUnavailable={({ start, end }) => start.slice(0, 10) === '2026-10-30' && end.slice(11) > '17:00'}
|
|
92
|
+
onChange={async ({ id, start, end }) => {
|
|
93
|
+
await schWait(300);
|
|
94
|
+
// The demo service refuses Wednesday mornings: the rooms are being cleaned.
|
|
95
|
+
if (start.slice(0, 10) === '2026-10-28' && start.slice(11) < '12:00') return { ok: false, error: 'Both rooms are being cleaned on Wednesday morning.' };
|
|
96
|
+
setEvents(list => list.map(e => e.id === id ? { ...e, start, end } : e)); return { ok: true };
|
|
97
|
+
}}
|
|
98
|
+
onCreate={async ({ start, end, title }) => { await schWait(300); const event = { id: 'n' + ++seq.current, title, start, end, room: 'Room A' }; setEvents(list => [...list, event]); return { ok: true, value: event }; }}
|
|
99
|
+
onRename={async (e, title) => { await schWait(200); setEvents(list => list.map(x => x.id === e.id ? { ...x, title } : x)); return { ok: true }; }}
|
|
100
|
+
onRemove={e => setEvents(list => list.filter(x => x.id !== e.id))} />
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The full preview, as rendered in Storybook and checked in CI (`sch-demo`, `sch-room` are the preview page's own layout classes, not Altitude's):
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import * as React from 'react';
|
|
107
|
+
import { Schedule } from 'altitude-design';
|
|
108
|
+
|
|
109
|
+
const SCH_SEED = [
|
|
110
|
+
{ id: 'e1', title: 'Standup', start: '2026-10-26T09:00', end: '2026-10-26T09:30', room: 'Room A' },
|
|
111
|
+
{ id: 'e2', title: 'Design review', start: '2026-10-27T10:00', end: '2026-10-27T11:00', room: 'Room B' },
|
|
112
|
+
{ id: 'e3', title: 'Customer call', start: '2026-10-27T10:30', end: '2026-10-27T11:15', room: 'Room A' },
|
|
113
|
+
{ id: 'e4', title: 'Planning', start: '2026-10-28T13:00', end: '2026-10-28T14:30', room: 'Room B' },
|
|
114
|
+
{ id: 'e5', title: 'Interview', start: '2026-10-29T15:00', end: '2026-10-29T16:00', room: 'Room A' },
|
|
115
|
+
{ id: 'e6', title: 'Retro', start: '2026-10-30T11:00', end: '2026-10-30T12:00', room: 'Room B' },
|
|
116
|
+
];
|
|
117
|
+
const schWait = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
118
|
+
export default function App() {
|
|
119
|
+
const [date, setDate] = React.useState('2026-10-26');
|
|
120
|
+
const [events, setEvents] = React.useState(SCH_SEED);
|
|
121
|
+
const seq = React.useRef(0);
|
|
122
|
+
return <div className="sch-demo">
|
|
123
|
+
<Schedule aria-label="Room bookings" view="workweek" date={date} onDateChange={setDate} now="2026-10-27T10:20"
|
|
124
|
+
events={events} getKey={e => e.id} getStart={e => e.start} getEnd={e => e.end} getLabel={e => e.title}
|
|
125
|
+
dayStart="08:00" dayEnd="18:00"
|
|
126
|
+
renderEvent={e => <><strong>{e.title}</strong><small className="sch-room">{e.start.slice(11)}–{e.end.slice(11)} · {e.room}</small></>}
|
|
127
|
+
heldBy={e => e.id === 'e6' ? { name: 'Ana', presence: 2 } : null}
|
|
128
|
+
// Rooms close at 17:00 on Fridays.
|
|
129
|
+
isUnavailable={({ start, end }) => start.slice(0, 10) === '2026-10-30' && end.slice(11) > '17:00'}
|
|
130
|
+
onChange={async ({ id, start, end }) => {
|
|
131
|
+
await schWait(300);
|
|
132
|
+
// The demo service refuses Wednesday mornings: the rooms are being cleaned.
|
|
133
|
+
if (start.slice(0, 10) === '2026-10-28' && start.slice(11) < '12:00') return { ok: false, error: 'Both rooms are being cleaned on Wednesday morning.' };
|
|
134
|
+
setEvents(list => list.map(e => e.id === id ? { ...e, start, end } : e)); return { ok: true };
|
|
135
|
+
}}
|
|
136
|
+
onCreate={async ({ start, end, title }) => { await schWait(300); const event = { id: 'n' + ++seq.current, title, start, end, room: 'Room A' }; setEvents(list => [...list, event]); return { ok: true, value: event }; }}
|
|
137
|
+
onRename={async (e, title) => { await schWait(200); setEvents(list => list.map(x => x.id === e.id ? { ...x, title } : x)); return { ok: true }; }}
|
|
138
|
+
onRemove={e => setEvents(list => list.filter(x => x.id !== e.id))} />
|
|
139
|
+
</div>;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
<!-- /example -->
|
|
@@ -5,3 +5,31 @@ A scrolling region whose edges show where content continues, so it never looks f
|
|
|
5
5
|
When content continues above or below, that edge gets an inner lip (`lip-top`, `lip-bottom`), as if the content curves away under the rim. With a `header`, the header sits flush at the top and rises to `elev-2` as soon as content scrolls beneath it, then settles back when you return to the top. Both counter the illusion of completeness: people stop scrolling when a region looks complete.
|
|
6
6
|
|
|
7
7
|
**You provide:** children, `maxHeight` (px or any CSS length), an optional `header`, and an `aria-label` (the body is keyboard-focusable so it can be scrolled with arrow keys).
|
|
8
|
+
|
|
9
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
10
|
+
## Example
|
|
11
|
+
|
|
12
|
+
`ScrollArea` as first used in its preview:
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { ScrollArea } from 'altitude-design';
|
|
16
|
+
|
|
17
|
+
<ScrollArea maxHeight={240} aria-label="Invoices">{rows.map(([a, b]) => <div className="item" key={a}>{a}<span>{b}</span></div>)}</ScrollArea>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The full preview, as rendered in Storybook and checked in CI (`pair`, `item`, `head` are the preview page's own layout classes, not Altitude's):
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { ScrollArea } from 'altitude-design';
|
|
24
|
+
|
|
25
|
+
const rows = Array.from({ length: 18 }, (_, i) => ['Invoice #' + (2041 + i), '$' + (120 + i * 37) + '.00']);
|
|
26
|
+
export default function App() {
|
|
27
|
+
return (
|
|
28
|
+
<div className="pair">
|
|
29
|
+
<ScrollArea maxHeight={240} aria-label="Invoices">{rows.map(([a, b]) => <div className="item" key={a}>{a}<span>{b}</span></div>)}</ScrollArea>
|
|
30
|
+
<ScrollArea maxHeight={196} header={<div className="head">Invoices · October</div>} aria-label="Invoices with header">{rows.map(([a, b]) => <div className="item" key={a}>{a}<span>{b}</span></div>)}</ScrollArea>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
<!-- /example -->
|
|
@@ -26,3 +26,49 @@ A key or token: an API key, a webhook signing secret, a personal access token. I
|
|
|
26
26
|
- Do leave out `onReveal` for secrets you don't store in a retrievable form.
|
|
27
27
|
- Don't show a stored secret in full by default.
|
|
28
28
|
- Don't put revoke one click away; it's held.
|
|
29
|
+
|
|
30
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
31
|
+
## Example
|
|
32
|
+
|
|
33
|
+
`SecretField` as first used in its preview:
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { SecretField } from 'altitude-design';
|
|
37
|
+
|
|
38
|
+
<SecretField label="Secret key" value="ak_live_EXAMPLE0000demo4f2a" once onAcknowledge={() => setSaved(true)} />
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The full preview, as rendered in Storybook and checked in CI (`sf`, `col`, `cap` are the preview page's own layout classes, not Altitude's):
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import * as React from 'react';
|
|
45
|
+
import { Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, SecretField } from 'altitude-design';
|
|
46
|
+
|
|
47
|
+
export default function App() {
|
|
48
|
+
const [saved, setSaved] = React.useState(false);
|
|
49
|
+
const [said, setSaid] = React.useState('');
|
|
50
|
+
return (
|
|
51
|
+
<div className="sf">
|
|
52
|
+
<Card>
|
|
53
|
+
<CardHeader><CardTitle>API key created</CardTitle><CardDescription>Production · created just now</CardDescription></CardHeader>
|
|
54
|
+
<CardContent>
|
|
55
|
+
<SecretField label="Secret key" value="ak_live_EXAMPLE0000demo4f2a" once onAcknowledge={() => setSaved(true)} />
|
|
56
|
+
</CardContent>
|
|
57
|
+
<CardFooter><Button variant="primary" disabled={!saved}>Done</Button></CardFooter>
|
|
58
|
+
</Card>
|
|
59
|
+
<Card>
|
|
60
|
+
<CardHeader><CardTitle>Webhook signing secret</CardTitle><CardDescription>Used to verify deliveries to /hooks/billing</CardDescription></CardHeader>
|
|
61
|
+
<CardContent>
|
|
62
|
+
<div className="col">
|
|
63
|
+
<SecretField label="Signing secret" noun="secret" prefix="whk_" maskedTail="9d1c"
|
|
64
|
+
onReveal={() => new Promise((r) => setTimeout(() => r('whk_EXAMPLE0000demo9d1c'), 400))}
|
|
65
|
+
onRotate={() => setSaid('A new secret would be created; the old one keeps working for 24 hours.')} onRevoke={() => setSaid('Secret revoked.')} />
|
|
66
|
+
<p className="cap" role="status">{said}</p>
|
|
67
|
+
</div>
|
|
68
|
+
</CardContent>
|
|
69
|
+
</Card>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
<!-- /example -->
|
|
@@ -25,3 +25,73 @@ Chooses one value from a list: an opening you fill by choosing, with the list ri
|
|
|
25
25
|
- Do use a RadioGroup instead when there are five options or fewer: people can see and compare them without opening anything.
|
|
26
26
|
- Do give a non-string `label` a `textValue`, so typeahead can find it.
|
|
27
27
|
- Don't use it for actions; that's a DropdownMenu.
|
|
28
|
+
|
|
29
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
30
|
+
## Example
|
|
31
|
+
|
|
32
|
+
`Select` as first used in its preview:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { Select } from 'altitude-design';
|
|
36
|
+
|
|
37
|
+
<Select items={regions} placeholder="Choose a region" name="region" />
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The full preview, as rendered in Storybook and checked in CI (`col`, `grid`, `cap` are the preview page's own layout classes, not Altitude's):
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import * as React from 'react';
|
|
44
|
+
import { Field, Select, SelectItem } from 'altitude-design';
|
|
45
|
+
|
|
46
|
+
const regions = [
|
|
47
|
+
{ value: "iad", label: "Virginia", description: "us-east-1" },
|
|
48
|
+
{ value: "fra", label: "Frankfurt", description: "eu-central-2" },
|
|
49
|
+
{ value: "sin", label: "Singapore", description: "ap-southeast-1" },
|
|
50
|
+
{ value: "gru", label: "S\xE3o Paulo", description: "sa-east-1" },
|
|
51
|
+
{ value: "syd", label: "Sydney", description: "Opens in March", disabled: true }
|
|
52
|
+
];
|
|
53
|
+
export default function App() {
|
|
54
|
+
const [plan, setPlan] = React.useState("team");
|
|
55
|
+
const [tz, setTz] = React.useState("");
|
|
56
|
+
return <div className="col">
|
|
57
|
+
<div className="grid">
|
|
58
|
+
<Field label="Region" hint="Where your deployments run.">
|
|
59
|
+
<Select items={regions} placeholder="Choose a region" name="region" />
|
|
60
|
+
</Field>
|
|
61
|
+
<Field label="Plan">
|
|
62
|
+
<Select value={plan} onValueChange={setPlan}>
|
|
63
|
+
<SelectItem value="solo">Solo</SelectItem>
|
|
64
|
+
<SelectItem value="team">Team</SelectItem>
|
|
65
|
+
<SelectItem value="org">Organization</SelectItem>
|
|
66
|
+
</Select>
|
|
67
|
+
</Field>
|
|
68
|
+
<Field label="Timezone" required={true} error={tz ? null : "Choose a timezone."}>
|
|
69
|
+
<Select
|
|
70
|
+
value={tz}
|
|
71
|
+
onValueChange={setTz}
|
|
72
|
+
placeholder="Choose a timezone"
|
|
73
|
+
items={[{ value: "utc", label: "UTC" }, { value: "lis", label: "Europe/Lisbon" }, { value: "nyc", label: "America/New_York" }]}
|
|
74
|
+
/>
|
|
75
|
+
</Field>
|
|
76
|
+
<Field label="Runtime">
|
|
77
|
+
<Select
|
|
78
|
+
defaultValue="node"
|
|
79
|
+
defaultOpen={true}
|
|
80
|
+
items={[{ value: "node", label: "Node.js 22" }, { value: "bun", label: "Bun 1.2" }, { value: "deno", label: "Deno 2" }, { value: "edge", label: "Edge (V8 isolates)" }]}
|
|
81
|
+
/>
|
|
82
|
+
</Field>
|
|
83
|
+
<Field label="Owner">
|
|
84
|
+
<Select
|
|
85
|
+
disabled={true}
|
|
86
|
+
defaultValue="mara"
|
|
87
|
+
items={[{ value: "mara", label: "Mara Quist" }]}
|
|
88
|
+
/>
|
|
89
|
+
</Field>
|
|
90
|
+
</div>
|
|
91
|
+
<p className="cap" style={{ marginTop: 150 }}>
|
|
92
|
+
Empty fields are holes with their back wall showing; choosing raises the floor flush. Type a letter on a focused field to jump to it.
|
|
93
|
+
</p>
|
|
94
|
+
</div>;
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
<!-- /example -->
|
|
@@ -24,3 +24,47 @@ A divider: between sections in a card, groups in a toolbar, items in a sidebar,
|
|
|
24
24
|
- Do make a separator between groups of toolbar buttons `decorative`.
|
|
25
25
|
- Don't use it to fake a table's row lines; `Table` and `ItemGroup` draw their own (`ItemSeparator`).
|
|
26
26
|
- Don't give it a colour, a thickness or a shadow.
|
|
27
|
+
|
|
28
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
29
|
+
## Example
|
|
30
|
+
|
|
31
|
+
`Separator` as first used in its preview:
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { Separator } from 'altitude-design';
|
|
35
|
+
|
|
36
|
+
<Separator />
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The full preview, as rendered in Storybook and checked in CI (`sp`, `cap`, `col`, `bar`, `signin` are the preview page's own layout classes, not Altitude's):
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Separator } from 'altitude-design';
|
|
43
|
+
|
|
44
|
+
export default function App() {
|
|
45
|
+
return (
|
|
46
|
+
<div className="sp">
|
|
47
|
+
<Card>
|
|
48
|
+
<CardHeader><CardTitle>Workspace</CardTitle><CardDescription>Name, URL and region</CardDescription></CardHeader>
|
|
49
|
+
<Separator />
|
|
50
|
+
<CardContent><p className="cap">Sections in a card are parted by a horizontal separator: role="separator", 1px of line.</p></CardContent>
|
|
51
|
+
</Card>
|
|
52
|
+
<div className="col">
|
|
53
|
+
<div className="bar">
|
|
54
|
+
<Button size="sm" variant="ghost">Bold</Button>
|
|
55
|
+
<Button size="sm" variant="ghost">Italic</Button>
|
|
56
|
+
<Separator orientation="vertical" decorative />
|
|
57
|
+
<Button size="sm" variant="ghost">Link</Button>
|
|
58
|
+
</div>
|
|
59
|
+
<div className="signin">
|
|
60
|
+
<Button variant="secondary">Continue with SSO</Button>
|
|
61
|
+
<Separator label="or" />
|
|
62
|
+
<Input placeholder="Email address" aria-label="Email address" />
|
|
63
|
+
</div>
|
|
64
|
+
<p className="cap">A vertical one between groups of buttons is decorative. "or" is read as a word; its lines are drawn.</p>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
<!-- /example -->
|
|
@@ -30,3 +30,97 @@ A large panel laid over the page at level 4, anchored to an edge, for a task tha
|
|
|
30
30
|
A modal Sheet shares document-scoped isolation, scroll ownership and eligible-focus containment with Dialog. Its locks remain through exit. Opening a Dialog from a Sheet suspends the Sheet, then resumes its focus and existing background policy after the Dialog closes. A Sheet requested beneath a summit cannot take its focus or `aria-modal` ownership. A stationary Dialog also removes the lower Sheet's scrim blur until it closes.
|
|
31
31
|
|
|
32
32
|
`modal={false}` keeps the page interactive and scrollable: no scrim, recession or focus trap. Closing that Sheet does not restore focus over a newer modal.
|
|
33
|
+
|
|
34
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
35
|
+
## Example
|
|
36
|
+
|
|
37
|
+
`Sheet` as first used in its preview:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { Button, Input, Sheet, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from 'altitude-design';
|
|
41
|
+
|
|
42
|
+
<Sheet side="bottom" size="sm" trigger={<Button>Share</Button>}>
|
|
43
|
+
<SheetHeader>
|
|
44
|
+
<SheetTitle>Share Northwind</SheetTitle>
|
|
45
|
+
<SheetDescription>
|
|
46
|
+
Anyone with the link can view the latest deployment.
|
|
47
|
+
</SheetDescription>
|
|
48
|
+
</SheetHeader>
|
|
49
|
+
<Input
|
|
50
|
+
defaultValue="https://acme.dev/n/8dc01b2"
|
|
51
|
+
readOnly={true}
|
|
52
|
+
aria-label="Share link"
|
|
53
|
+
/>
|
|
54
|
+
<SheetFooter><Button variant="primary">Copy link</Button></SheetFooter>
|
|
55
|
+
</Sheet>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The full preview, as rendered in Storybook and checked in CI (`page`, `row`, `grid`, `form` are the preview page's own layout classes, not Altitude's):
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import * as React from 'react';
|
|
62
|
+
import { Button, Card, Checkbox, Field, Input, Sheet, SheetDescription, SheetFooter, SheetHeader, SheetTitle, Switch, Textarea } from 'altitude-design';
|
|
63
|
+
|
|
64
|
+
export default function App() {
|
|
65
|
+
const [edit, setEdit] = React.useState(true);
|
|
66
|
+
return <div className="page">
|
|
67
|
+
<div className="row">
|
|
68
|
+
<Button variant="primary" onClick={() => setEdit(true)}>Edit project</Button>
|
|
69
|
+
<Sheet side="bottom" size="sm" trigger={<Button>Share</Button>}>
|
|
70
|
+
<SheetHeader>
|
|
71
|
+
<SheetTitle>Share Northwind</SheetTitle>
|
|
72
|
+
<SheetDescription>
|
|
73
|
+
Anyone with the link can view the latest deployment.
|
|
74
|
+
</SheetDescription>
|
|
75
|
+
</SheetHeader>
|
|
76
|
+
<Input
|
|
77
|
+
defaultValue="https://acme.dev/n/8dc01b2"
|
|
78
|
+
readOnly={true}
|
|
79
|
+
aria-label="Share link"
|
|
80
|
+
/>
|
|
81
|
+
<SheetFooter><Button variant="primary">Copy link</Button></SheetFooter>
|
|
82
|
+
</Sheet>
|
|
83
|
+
<Sheet
|
|
84
|
+
side="left"
|
|
85
|
+
size="sm"
|
|
86
|
+
modal={false}
|
|
87
|
+
trigger={<Button variant="ghost">Filters</Button>}
|
|
88
|
+
>
|
|
89
|
+
<SheetHeader>
|
|
90
|
+
<SheetTitle>Filters</SheetTitle>
|
|
91
|
+
<SheetDescription>The page stays usable while this is open.</SheetDescription>
|
|
92
|
+
</SheetHeader>
|
|
93
|
+
<Checkbox label="Production" defaultChecked={true} />
|
|
94
|
+
<Checkbox label="Preview" />
|
|
95
|
+
<Checkbox label="Failed builds only" />
|
|
96
|
+
</Sheet>
|
|
97
|
+
</div>
|
|
98
|
+
<div className="grid" data-tells-ignore="LA4 placeholder page behind the overlay">
|
|
99
|
+
<Card />
|
|
100
|
+
<Card />
|
|
101
|
+
<Card />
|
|
102
|
+
<Card />
|
|
103
|
+
<Card />
|
|
104
|
+
<Card />
|
|
105
|
+
</div>
|
|
106
|
+
<Sheet open={edit} onOpenChange={setEdit}>
|
|
107
|
+
<SheetHeader>
|
|
108
|
+
<SheetTitle>Edit project</SheetTitle>
|
|
109
|
+
<SheetDescription>Changes apply to every environment.</SheetDescription>
|
|
110
|
+
</SheetHeader>
|
|
111
|
+
<div className="form">
|
|
112
|
+
<Field label="Project name"><Input defaultValue="Northwind" /></Field>
|
|
113
|
+
<Field label="Description" hint="Shown on the project's card.">
|
|
114
|
+
<Textarea placeholder="What is this project for?" />
|
|
115
|
+
</Field>
|
|
116
|
+
<Switch label="Deploy previews for every branch" defaultChecked={true} />
|
|
117
|
+
</div>
|
|
118
|
+
<SheetFooter>
|
|
119
|
+
<Button variant="ghost" onClick={() => setEdit(false)}>Cancel</Button>
|
|
120
|
+
<Button variant="primary" onClick={() => setEdit(false)}>Save changes</Button>
|
|
121
|
+
</SheetFooter>
|
|
122
|
+
</Sheet>
|
|
123
|
+
</div>;
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
<!-- /example -->
|
|
@@ -12,3 +12,39 @@ Imprints are wells (`surface-well`, inset shadow) because they're openings waiti
|
|
|
12
12
|
- Don't show imprints for loads under about 300ms; show nothing.
|
|
13
13
|
|
|
14
14
|
Under `AltitudeProvider loading="breath"` (experimental) a Skeleton breathes in depth instead of being swept by light, on the same page clock as every other imprint, and its content fills past the ground when `loaded` turns true. See `Await`.
|
|
15
|
+
|
|
16
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
17
|
+
## Example
|
|
18
|
+
|
|
19
|
+
`Skeleton` as first used in its preview:
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { Avatar, Skeleton } from 'altitude-design';
|
|
23
|
+
|
|
24
|
+
<Skeleton loaded={loaded} shape="circle" width={44}><Avatar name="Mara Quist" size={44} status="active" /></Skeleton>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The full preview, as rendered in Storybook and checked in CI (`col`, `card`, `lines`, `txt`, `cap` are the preview page's own layout classes, not Altitude's):
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import * as React from 'react';
|
|
31
|
+
import { Avatar, Skeleton, Surface } from 'altitude-design';
|
|
32
|
+
|
|
33
|
+
export default function App() {
|
|
34
|
+
const [loaded, setLoaded] = React.useState(false);
|
|
35
|
+
React.useEffect(() => { const t = setInterval(() => setLoaded((l) => !l), 2600); return () => clearInterval(t); }, []);
|
|
36
|
+
return (
|
|
37
|
+
<div className="col">
|
|
38
|
+
<Surface level={1} className="card">
|
|
39
|
+
<Skeleton loaded={loaded} shape="circle" width={44}><Avatar name="Mara Quist" size={44} status="active" /></Skeleton>
|
|
40
|
+
<div className="lines">
|
|
41
|
+
<Skeleton loaded={loaded} width="45%" height={14}><b style={{ font: '600 14px/20px var(--font-sans)' }}>Mara Quist</b></Skeleton>
|
|
42
|
+
<Skeleton loaded={loaded} shape="text" lines={2}><p className="txt">Merged “Faster cold starts” into main. Cold starts are down 38% across all regions.</p></Skeleton>
|
|
43
|
+
</div>
|
|
44
|
+
</Surface>
|
|
45
|
+
<p className="cap">While it loads, the layout is pressed into the page; when content lands it rises out of its imprint, four at a time at most.</p>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
<!-- /example -->
|