@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.
Files changed (154) hide show
  1. package/AGENTS.md +10 -3
  2. package/CHANGELOG.md +235 -0
  3. package/README.md +11 -4
  4. package/bin/sable.mjs +266 -30
  5. package/dist/{_contract-CDa9-NdB.d.ts → _contract-DlklV0Im.d.ts} +2 -2
  6. package/dist/activity-DT8VK9fx.d.ts +25 -0
  7. package/dist/adapters/github.d.ts +41 -0
  8. package/dist/adapters/github.js +24 -0
  9. package/dist/blocks/index.d.ts +1 -1
  10. package/dist/components/activity-heatmap/activity-heatmap.js +420 -0
  11. package/dist/components/activity-heatmap/activity-heatmap.meta.js +142 -0
  12. package/dist/components/activity-heatmap/index.js +9 -0
  13. package/dist/components/avatar/avatar.js +16 -7
  14. package/dist/components/avatar/avatar.meta.js +43 -10
  15. package/dist/components/avatar/avatar.variants.js +26 -2
  16. package/dist/components/code-block/code-block.js +5 -3
  17. package/dist/components/code-block/code-block.meta.js +6 -0
  18. package/dist/components/collapsible/collapsible.js +6 -5
  19. package/dist/components/collapsible/collapsible.meta.js +14 -5
  20. package/dist/components/data-table/data-table.js +132 -45
  21. package/dist/components/data-table/data-table.meta.js +63 -4
  22. package/dist/components/date-picker/date-picker.js +13 -6
  23. package/dist/components/date-picker/date-picker.meta.js +22 -0
  24. package/dist/components/dialog/dialog.js +25 -5
  25. package/dist/components/dialog/dialog.meta.js +26 -5
  26. package/dist/components/media-image/media-image.js +3 -1
  27. package/dist/components/media-image/media-image.meta.js +15 -1
  28. package/dist/components/media-tile/media-tile.js +13 -1
  29. package/dist/components/media-tile/media-tile.meta.js +10 -0
  30. package/dist/components/menu/menu.js +2 -0
  31. package/dist/components/menu/menu.meta.js +2 -2
  32. package/dist/components/page-header/page-header.js +9 -4
  33. package/dist/components/page-header/page-header.meta.js +26 -1
  34. package/dist/components/panel-group/panel-group.js +17 -1
  35. package/dist/components/panel-group/panel-group.meta.js +43 -3
  36. package/dist/components/pill/pill.meta.js +15 -6
  37. package/dist/components/pill/pill.variants.js +1 -0
  38. package/dist/components/search-input/search-input.js +30 -5
  39. package/dist/components/search-input/search-input.meta.js +29 -4
  40. package/dist/components/section-header/section-header.js +27 -17
  41. package/dist/components/section-header/section-header.meta.js +27 -5
  42. package/dist/components/sheet/sheet.js +15 -3
  43. package/dist/components/sheet/sheet.meta.js +2 -2
  44. package/dist/components/side-nav/side-nav.variants.js +8 -3
  45. package/dist/components/sidebar/index.js +4 -0
  46. package/dist/components/sidebar/sidebar.js +90 -11
  47. package/dist/components/sidebar/sidebar.meta.js +38 -4
  48. package/dist/components/table/table.js +7 -3
  49. package/dist/components/tabs/tabs.js +10 -2
  50. package/dist/components/tabs/tabs.meta.js +4 -0
  51. package/dist/components/workspace/workspace.js +13 -3
  52. package/dist/components/workspace/workspace.meta.js +109 -1
  53. package/dist/file-hashes.json +94 -80
  54. package/dist/foundations/typography.js +13 -0
  55. package/dist/icon/Icon.js +21 -2
  56. package/dist/icon/index.d.ts +36 -1
  57. package/dist/icon/index.js +5 -0
  58. package/dist/icon/names.js +240 -0
  59. package/dist/icon-sets/reicon.js +41 -1
  60. package/dist/index.d.ts +540 -75
  61. package/dist/index.js +18 -1
  62. package/dist/layout/AspectBox.js +2 -5
  63. package/dist/layout/Bleed.js +10 -6
  64. package/dist/layout/Center.js +11 -3
  65. package/dist/layout/Columns.js +6 -3
  66. package/dist/layout/Grid.js +14 -6
  67. package/dist/layout/GridOverlay.js +7 -7
  68. package/dist/layout/Region.js +18 -3
  69. package/dist/layout/Row.js +27 -20
  70. package/dist/layout/Spacer.js +13 -2
  71. package/dist/layout/SplitPane.js +1 -1
  72. package/dist/layout/Stack.js +25 -18
  73. package/dist/layout/index.d.ts +57 -6
  74. package/dist/lib/chart/activity.js +32 -0
  75. package/dist/lib/chart/calendar.js +67 -0
  76. package/dist/lib/chart/chart-tooltip.js +2 -2
  77. package/dist/lib/chart/heat.js +55 -0
  78. package/dist/lib/cn.js +1 -0
  79. package/dist/lib/focus-return.js +53 -0
  80. package/dist/lib/taxonomy.js +1 -0
  81. package/dist/lib/use-media-query.js +41 -0
  82. package/dist/{meta-DX5r5wQL.d.ts → meta-Dfm9AExe.d.ts} +6 -0
  83. package/dist/patterns/dashboard-page.js +23 -17
  84. package/dist/patterns/index.d.ts +7 -2
  85. package/dist/patterns/index.js +5 -1
  86. package/dist/patterns/tracker-page.js +392 -0
  87. package/dist/patterns/tracker-page.meta.js +40 -0
  88. package/dist/presets/index.d.ts +1 -1
  89. package/dist/styles.css +301 -188
  90. package/dist/{theme-provider-BydfM5Z0.d.ts → theme-provider-BUBealk_.d.ts} +1 -1
  91. package/dist/themes/_contract.js +9 -1
  92. package/dist/themes/atelier/index.d.ts +1 -1
  93. package/dist/themes/ember/index.d.ts +1 -1
  94. package/dist/themes/index.d.ts +2 -2
  95. package/dist/themes/meridian/index.d.ts +1 -1
  96. package/dist/themes/poppy/index.d.ts +1 -1
  97. package/dist/themes/prism/index.d.ts +1 -1
  98. package/dist/themes/sable/index.d.ts +1 -1
  99. package/dist/themes/verdant/index.d.ts +1 -1
  100. package/llms/components/activity-heatmap.md +133 -0
  101. package/llms/components/avatar.md +32 -9
  102. package/llms/components/code-block.md +1 -0
  103. package/llms/components/collapsible.md +12 -4
  104. package/llms/components/data-table.md +40 -3
  105. package/llms/components/date-picker.md +15 -1
  106. package/llms/components/dialog.md +34 -3
  107. package/llms/components/media-image.md +12 -0
  108. package/llms/components/media-tile.md +21 -0
  109. package/llms/components/menu.md +1 -1
  110. package/llms/components/page-header.md +16 -1
  111. package/llms/components/panel-group.md +34 -2
  112. package/llms/components/pill.md +24 -5
  113. package/llms/components/search-input.md +37 -4
  114. package/llms/components/section-header.md +28 -4
  115. package/llms/components/sheet.md +2 -2
  116. package/llms/components/sidebar.md +17 -4
  117. package/llms/components/tabs.md +1 -0
  118. package/llms/components/workspace.md +81 -1
  119. package/llms/foundations/typography.md +4 -1
  120. package/llms/layout/aspect-box.md +1 -1
  121. package/llms/layout/bleed.md +1 -1
  122. package/llms/layout/center.md +1 -1
  123. package/llms/layout/columns.md +1 -1
  124. package/llms/layout/grid.md +1 -1
  125. package/llms/layout/region.md +1 -1
  126. package/llms/layout/row.md +9 -1
  127. package/llms/layout/spacer.md +1 -1
  128. package/llms/layout/split-pane.md +1 -1
  129. package/llms/layout/stack.md +1 -1
  130. package/llms/patterns/dashboard-page.md +18 -15
  131. package/llms/patterns/tracker-page.md +450 -0
  132. package/llms/reference/token-intent.md +3 -1
  133. package/llms/reference/utilities.md +4 -1
  134. package/llms/tasks/auditing-an-app.md +35 -1
  135. package/llms/tasks/building-a-page.md +3 -2
  136. package/llms/tasks/building-a-tool.md +101 -5
  137. package/llms/tasks/building-with-sable.md +11 -3
  138. package/llms/tasks/creating-a-preset.md +9 -0
  139. package/llms/tasks/getting-started.md +10 -3
  140. package/llms/tasks/using-presets.md +1 -2
  141. package/llms-full.txt +1215 -89
  142. package/llms.txt +11 -9
  143. package/package.json +7 -1
  144. package/registry.json +1021 -70
  145. package/scripts/adoption.mjs +252 -0
  146. package/scripts/agent-setup.mjs +110 -18
  147. package/scripts/app-css.mjs +129 -0
  148. package/scripts/grade.mjs +178 -2
  149. package/scripts/lint-rules.mjs +126 -0
  150. package/scripts/wiring.mjs +2 -1
  151. package/skills/entry/SKILL.md +1 -1
  152. package/skills/sable/SKILL.md +64 -6
  153. package/skills/sable/agents/openai.yaml +1 -1
  154. package/targets/ios.json +9 -0
