@sableui/react 0.17.0 → 0.19.0
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/AGENTS.md +10 -3
- package/CHANGELOG.md +235 -0
- package/README.md +11 -4
- package/bin/sable.mjs +266 -30
- package/dist/{_contract-CDa9-NdB.d.ts → _contract-DlklV0Im.d.ts} +2 -2
- package/dist/activity-DT8VK9fx.d.ts +25 -0
- package/dist/adapters/github.d.ts +41 -0
- package/dist/adapters/github.js +24 -0
- package/dist/blocks/index.d.ts +1 -1
- package/dist/components/activity-heatmap/activity-heatmap.js +420 -0
- package/dist/components/activity-heatmap/activity-heatmap.meta.js +142 -0
- package/dist/components/activity-heatmap/index.js +9 -0
- package/dist/components/avatar/avatar.js +16 -7
- package/dist/components/avatar/avatar.meta.js +43 -10
- package/dist/components/avatar/avatar.variants.js +26 -2
- package/dist/components/code-block/code-block.js +5 -3
- package/dist/components/code-block/code-block.meta.js +6 -0
- package/dist/components/collapsible/collapsible.js +6 -5
- package/dist/components/collapsible/collapsible.meta.js +14 -5
- package/dist/components/data-table/data-table.js +132 -45
- package/dist/components/data-table/data-table.meta.js +63 -4
- package/dist/components/date-picker/date-picker.js +13 -6
- package/dist/components/date-picker/date-picker.meta.js +22 -0
- package/dist/components/dialog/dialog.js +25 -5
- package/dist/components/dialog/dialog.meta.js +26 -5
- package/dist/components/media-image/media-image.js +3 -1
- package/dist/components/media-image/media-image.meta.js +15 -1
- package/dist/components/media-tile/media-tile.js +13 -1
- package/dist/components/media-tile/media-tile.meta.js +10 -0
- package/dist/components/menu/menu.js +2 -0
- package/dist/components/menu/menu.meta.js +2 -2
- package/dist/components/page-header/page-header.js +9 -4
- package/dist/components/page-header/page-header.meta.js +26 -1
- package/dist/components/panel-group/panel-group.js +17 -1
- package/dist/components/panel-group/panel-group.meta.js +43 -3
- package/dist/components/pill/pill.meta.js +15 -6
- package/dist/components/pill/pill.variants.js +1 -0
- package/dist/components/search-input/search-input.js +30 -5
- package/dist/components/search-input/search-input.meta.js +29 -4
- package/dist/components/section-header/section-header.js +27 -17
- package/dist/components/section-header/section-header.meta.js +27 -5
- package/dist/components/sheet/sheet.js +15 -3
- package/dist/components/sheet/sheet.meta.js +2 -2
- package/dist/components/side-nav/side-nav.variants.js +8 -3
- package/dist/components/sidebar/index.js +4 -0
- package/dist/components/sidebar/sidebar.js +90 -11
- package/dist/components/sidebar/sidebar.meta.js +38 -4
- package/dist/components/table/table.js +7 -3
- package/dist/components/tabs/tabs.js +10 -2
- package/dist/components/tabs/tabs.meta.js +4 -0
- package/dist/components/workspace/workspace.js +13 -3
- package/dist/components/workspace/workspace.meta.js +109 -1
- package/dist/file-hashes.json +94 -80
- package/dist/foundations/typography.js +13 -0
- package/dist/icon/Icon.js +21 -2
- package/dist/icon/index.d.ts +36 -1
- package/dist/icon/index.js +5 -0
- package/dist/icon/names.js +240 -0
- package/dist/icon-sets/reicon.js +41 -1
- package/dist/index.d.ts +540 -75
- package/dist/index.js +18 -1
- package/dist/layout/AspectBox.js +2 -5
- package/dist/layout/Bleed.js +10 -6
- package/dist/layout/Center.js +11 -3
- package/dist/layout/Columns.js +6 -3
- package/dist/layout/Grid.js +14 -6
- package/dist/layout/GridOverlay.js +7 -7
- package/dist/layout/Region.js +18 -3
- package/dist/layout/Row.js +27 -20
- package/dist/layout/Spacer.js +13 -2
- package/dist/layout/SplitPane.js +1 -1
- package/dist/layout/Stack.js +25 -18
- package/dist/layout/index.d.ts +57 -6
- package/dist/lib/chart/activity.js +32 -0
- package/dist/lib/chart/calendar.js +67 -0
- package/dist/lib/chart/chart-tooltip.js +2 -2
- package/dist/lib/chart/heat.js +55 -0
- package/dist/lib/cn.js +1 -0
- package/dist/lib/focus-return.js +53 -0
- package/dist/lib/taxonomy.js +1 -0
- package/dist/lib/use-media-query.js +41 -0
- package/dist/{meta-DX5r5wQL.d.ts → meta-Dfm9AExe.d.ts} +6 -0
- package/dist/patterns/dashboard-page.js +23 -17
- package/dist/patterns/index.d.ts +7 -2
- package/dist/patterns/index.js +5 -1
- package/dist/patterns/tracker-page.js +392 -0
- package/dist/patterns/tracker-page.meta.js +40 -0
- package/dist/presets/index.d.ts +1 -1
- package/dist/styles.css +301 -188
- package/dist/{theme-provider-BydfM5Z0.d.ts → theme-provider-BUBealk_.d.ts} +1 -1
- package/dist/themes/_contract.js +9 -1
- package/dist/themes/atelier/index.d.ts +1 -1
- package/dist/themes/ember/index.d.ts +1 -1
- package/dist/themes/index.d.ts +2 -2
- package/dist/themes/meridian/index.d.ts +1 -1
- package/dist/themes/poppy/index.d.ts +1 -1
- package/dist/themes/prism/index.d.ts +1 -1
- package/dist/themes/sable/index.d.ts +1 -1
- package/dist/themes/verdant/index.d.ts +1 -1
- package/llms/components/activity-heatmap.md +133 -0
- package/llms/components/avatar.md +32 -9
- package/llms/components/code-block.md +1 -0
- package/llms/components/collapsible.md +12 -4
- package/llms/components/data-table.md +40 -3
- package/llms/components/date-picker.md +15 -1
- package/llms/components/dialog.md +34 -3
- package/llms/components/media-image.md +12 -0
- package/llms/components/media-tile.md +21 -0
- package/llms/components/menu.md +1 -1
- package/llms/components/page-header.md +16 -1
- package/llms/components/panel-group.md +34 -2
- package/llms/components/pill.md +24 -5
- package/llms/components/search-input.md +37 -4
- package/llms/components/section-header.md +28 -4
- package/llms/components/sheet.md +2 -2
- package/llms/components/sidebar.md +17 -4
- package/llms/components/tabs.md +1 -0
- package/llms/components/workspace.md +81 -1
- package/llms/foundations/typography.md +4 -1
- package/llms/layout/aspect-box.md +1 -1
- package/llms/layout/bleed.md +1 -1
- package/llms/layout/center.md +1 -1
- package/llms/layout/columns.md +1 -1
- package/llms/layout/grid.md +1 -1
- package/llms/layout/region.md +1 -1
- package/llms/layout/row.md +9 -1
- package/llms/layout/spacer.md +1 -1
- package/llms/layout/split-pane.md +1 -1
- package/llms/layout/stack.md +1 -1
- package/llms/patterns/dashboard-page.md +18 -15
- package/llms/patterns/tracker-page.md +450 -0
- package/llms/reference/token-intent.md +3 -1
- package/llms/reference/utilities.md +4 -1
- package/llms/tasks/auditing-an-app.md +35 -1
- package/llms/tasks/building-a-page.md +3 -2
- package/llms/tasks/building-a-tool.md +101 -5
- package/llms/tasks/building-with-sable.md +11 -3
- package/llms/tasks/creating-a-preset.md +9 -0
- package/llms/tasks/getting-started.md +10 -3
- package/llms/tasks/using-presets.md +1 -2
- package/llms-full.txt +1215 -89
- package/llms.txt +11 -9
- package/package.json +7 -1
- package/registry.json +1021 -70
- package/scripts/adoption.mjs +252 -0
- package/scripts/agent-setup.mjs +110 -18
- package/scripts/app-css.mjs +129 -0
- package/scripts/grade.mjs +178 -2
- package/scripts/lint-rules.mjs +126 -0
- package/scripts/wiring.mjs +2 -1
- package/skills/entry/SKILL.md +1 -1
- package/skills/sable/SKILL.md +64 -6
- package/skills/sable/agents/openai.yaml +1 -1
- package/targets/ios.json +9 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
// src/patterns/tracker-page.tsx
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Icon } from "../icon/index.js";
|
|
4
|
+
import { Avatar } from "../components/avatar/index.js";
|
|
5
|
+
import { Badge } from "../components/badge/index.js";
|
|
6
|
+
import { BottomNav, BottomNavItem } from "../components/bottom-nav/index.js";
|
|
7
|
+
import {
|
|
8
|
+
BreadcrumbItem,
|
|
9
|
+
BreadcrumbLink,
|
|
10
|
+
BreadcrumbPage,
|
|
11
|
+
Breadcrumbs,
|
|
12
|
+
BreadcrumbSeparator
|
|
13
|
+
} from "../components/breadcrumbs/index.js";
|
|
14
|
+
import { Button } from "../components/button/index.js";
|
|
15
|
+
import { DataTable } from "../components/data-table/index.js";
|
|
16
|
+
import { DatePicker } from "../components/date-picker/index.js";
|
|
17
|
+
import { EmptyState } from "../components/empty-state/index.js";
|
|
18
|
+
import { Form } from "../components/form/index.js";
|
|
19
|
+
import { Kbd } from "../components/kbd/index.js";
|
|
20
|
+
import {
|
|
21
|
+
Menu,
|
|
22
|
+
MenuContent,
|
|
23
|
+
MenuItem,
|
|
24
|
+
MenuRadioGroup,
|
|
25
|
+
MenuRadioItem,
|
|
26
|
+
MenuSeparator,
|
|
27
|
+
MenuSubmenu,
|
|
28
|
+
MenuSubmenuTrigger,
|
|
29
|
+
MenuTrigger
|
|
30
|
+
} from "../components/menu/index.js";
|
|
31
|
+
import { Panel, PanelGroup } from "../components/panel-group/index.js";
|
|
32
|
+
import { PanelSection, PropertyRow } from "../components/panel-section/index.js";
|
|
33
|
+
import { RelativeTime } from "../components/relative-time/index.js";
|
|
34
|
+
import { SearchInput } from "../components/search-input/index.js";
|
|
35
|
+
import { Select, SelectContent, SelectItem, SelectTrigger } from "../components/select/index.js";
|
|
36
|
+
import {
|
|
37
|
+
Sidebar,
|
|
38
|
+
SidebarAccount,
|
|
39
|
+
SidebarBrand,
|
|
40
|
+
SidebarFooter,
|
|
41
|
+
SidebarGroup,
|
|
42
|
+
SidebarHeader,
|
|
43
|
+
SidebarItem,
|
|
44
|
+
SidebarNav
|
|
45
|
+
} from "../components/sidebar/index.js";
|
|
46
|
+
import { StatusBar, StatusBarGroup } from "../components/status-bar/index.js";
|
|
47
|
+
import { StatusDot } from "../components/status-dot/index.js";
|
|
48
|
+
import { Tabs, TabsList, TabsPanel, TabsTrigger } from "../components/tabs/index.js";
|
|
49
|
+
import { Textarea } from "../components/textarea/index.js";
|
|
50
|
+
import { Toggle, ToggleGroup } from "../components/toggle/index.js";
|
|
51
|
+
import { SimpleTooltip } from "../components/tooltip/index.js";
|
|
52
|
+
import { Workspace, WorkspaceBar, WorkspaceCanvas } from "../components/workspace/index.js";
|
|
53
|
+
import { Density } from "../lib/density.js";
|
|
54
|
+
import { useBelowBreakpoint } from "../lib/use-media-query.js";
|
|
55
|
+
import { useSableTheme } from "../themes/index.js";
|
|
56
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
57
|
+
var STATUSES = [
|
|
58
|
+
{ key: "progress", label: "In progress", tone: "warning" },
|
|
59
|
+
{ key: "review", label: "In review", tone: "2" },
|
|
60
|
+
{ key: "todo", label: "Todo", tone: "neutral" },
|
|
61
|
+
{ key: "done", label: "Done", tone: "success" }
|
|
62
|
+
];
|
|
63
|
+
var PRIORITIES = ["Urgent", "High", "Medium", "Low"];
|
|
64
|
+
var people = {
|
|
65
|
+
AK: "Ama Kusi",
|
|
66
|
+
JR: "Jonas Reiter",
|
|
67
|
+
LM: "Lena Moreau",
|
|
68
|
+
TO: "Tom\xE1s Ortega"
|
|
69
|
+
};
|
|
70
|
+
var MIN = 60 * 1e3;
|
|
71
|
+
var HOUR = 60 * MIN;
|
|
72
|
+
var DAY = 24 * HOUR;
|
|
73
|
+
var isoDay = (offset) => {
|
|
74
|
+
const d = new Date(Date.now() + offset);
|
|
75
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
76
|
+
};
|
|
77
|
+
var initialIssues = [
|
|
78
|
+
{ id: "FW-104", title: "Sync offline edits when the app reconnects", detail: "Edits made offline are queued but replayed out of order after a reconnect. Replay them by timestamp and surface conflicts.", status: "progress", priority: "Urgent", assignee: "AK", label: "Sync", due: isoDay(2 * DAY), notes: [{ id: "n1", who: "JR", text: "Repro steps are in the test plan. It only happens after a cold start.", at: -3 * HOUR }] },
|
|
79
|
+
{ id: "FW-105", title: "Onboarding checklist on first launch", detail: "Three steps: connect a calendar, invite a teammate, create a first project.", status: "progress", priority: "Medium", assignee: "LM", label: "Onboarding", due: isoDay(5 * DAY), notes: [] },
|
|
80
|
+
{ id: "FW-108", title: "Push notification settings per project", detail: "Let people mute a project without muting the workspace.", status: "review", priority: "High", assignee: "TO", label: "Notifications", due: isoDay(1 * DAY), notes: [] },
|
|
81
|
+
{ id: "FW-110", title: "Crash when attaching a large photo", detail: "Photos over 20 MB crash the uploader on older devices.", status: "review", priority: "Urgent", assignee: "JR", label: "Bug", due: isoDay(-1 * DAY), notes: [] },
|
|
82
|
+
{ id: "FW-112", title: "Search across archived projects", detail: "Archived projects are left out of search today.", status: "todo", priority: "Low", assignee: "AK", label: "Search", due: isoDay(9 * DAY), notes: [] },
|
|
83
|
+
{ id: "FW-113", title: "Keyboard shortcuts for the board view", detail: "J and K move between cards; C creates an issue in the focused column.", status: "todo", priority: "Medium", assignee: "LM", label: "Board", due: isoDay(12 * DAY), notes: [] },
|
|
84
|
+
{ id: "FW-114", title: "Export a project as CSV", detail: "One row per issue, with status, assignee and dates.", status: "todo", priority: "Low", assignee: "TO", label: "Export", due: isoDay(14 * DAY), notes: [] },
|
|
85
|
+
{ id: "FW-101", title: "Dark mode for the widget", detail: "The home-screen widget ignores the system appearance.", status: "done", priority: "Medium", assignee: "JR", label: "Widget", due: isoDay(-4 * DAY), notes: [] }
|
|
86
|
+
];
|
|
87
|
+
var MODE_LABEL = { light: "Light", dark: "Dark", system: "System" };
|
|
88
|
+
function TrackerPage() {
|
|
89
|
+
const [issues, setIssues] = useState(initialIssues);
|
|
90
|
+
const [selectedId, setSelectedId] = useState("FW-104");
|
|
91
|
+
const [inspecting, setInspecting] = useState(false);
|
|
92
|
+
const [navOpen, setNavOpen] = useState(false);
|
|
93
|
+
const [query, setQuery] = useState("");
|
|
94
|
+
const [priority, setPriority] = useState("all");
|
|
95
|
+
const [groupBy, setGroupBy] = useState("status");
|
|
96
|
+
const [tab, setTab] = useState("issues");
|
|
97
|
+
const [draft, setDraft] = useState("");
|
|
98
|
+
const narrow = useBelowBreakpoint("lg");
|
|
99
|
+
const { preference, setPreference } = useSableTheme();
|
|
100
|
+
const needle = query.trim().toLowerCase();
|
|
101
|
+
const visible = issues.filter(
|
|
102
|
+
(i) => (priority === "all" || i.priority === priority) && `${i.id} ${i.title} ${i.label}`.toLowerCase().includes(needle)
|
|
103
|
+
);
|
|
104
|
+
const selected = issues.find((i) => i.id === selectedId) ?? null;
|
|
105
|
+
const done = issues.filter((i) => i.status === "done").length;
|
|
106
|
+
function update(id, patch) {
|
|
107
|
+
setIssues((all) => all.map((i) => i.id === id ? { ...i, ...patch } : i));
|
|
108
|
+
}
|
|
109
|
+
function open(id) {
|
|
110
|
+
setSelectedId(id);
|
|
111
|
+
setInspecting(true);
|
|
112
|
+
}
|
|
113
|
+
function close() {
|
|
114
|
+
setInspecting(false);
|
|
115
|
+
if (!narrow) setSelectedId(null);
|
|
116
|
+
}
|
|
117
|
+
function addIssue(status = "todo") {
|
|
118
|
+
const id = `FW-${115 + issues.length}`;
|
|
119
|
+
setIssues((all) => [
|
|
120
|
+
...all,
|
|
121
|
+
{ id, title: "New issue", detail: "", status, priority: "Medium", assignee: "AK", label: "Triage", due: isoDay(7 * DAY), notes: [] }
|
|
122
|
+
]);
|
|
123
|
+
open(id);
|
|
124
|
+
}
|
|
125
|
+
function addNote() {
|
|
126
|
+
if (!selected || !draft.trim()) return;
|
|
127
|
+
update(selected.id, { notes: [...selected.notes, { id: `n${Date.now()}`, who: "AK", text: draft.trim(), at: 0 }] });
|
|
128
|
+
setDraft("");
|
|
129
|
+
}
|
|
130
|
+
const columns = [
|
|
131
|
+
{ key: "id", header: "ID", className: "w-20 whitespace-nowrap", cell: (i) => /* @__PURE__ */ jsx("span", { className: "text-ui-num text-muted-foreground", children: i.id }) },
|
|
132
|
+
{ key: "title", header: "Issue", className: "w-full max-w-0", cell: (i) => /* @__PURE__ */ jsx("span", { className: "block truncate", children: i.title }) },
|
|
133
|
+
{ key: "label", header: "Label", className: "w-28 max-sm:hidden", cell: (i) => /* @__PURE__ */ jsx(Badge, { appearance: "outline", size: "sm", children: i.label }) },
|
|
134
|
+
{ key: "due", header: "Due", className: "w-20 whitespace-nowrap max-sm:hidden", sortValue: (i) => i.due.getTime(), cell: (i) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: i.due.toLocaleDateString("en-US", { month: "short", day: "numeric" }) }) },
|
|
135
|
+
{ key: "assignee", header: /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Assignee" }), className: "w-10", cell: (i) => /* @__PURE__ */ jsx(Avatar, { size: "2xs", fallback: i.assignee, alt: people[i.assignee] }) }
|
|
136
|
+
];
|
|
137
|
+
const groups = groupBy === "status" ? STATUSES.map((s) => ({
|
|
138
|
+
key: s.key,
|
|
139
|
+
label: s.label,
|
|
140
|
+
icon: /* @__PURE__ */ jsx(StatusDot, { tone: s.tone }),
|
|
141
|
+
action: /* @__PURE__ */ jsx(SimpleTooltip, { content: `Add to ${s.label}`, children: /* @__PURE__ */ jsx(Button, { size: "icon-xs", variant: "ghost", "aria-label": `Add to ${s.label}`, onClick: () => addIssue(s.key), children: /* @__PURE__ */ jsx(Icon, { name: "plus" }) }) }),
|
|
142
|
+
defaultExpanded: s.key !== "done"
|
|
143
|
+
})) : Object.entries(people).map(([key, name]) => ({
|
|
144
|
+
key,
|
|
145
|
+
label: name,
|
|
146
|
+
icon: /* @__PURE__ */ jsx(Avatar, { size: "2xs", fallback: key, alt: "" })
|
|
147
|
+
}));
|
|
148
|
+
const sidebar = /* @__PURE__ */ jsxs(Sidebar, { variant: "inline", className: "h-full w-full", children: [
|
|
149
|
+
/* @__PURE__ */ jsxs(SidebarHeader, { children: [
|
|
150
|
+
/* @__PURE__ */ jsx(SidebarBrand, { mark: /* @__PURE__ */ jsx(Avatar, { shape: "square", size: "2xs", tone: "teal", fallback: "F", alt: "" }), label: "Fieldwork" }),
|
|
151
|
+
/* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "basis-full justify-between", children: [
|
|
152
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
153
|
+
/* @__PURE__ */ jsx(Icon, { name: "search" }),
|
|
154
|
+
"Search"
|
|
155
|
+
] }),
|
|
156
|
+
/* @__PURE__ */ jsx(Kbd, { children: "Ctrl K" })
|
|
157
|
+
] })
|
|
158
|
+
] }),
|
|
159
|
+
/* @__PURE__ */ jsxs(SidebarNav, { "aria-label": "Workspace", children: [
|
|
160
|
+
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "inbox" }), label: "Inbox", badge: /* @__PURE__ */ jsx("span", { className: "text-ui-num text-muted-foreground", children: "3" }) }),
|
|
161
|
+
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "circle-check" }), label: "My issues" }),
|
|
162
|
+
/* @__PURE__ */ jsxs(SidebarGroup, { label: "Mobile app", children: [
|
|
163
|
+
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "layers" }), label: "Issues", active: true }),
|
|
164
|
+
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "refresh" }), label: "Cycles" }),
|
|
165
|
+
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "archive" }), label: "Backlog" })
|
|
166
|
+
] }),
|
|
167
|
+
/* @__PURE__ */ jsxs(SidebarGroup, { label: "Workspace", children: [
|
|
168
|
+
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "folder" }), label: "Projects" }),
|
|
169
|
+
/* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "users" }), label: "Members" })
|
|
170
|
+
] })
|
|
171
|
+
] }),
|
|
172
|
+
/* @__PURE__ */ jsx(SidebarFooter, { children: /* @__PURE__ */ jsxs(Menu, { children: [
|
|
173
|
+
/* @__PURE__ */ jsx(
|
|
174
|
+
MenuTrigger,
|
|
175
|
+
{
|
|
176
|
+
render: /* @__PURE__ */ jsx(SidebarAccount, { avatar: /* @__PURE__ */ jsx(Avatar, { size: "2xs", fallback: "AK", alt: "" }), label: "Ama Kusi", detail: "Fieldwork \xB7 Admin" })
|
|
177
|
+
}
|
|
178
|
+
),
|
|
179
|
+
/* @__PURE__ */ jsxs(MenuContent, { side: "top", align: "start", children: [
|
|
180
|
+
/* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "gear" }), children: "Settings" }),
|
|
181
|
+
/* @__PURE__ */ jsxs(MenuSubmenu, { children: [
|
|
182
|
+
/* @__PURE__ */ jsx(
|
|
183
|
+
MenuSubmenuTrigger,
|
|
184
|
+
{
|
|
185
|
+
icon: /* @__PURE__ */ jsx(Icon, { name: preference === "dark" ? "moon" : preference === "light" ? "sun" : "desktop" }),
|
|
186
|
+
value: MODE_LABEL[preference],
|
|
187
|
+
children: "Appearance"
|
|
188
|
+
}
|
|
189
|
+
),
|
|
190
|
+
/* @__PURE__ */ jsx(MenuContent, { side: "inline-end", children: /* @__PURE__ */ jsxs(MenuRadioGroup, { value: preference, onValueChange: (v) => setPreference(v), children: [
|
|
191
|
+
/* @__PURE__ */ jsx(MenuRadioItem, { value: "light", children: "Light" }),
|
|
192
|
+
/* @__PURE__ */ jsx(MenuRadioItem, { value: "dark", children: "Dark" }),
|
|
193
|
+
/* @__PURE__ */ jsx(MenuRadioItem, { value: "system", children: "System" })
|
|
194
|
+
] }) })
|
|
195
|
+
] }),
|
|
196
|
+
/* @__PURE__ */ jsx(MenuSeparator, {}),
|
|
197
|
+
/* @__PURE__ */ jsx(MenuItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "logout" }), children: "Sign out" })
|
|
198
|
+
] })
|
|
199
|
+
] }) })
|
|
200
|
+
] });
|
|
201
|
+
const list = visible.length === 0 ? /* @__PURE__ */ jsx(
|
|
202
|
+
EmptyState,
|
|
203
|
+
{
|
|
204
|
+
icon: /* @__PURE__ */ jsx(Icon, { name: "search" }),
|
|
205
|
+
title: "No issues match",
|
|
206
|
+
description: "Try another search, or clear the priority filter.",
|
|
207
|
+
action: /* @__PURE__ */ jsx(
|
|
208
|
+
Button,
|
|
209
|
+
{
|
|
210
|
+
variant: "secondary",
|
|
211
|
+
size: "sm",
|
|
212
|
+
onClick: () => {
|
|
213
|
+
setQuery("");
|
|
214
|
+
setPriority("all");
|
|
215
|
+
},
|
|
216
|
+
children: "Clear filters"
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
) : /* @__PURE__ */ jsx(
|
|
221
|
+
DataTable,
|
|
222
|
+
{
|
|
223
|
+
columns,
|
|
224
|
+
data: visible,
|
|
225
|
+
rowKey: (i) => i.id,
|
|
226
|
+
groupBy: (i) => groupBy === "status" ? i.status : i.assignee,
|
|
227
|
+
groups,
|
|
228
|
+
onRowClick: (i) => open(i.id),
|
|
229
|
+
isRowSelected: (i) => i.id === selectedId && (!narrow || inspecting)
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
const inspector = selected ? /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [
|
|
233
|
+
/* @__PURE__ */ jsxs(WorkspaceBar, { className: "justify-between", children: [
|
|
234
|
+
/* @__PURE__ */ jsx("span", { className: "text-ui-num text-muted-foreground", children: selected.id }),
|
|
235
|
+
/* @__PURE__ */ jsx(SimpleTooltip, { content: "Close issue", children: /* @__PURE__ */ jsx(Button, { size: "icon-xs", variant: "ghost", "aria-label": "Close issue", onClick: close, children: /* @__PURE__ */ jsx(Icon, { name: "xmark" }) }) })
|
|
236
|
+
] }),
|
|
237
|
+
/* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto scrollbar-quiet", children: [
|
|
238
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 px-2 pt-4 pb-3", children: [
|
|
239
|
+
/* @__PURE__ */ jsx("h2", { className: "text-title-lg", children: selected.title }),
|
|
240
|
+
/* @__PURE__ */ jsx("p", { className: "text-ui-body text-muted-foreground", children: selected.detail || "Add the details the team needs." })
|
|
241
|
+
] }),
|
|
242
|
+
/* @__PURE__ */ jsxs(PanelSection, { title: "Properties", children: [
|
|
243
|
+
/* @__PURE__ */ jsx(PropertyRow, { label: "Status", children: /* @__PURE__ */ jsxs(Select, { value: selected.status, items: STATUSES.map((s) => ({ value: s.key, label: s.label })), onValueChange: (v) => v && update(selected.id, { status: v }), children: [
|
|
244
|
+
/* @__PURE__ */ jsx(SelectTrigger, { size: "xs", "aria-label": "Status" }),
|
|
245
|
+
/* @__PURE__ */ jsx(SelectContent, { children: STATUSES.map((s) => /* @__PURE__ */ jsx(SelectItem, { value: s.key, children: s.label }, s.key)) })
|
|
246
|
+
] }) }),
|
|
247
|
+
/* @__PURE__ */ jsx(PropertyRow, { label: "Priority", children: /* @__PURE__ */ jsxs(Select, { value: selected.priority, onValueChange: (v) => v && update(selected.id, { priority: v }), children: [
|
|
248
|
+
/* @__PURE__ */ jsx(SelectTrigger, { size: "xs", "aria-label": "Priority" }),
|
|
249
|
+
/* @__PURE__ */ jsx(SelectContent, { children: PRIORITIES.map((p) => /* @__PURE__ */ jsx(SelectItem, { value: p, children: p }, p)) })
|
|
250
|
+
] }) }),
|
|
251
|
+
/* @__PURE__ */ jsx(PropertyRow, { label: "Assignee", children: /* @__PURE__ */ jsxs(Select, { value: selected.assignee, items: people, onValueChange: (v) => v && update(selected.id, { assignee: v }), children: [
|
|
252
|
+
/* @__PURE__ */ jsx(SelectTrigger, { size: "xs", "aria-label": "Assignee" }),
|
|
253
|
+
/* @__PURE__ */ jsx(SelectContent, { children: Object.entries(people).map(([key, name]) => /* @__PURE__ */ jsx(SelectItem, { value: key, children: name }, key)) })
|
|
254
|
+
] }) }),
|
|
255
|
+
/* @__PURE__ */ jsx(PropertyRow, { label: "Due date", children: /* @__PURE__ */ jsx(
|
|
256
|
+
DatePicker,
|
|
257
|
+
{
|
|
258
|
+
size: "xs",
|
|
259
|
+
"aria-label": "Due date",
|
|
260
|
+
value: selected.due,
|
|
261
|
+
formatOptions: { month: "short", day: "numeric", year: "numeric" },
|
|
262
|
+
onValueChange: (d) => d && update(selected.id, { due: d })
|
|
263
|
+
}
|
|
264
|
+
) })
|
|
265
|
+
] }),
|
|
266
|
+
/* @__PURE__ */ jsx(PanelSection, { title: "Activity", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 pb-4", children: [
|
|
267
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-ui-sm text-muted-foreground", children: [
|
|
268
|
+
/* @__PURE__ */ jsx(Icon, { name: "plus" }),
|
|
269
|
+
/* @__PURE__ */ jsx("span", { children: "Created in Mobile app" })
|
|
270
|
+
] }),
|
|
271
|
+
selected.notes.map((n) => /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
|
|
272
|
+
/* @__PURE__ */ jsx(Avatar, { size: "2xs", fallback: n.who, alt: "" }),
|
|
273
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
274
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
275
|
+
/* @__PURE__ */ jsx("span", { className: "text-ui-md", children: people[n.who] }),
|
|
276
|
+
/* @__PURE__ */ jsx(RelativeTime, { date: new Date(Date.now() + n.at), className: "text-ui-sm text-muted-foreground" })
|
|
277
|
+
] }),
|
|
278
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-ui-body", children: n.text })
|
|
279
|
+
] })
|
|
280
|
+
] }, n.id))
|
|
281
|
+
] }) })
|
|
282
|
+
] }),
|
|
283
|
+
/* @__PURE__ */ jsxs(
|
|
284
|
+
Form,
|
|
285
|
+
{
|
|
286
|
+
className: "flex shrink-0 flex-col gap-2 border-t border-hairline p-2",
|
|
287
|
+
onSubmit: (e) => {
|
|
288
|
+
e.preventDefault();
|
|
289
|
+
addNote();
|
|
290
|
+
},
|
|
291
|
+
children: [
|
|
292
|
+
/* @__PURE__ */ jsx(Textarea, { "aria-label": "Add a comment", placeholder: "Leave a comment\u2026", rows: 2, value: draft, onChange: (e) => setDraft(e.target.value) }),
|
|
293
|
+
/* @__PURE__ */ jsx(Button, { size: "xs", variant: "secondary", type: "submit", className: "self-end", disabled: !draft.trim(), iconStart: /* @__PURE__ */ jsx(Icon, { name: "arrow-up" }), children: "Comment" })
|
|
294
|
+
]
|
|
295
|
+
}
|
|
296
|
+
)
|
|
297
|
+
] }) : null;
|
|
298
|
+
return /* @__PURE__ */ jsx(
|
|
299
|
+
Workspace,
|
|
300
|
+
{
|
|
301
|
+
skipLinkLabel: "Skip to issues",
|
|
302
|
+
statusBar: /* @__PURE__ */ jsxs(StatusBar, { children: [
|
|
303
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
304
|
+
/* @__PURE__ */ jsx(StatusDot, { tone: "success", size: "sm" }),
|
|
305
|
+
"Saved"
|
|
306
|
+
] }),
|
|
307
|
+
/* @__PURE__ */ jsxs("span", { className: "max-sm:hidden", children: [
|
|
308
|
+
issues.length,
|
|
309
|
+
" issues \xB7 ",
|
|
310
|
+
done,
|
|
311
|
+
" done"
|
|
312
|
+
] }),
|
|
313
|
+
/* @__PURE__ */ jsx(StatusBarGroup, { align: "end", children: /* @__PURE__ */ jsx("span", { className: "max-sm:hidden", children: "Ctrl K to search" }) })
|
|
314
|
+
] }),
|
|
315
|
+
bottomNav: /* @__PURE__ */ jsxs(BottomNav, { "aria-label": "Primary", children: [
|
|
316
|
+
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "inbox" }), label: "Inbox", showLabel: true }),
|
|
317
|
+
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "layers" }), label: "Issues", showLabel: true, active: true }),
|
|
318
|
+
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "folder" }), label: "Projects", showLabel: true }),
|
|
319
|
+
/* @__PURE__ */ jsx(BottomNavItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "users" }), label: "Members", showLabel: true })
|
|
320
|
+
] }),
|
|
321
|
+
children: /* @__PURE__ */ jsxs(PanelGroup, { children: [
|
|
322
|
+
/* @__PURE__ */ jsx(Panel, { label: "Workspace navigation", width: 232, narrow: "sheet", side: "left", open: narrow ? navOpen : true, onOpenChange: setNavOpen, scroll: false, children: sidebar }),
|
|
323
|
+
/* @__PURE__ */ jsxs(WorkspaceCanvas, { className: "flex flex-col", children: [
|
|
324
|
+
/* @__PURE__ */ jsxs(WorkspaceBar, { className: "gap-2 px-3", children: [
|
|
325
|
+
/* @__PURE__ */ jsx("span", { className: "lg:hidden", children: /* @__PURE__ */ jsx(SimpleTooltip, { content: "Open navigation", children: /* @__PURE__ */ jsx(Button, { size: "icon-xs", variant: "ghost", "aria-label": "Open navigation", onClick: () => setNavOpen(true), children: /* @__PURE__ */ jsx(Icon, { name: "bars" }) }) }) }),
|
|
326
|
+
/* @__PURE__ */ jsx("h1", { className: "sr-only", children: "Mobile app issues" }),
|
|
327
|
+
/* @__PURE__ */ jsxs(Breadcrumbs, { className: "min-w-0", children: [
|
|
328
|
+
/* @__PURE__ */ jsx(BreadcrumbItem, { className: "max-sm:hidden", children: /* @__PURE__ */ jsx(BreadcrumbLink, { href: "#", children: "Fieldwork" }) }),
|
|
329
|
+
/* @__PURE__ */ jsx(BreadcrumbSeparator, { className: "max-sm:hidden" }),
|
|
330
|
+
/* @__PURE__ */ jsx(BreadcrumbItem, { children: /* @__PURE__ */ jsx(BreadcrumbPage, { children: "Mobile app" }) })
|
|
331
|
+
] })
|
|
332
|
+
] }),
|
|
333
|
+
/* @__PURE__ */ jsxs(Tabs, { value: tab, onValueChange: (v) => setTab(String(v)), className: "flex min-h-0 flex-1 flex-col", children: [
|
|
334
|
+
/* @__PURE__ */ jsx(WorkspaceBar, { className: "px-3", children: /* @__PURE__ */ jsxs(TabsList, { variant: "pill", children: [
|
|
335
|
+
/* @__PURE__ */ jsxs(TabsTrigger, { value: "issues", children: [
|
|
336
|
+
"Issues ",
|
|
337
|
+
/* @__PURE__ */ jsx("span", { className: "text-ui-num text-muted-foreground", children: issues.length })
|
|
338
|
+
] }),
|
|
339
|
+
/* @__PURE__ */ jsx(TabsTrigger, { value: "overview", children: "Overview" })
|
|
340
|
+
] }) }),
|
|
341
|
+
/* @__PURE__ */ jsxs(TabsPanel, { value: "issues", className: "flex min-h-0 flex-1 flex-col", children: [
|
|
342
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-hairline px-3 py-2", children: [
|
|
343
|
+
/* @__PURE__ */ jsxs(Menu, { children: [
|
|
344
|
+
/* @__PURE__ */ jsx(MenuTrigger, { render: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "xs", iconStart: /* @__PURE__ */ jsx(Icon, { name: "filter" }), children: priority === "all" ? "Filter" : priority }) }),
|
|
345
|
+
/* @__PURE__ */ jsx(MenuContent, { children: /* @__PURE__ */ jsxs(MenuRadioGroup, { value: priority, onValueChange: (v) => setPriority(v), children: [
|
|
346
|
+
/* @__PURE__ */ jsx(MenuRadioItem, { value: "all", children: "All priorities" }),
|
|
347
|
+
PRIORITIES.map((p) => /* @__PURE__ */ jsx(MenuRadioItem, { value: p, children: p }, p))
|
|
348
|
+
] }) })
|
|
349
|
+
] }),
|
|
350
|
+
/* @__PURE__ */ jsx(SearchInput, { size: "xs", value: query, onValueChange: setQuery, placeholder: "Find issue\u2026", "aria-label": "Find issue", className: "w-40" }),
|
|
351
|
+
/* @__PURE__ */ jsxs(ToggleGroup, { appearance: "segmented", value: [groupBy], onValueChange: (v) => v[0] && setGroupBy(v[0]), "aria-label": "Group by", className: "ms-auto", children: [
|
|
352
|
+
/* @__PURE__ */ jsx(Toggle, { value: "status", children: "Status" }),
|
|
353
|
+
/* @__PURE__ */ jsx(Toggle, { value: "assignee", children: "Assignee" })
|
|
354
|
+
] }),
|
|
355
|
+
/* @__PURE__ */ jsx(Button, { size: "xs", iconStart: /* @__PURE__ */ jsx(Icon, { name: "plus" }), onClick: () => addIssue(), children: "New issue" })
|
|
356
|
+
] }),
|
|
357
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto scrollbar-quiet", children: list })
|
|
358
|
+
] }),
|
|
359
|
+
/* @__PURE__ */ jsx(TabsPanel, { value: "overview", className: "flex min-h-0 flex-1 flex-col", children: /* @__PURE__ */ jsx(Density, { value: "comfortable", className: "min-h-0 flex-1 overflow-y-auto scrollbar-quiet", children: /* @__PURE__ */ jsxs("article", { className: "flex max-w-prose flex-col gap-4 p-8 max-sm:p-5", children: [
|
|
360
|
+
/* @__PURE__ */ jsx("h2", { className: "text-heading-3", children: "Mobile app" }),
|
|
361
|
+
/* @__PURE__ */ jsx("p", { className: "text-body-md text-muted-foreground", children: "The phone app for field teams: capture work on site, sync it when the signal comes back, and hand it to the office without retyping anything." }),
|
|
362
|
+
/* @__PURE__ */ jsxs("dl", { className: "sable-list-dl", children: [
|
|
363
|
+
/* @__PURE__ */ jsx("dt", { children: "Lead" }),
|
|
364
|
+
/* @__PURE__ */ jsx("dd", { children: people.AK }),
|
|
365
|
+
/* @__PURE__ */ jsx("dt", { children: "Open issues" }),
|
|
366
|
+
/* @__PURE__ */ jsx("dd", { children: issues.length - done }),
|
|
367
|
+
/* @__PURE__ */ jsx("dt", { children: "Target" }),
|
|
368
|
+
/* @__PURE__ */ jsx("dd", { children: isoDay(30 * DAY).toLocaleDateString("en-US", { month: "long", day: "numeric" }) })
|
|
369
|
+
] })
|
|
370
|
+
] }) }) })
|
|
371
|
+
] })
|
|
372
|
+
] }),
|
|
373
|
+
/* @__PURE__ */ jsx(
|
|
374
|
+
Panel,
|
|
375
|
+
{
|
|
376
|
+
label: "Issue details",
|
|
377
|
+
width: 320,
|
|
378
|
+
narrow: "sheet",
|
|
379
|
+
open: narrow ? inspecting && selected !== null : selected !== null,
|
|
380
|
+
onOpenChange: (next) => !next && close(),
|
|
381
|
+
scroll: false,
|
|
382
|
+
className: "border-s border-hairline",
|
|
383
|
+
children: inspector
|
|
384
|
+
}
|
|
385
|
+
)
|
|
386
|
+
] })
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
export {
|
|
391
|
+
TrackerPage
|
|
392
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/patterns/tracker-page.meta.ts
|
|
2
|
+
var trackerPageMeta = {
|
|
3
|
+
name: "TrackerPage",
|
|
4
|
+
summary: "Records workspace in the tool register: a Sidebar rail of grouped views ending in an account menu, a WorkspaceBar of Breadcrumbs over pill Tabs, a filter row, one grouped DataTable, and a docked inspector of PanelSection property rows with a pinned comment form. Below lg the rail and the inspector become sheets and a BottomNav takes the last row.",
|
|
5
|
+
intent: "Reach for this when a team works through records in a dense tool \u2014 an issue tracker, a support queue, a pipeline: a grouped list, a docked inspector for the selected record, and a rail of views. For records a person reads and edits one at a time on a page, ListDetailPage is the page-register shape; for monitoring with headline numbers, DashboardPage.",
|
|
6
|
+
keywords: [
|
|
7
|
+
"issue tracker",
|
|
8
|
+
"tracker",
|
|
9
|
+
"linear",
|
|
10
|
+
"like linear",
|
|
11
|
+
"jira",
|
|
12
|
+
"project management",
|
|
13
|
+
"task manager",
|
|
14
|
+
"support queue",
|
|
15
|
+
"triage",
|
|
16
|
+
"crm",
|
|
17
|
+
"pipeline",
|
|
18
|
+
"records workspace",
|
|
19
|
+
"inspector",
|
|
20
|
+
"backlog"
|
|
21
|
+
],
|
|
22
|
+
notes: [
|
|
23
|
+
'Workspace sets the compact density for everything inside: 32px rail rows, 28px tables, 24px property fields. Nothing in the pattern sizes a control or a row by hand. The Overview tab is a reading view, so it sits in `<Density value="comfortable">` and its prose gets page type back.',
|
|
24
|
+
"The rail is Sable's own parts on one grid: SidebarBrand, SidebarGroup, SidebarItem and SidebarAccount share the icon and label columns. The account row is the trigger of the account Menu, which holds Settings, an Appearance submenu wired to useSableTheme (light, dark, system) and Sign out. No Settings button or badge sits in the footer.",
|
|
25
|
+
"There is no Workspace `toolbar`: the bars belong to the list column, so the inspector runs the full height beside them. The first WorkspaceBar carries Breadcrumbs; the second carries pill Tabs, a view switch in chrome, which sits beside the bar's other controls rather than drawing a rule.",
|
|
26
|
+
"One DataTable groups the rows (`groupBy`, `groups`): one header for every group, a StatusDot, a count and an add action per group header, and Done starting closed. The segmented ToggleGroup switches the grouping between status and assignee. Filtering to nothing swaps the table for an EmptyState with a Clear filters action.",
|
|
27
|
+
"Workflow states map onto StatusDot tones once (`STATUSES`), so group headers and the status Select read from one place. A product with a richer status glyph keeps that one mapping and swaps the icon.",
|
|
28
|
+
'The inspector is a Panel with `narrow="sheet"`: docked at 320px from lg, a right sheet below it, its content rendered once. Below lg it opens only when a row is picked (`inspecting`), so a phone never loads with a sheet over the list. Properties are PanelSection + PropertyRow with xs Select triggers and an xs DatePicker; Activity uses RelativeTime; the comment Form is pinned under the scrolling body.',
|
|
29
|
+
'The rail is a Panel with `narrow="sheet" side="left"`, opened from a `lg:hidden` menu button in the first bar; in the sheet the Sidebar fills its width. Workspace `bottomNav` reserves the BottomNav\'s row below lg, so the status bar ends above it.'
|
|
30
|
+
],
|
|
31
|
+
a11y: [
|
|
32
|
+
"Workspace renders a skip link to the canvas (`Skip to issues`) and WorkspaceCanvas is the main landmark; the rail and the inspector are labelled complementary regions (`Workspace navigation`, `Issue details`), and as sheets they keep those labels as their dialog titles.",
|
|
33
|
+
"The page has one level-1 heading (visually hidden; the breadcrumb shows the place) and the inspector's title is a level-2 heading.",
|
|
34
|
+
"Group header rows are real buttons with aria-expanded; rows open the inspector from the keyboard (DataTable's row activation). Every icon-only action has an accessible name and a tooltip.",
|
|
35
|
+
"Each SidebarGroup is a named group; the account row names the person, and its menu is a standard Menu with a radio group for the colour mode."
|
|
36
|
+
]
|
|
37
|
+
};
|
|
38
|
+
export {
|
|
39
|
+
trackerPageMeta
|
|
40
|
+
};
|
package/dist/presets/index.d.ts
CHANGED