@@ -0,0 +1,55 @@
1
+ // src/lib/chart/heat.ts
2
+ function quantile(sorted, p) {
3
+ if (!sorted.length) return 0;
4
+ const i = (sorted.length - 1) * p;
5
+ const lo = Math.floor(i);
6
+ const hi = Math.ceil(i);
7
+ return sorted[lo] + (sorted[hi] - sorted[lo]) * (i - lo);
8
+ }
9
+ function activityLevels(values, { levels = 5, scale = "quantile" } = {}) {
10
+ const steps = Math.max(2, Math.round(levels));
11
+ const top = steps - 1;
12
+ const positive = values.filter((v) => v > 0).sort((a, b) => a - b);
13
+ const stats = { min: positive[0] ?? 0, max: positive.at(-1) ?? 0, levels: steps };
14
+ const clamp = (l) => Math.min(top, Math.max(0, Math.round(l)));
15
+ if (typeof scale === "function") {
16
+ return { thresholds: [], level: (v) => v > 0 ? Math.max(1, clamp(scale(v, stats))) : 0 };
17
+ }
18
+ let thresholds;
19
+ if (Array.isArray(scale)) {
20
+ thresholds = scale.slice(0, top);
21
+ } else if (scale === "linear") {
22
+ thresholds = Array.from({ length: top }, (_, k) => k === 0 ? Number.MIN_VALUE : stats.max * k / top);
23
+ } else {
24
+ thresholds = Array.from({ length: top }, (_, k) => quantile(positive, k / top));
25
+ }
26
+ return {
27
+ thresholds,
28
+ level: (v) => {
29
+ if (!(v > 0)) return 0;
30
+ let l = 1;
31
+ for (let k = 1; k < thresholds.length; k++) if (v >= thresholds[k]) l = k + 1;
32
+ return l;
33
+ }
34
+ };
35
+ }
36
+ var HEAT_EMPTY = "color-mix(in oklab, var(--foreground) 8%, transparent)";
37
+ function heatColor(base, level, levels) {
38
+ if (level <= 0) return HEAT_EMPTY;
39
+ const top = Math.max(1, levels - 1);
40
+ if (level >= top) return base;
41
+ const share = Math.round(35 + 65 * (level - 1) / (top - 1));
42
+ return `color-mix(in oklch, ${base} ${share}%, ${HEAT_EMPTY})`;
43
+ }
44
+ function levelNames(levels) {
45
+ if (levels === 5) return ["None", "Low", "Medium-low", "Medium-high", "High"];
46
+ if (levels === 2) return ["None", "Some"];
47
+ if (levels === 3) return ["None", "Low", "High"];
48
+ return Array.from({ length: levels }, (_, k) => k === 0 ? "None" : `Level ${k} of ${levels - 1}`);
49
+ }
50
+ export {
51
+ HEAT_EMPTY,
52
+ activityLevels,
53
+ heatColor,
54
+ levelNames
55
+ };
package/dist/lib/cn.js CHANGED
@@ -31,6 +31,7 @@ var dsTypographyTokens = [
31
31
  // not optional bookkeeping: an unrecognised `text-*` class lands in
32
32
  // tailwind-merge's COLOUR group, so `cn("text-ui-md", "text-primary-foreground")`
33
33
  // would drop the colour and render, for instance, white text on a white button.
34
+ "ui-title",
34
35
  "ui-md",
35
36
  "ui-sm",
36
37
  "ui-body",
@@ -0,0 +1,53 @@
1
+ // src/lib/focus-return.tsx
2
+ import { createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
3
+ var FocusReturnContext = createContext(null);
4
+ function focusedElement() {
5
+ if (typeof document === "undefined") return null;
6
+ let element = document.activeElement;
7
+ while (element?.shadowRoot?.activeElement) element = element.shadowRoot.activeElement;
8
+ return element instanceof HTMLElement && element !== document.body ? element : null;
9
+ }
10
+ function useFocusReturn(open, onOpenChange) {
11
+ const [opener, setOpener] = useState(null);
12
+ const popupRef = useRef(null);
13
+ const [wasOpen, setWasOpen] = useState(false);
14
+ if (open !== void 0 && open !== wasOpen) {
15
+ setWasOpen(open);
16
+ if (open) setOpener(focusedElement());
17
+ }
18
+ const handleOpenChange = useCallback(
19
+ (next, ...rest) => {
20
+ if (next && open === void 0) setOpener(focusedElement());
21
+ onOpenChange?.(next, ...rest);
22
+ },
23
+ [open, onOpenChange]
24
+ );
25
+ const value = useMemo(
26
+ () => ({
27
+ popupRef(node) {
28
+ popupRef.current = node;
29
+ },
30
+ // Base UI reads finalFocus when the popup closes, from the latest props,
31
+ // so a new function per opener is picked up.
32
+ finalFocus() {
33
+ const target = opener;
34
+ if (!target?.isConnected) return true;
35
+ const active = focusedElement();
36
+ const popup = popupRef.current;
37
+ if (active && active !== target && popup && !popup.contains(active)) return false;
38
+ return target;
39
+ }
40
+ }),
41
+ [opener]
42
+ );
43
+ return { onOpenChange: handleOpenChange, focusReturn: value };
44
+ }
45
+ var FocusReturnProvider = FocusReturnContext.Provider;
46
+ function useFocusReturnDefault() {
47
+ return useContext(FocusReturnContext);
48
+ }
49
+ export {
50
+ FocusReturnProvider,
51
+ useFocusReturn,
52
+ useFocusReturnDefault
53
+ };
@@ -131,6 +131,7 @@ var COMPONENT_TAXONOMY = {
131
131
  RadarChart: ["Charts", "page"],
132
132
  RadialChart: ["Charts", "page"],
133
133
  Sparkline: ["Charts", "either"],
134
+ ActivityHeatmap: ["Charts", "either"],
134
135
  /* ── Data display ─────────────────────────────────────────────────────────
135
136
  Showing records and readings. `Meter` (bar or ring) is here: it shows a
136
137
  value within a known range (a reading), which is not loading or progress. */
@@ -0,0 +1,41 @@
1
+ "use client";
2
+
3
+ // src/lib/use-media-query.ts
4
+ import { useCallback, useSyncExternalStore } from "react";
5
+ var VIEWPORT_BREAKPOINTS = {
6
+ sm: "40rem",
7
+ md: "48rem",
8
+ lg: "64rem",
9
+ xl: "80rem",
10
+ "2xl": "96rem"
11
+ };
12
+ function belowBreakpointQuery(breakpoint) {
13
+ return `(width < ${VIEWPORT_BREAKPOINTS[breakpoint]})`;
14
+ }
15
+ var hasMatchMedia = () => typeof window !== "undefined" && typeof window.matchMedia === "function";
16
+ function useMediaQuery(query, { serverValue = false } = {}) {
17
+ const subscribe = useCallback(
18
+ (onChange) => {
19
+ if (!hasMatchMedia()) return () => {
20
+ };
21
+ const mql = window.matchMedia(query);
22
+ mql.addEventListener("change", onChange);
23
+ return () => mql.removeEventListener("change", onChange);
24
+ },
25
+ [query]
26
+ );
27
+ return useSyncExternalStore(
28
+ subscribe,
29
+ () => hasMatchMedia() ? window.matchMedia(query).matches : serverValue,
30
+ () => serverValue
31
+ );
32
+ }
33
+ function useBelowBreakpoint(breakpoint = "lg", options) {
34
+ return useMediaQuery(belowBreakpointQuery(breakpoint), options);
35
+ }
36
+ export {
37
+ VIEWPORT_BREAKPOINTS,
38
+ belowBreakpointQuery,
39
+ useBelowBreakpoint,
40
+ useMediaQuery
41
+ };
@@ -143,6 +143,12 @@ interface PatternMeta {
143
143
  summary: string;
144
144
  /** When to reach for this shell — and when not to. */
145
145
  intent: string;
146
+ /**
147
+ * What a brief calls this kind of product ("issue tracker", "like Linear"),
148
+ * for `sable search`. A whole-app query should land on the shell, not on the
149
+ * one component whose keywords happen to share a word with it.
150
+ */
151
+ keywords?: string[];
146
152
  /** Canonical decisions the pattern encodes (region cap, rhythm, hierarchy). */
147
153
  notes?: string[];
148
154
  /** Accessibility notes. */
@@ -17,15 +17,15 @@ import {
17
17
  CardTitle
18
18
  } from "../components/card/index.js";
19
19
  import { DataTable } from "../components/data-table/index.js";
20
- import { OverflowMenu } from "../components/overflow-menu/index.js";
21
20
  import { Entity } from "../components/entity/index.js";
22
- import { MenuItem, MenuSeparator } from "../components/menu/index.js";
21
+ import { Menu, MenuContent, MenuItem, MenuSeparator, MenuTrigger } from "../components/menu/index.js";
23
22
  import { Meter } from "../components/meter/index.js";
24
23
  import { NotificationBadge } from "../components/notification-badge/index.js";
25
24
  import { PageHeader } from "../components/page-header/index.js";
26
25
  import { RelativeTime } from "../components/relative-time/index.js";
27
26
  import {
28
27
  Sidebar,
28
+ SidebarAccount,
29
29
  SidebarFooter,
30
30
  SidebarBrand,
31
31
  SidebarHeader,
@@ -126,21 +126,27 @@ function DashboardPage() {
126
126
  ),
127
127
  /* @__PURE__ */ jsx(SidebarItem, { icon: /* @__PURE__ */ jsx(Icon, { name: "gear" }), label: "Settings", render: /* @__PURE__ */ jsx("a", { href: "/settings" }) })
128
128
  ] }),
129
- /* @__PURE__ */ jsx(SidebarFooter, { children: /* @__PURE__ */ jsx(
130
- Entity,
131
- {
132
- className: "[--entity-py:0]",
133
- leading: /* @__PURE__ */ jsx(Avatar, { variant: "art", size: "sm", fallback: "AL", alt: "" }),
134
- title: "Ada Lovelace",
135
- subtitle: "@ada",
136
- action: /* @__PURE__ */ jsxs(OverflowMenu, { label: "Account", size: "icon-sm", children: [
137
- /* @__PURE__ */ jsx(MenuItem, { render: /* @__PURE__ */ jsx("a", { href: "/@ada" }), children: "View profile" }),
138
- /* @__PURE__ */ jsx(MenuItem, { render: /* @__PURE__ */ jsx("a", { href: "/settings" }), children: "Settings" }),
139
- /* @__PURE__ */ jsx(MenuSeparator, {}),
140
- /* @__PURE__ */ jsx(MenuItem, { children: "Sign out" })
141
- ] })
142
- }
143
- ) })
129
+ /* @__PURE__ */ jsx(SidebarFooter, { children: /* @__PURE__ */ jsxs(Menu, { children: [
130
+ /* @__PURE__ */ jsx(
131
+ MenuTrigger,
132
+ {
133
+ render: /* @__PURE__ */ jsx(
134
+ SidebarAccount,
135
+ {
136
+ avatar: /* @__PURE__ */ jsx(Avatar, { variant: "art", size: "xs", fallback: "AL", alt: "" }),
137
+ label: "Ada Lovelace",
138
+ detail: "@ada"
139
+ }
140
+ )
141
+ }
142
+ ),
143
+ /* @__PURE__ */ jsxs(MenuContent, { side: "top", align: "start", children: [
144
+ /* @__PURE__ */ jsx(MenuItem, { render: /* @__PURE__ */ jsx("a", { href: "/@ada" }), children: "View profile" }),
145
+ /* @__PURE__ */ jsx(MenuItem, { render: /* @__PURE__ */ jsx("a", { href: "/settings" }), children: "Settings" }),
146
+ /* @__PURE__ */ jsx(MenuSeparator, {}),
147
+ /* @__PURE__ */ jsx(MenuItem, { children: "Sign out" })
148
+ ] })
149
+ ] }) })
144
150
  ] }),
145
151
  children: /* @__PURE__ */ jsxs(Page, { max: "wide", gap: 4, className: "py-6", children: [
146
152
  /* @__PURE__ */ jsx(
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { P as PatternMeta } from '../meta-DX5r5wQL.js';
2
+ import { P as PatternMeta } from '../meta-Dfm9AExe.js';
3
3
 
4
4
  declare function AuthPage(): react.JSX.Element;
5
5
 
@@ -33,4 +33,9 @@ declare function SettingsPage(): react.JSX.Element;
33
33
 
34
34
  declare const settingsPageMeta: PatternMeta;
35
35
 
36
- export { AuthPage, BrandLandingPage, ChatPanel, DashboardPage, FeedPage, LandingPage, ListDetailPage, SettingsPage, authPageMeta, brandLandingPageMeta, chatPanelMeta, dashboardPageMeta, feedPageMeta, landingPageMeta, listDetailPageMeta, settingsPageMeta };
36
+ /** One icon-only action with a visible name on hover and focus. */
37
+ declare function TrackerPage(): react.JSX.Element;
38
+
39
+ declare const trackerPageMeta: PatternMeta;
40
+
41
+ export { AuthPage, BrandLandingPage, ChatPanel, DashboardPage, FeedPage, LandingPage, ListDetailPage, SettingsPage, TrackerPage, authPageMeta, brandLandingPageMeta, chatPanelMeta, dashboardPageMeta, feedPageMeta, landingPageMeta, listDetailPageMeta, settingsPageMeta, trackerPageMeta };
@@ -15,6 +15,8 @@ import { ListDetailPage } from "./list-detail-page.js";
15
15
  import { listDetailPageMeta } from "./list-detail-page.meta.js";
16
16
  import { SettingsPage } from "./settings-page.js";
17
17
  import { settingsPageMeta } from "./settings-page.meta.js";
18
+ import { TrackerPage } from "./tracker-page.js";
19
+ import { trackerPageMeta } from "./tracker-page.meta.js";
18
20
  export {
19
21
  AuthPage,
20
22
  BrandLandingPage,
@@ -24,6 +26,7 @@ export {
24
26
  LandingPage,
25
27
  ListDetailPage,
26
28
  SettingsPage,
29
+ TrackerPage,
27
30
  authPageMeta,
28
31
  brandLandingPageMeta,
29
32
  chatPanelMeta,
@@ -31,5 +34,6 @@ export {
31
34
  feedPageMeta,
32
35
  landingPageMeta,
33
36
  listDetailPageMeta,
34
- settingsPageMeta
37
+ settingsPageMeta,
38
+ trackerPageMeta
35
39
  };