@cosxai/ui 0.24.0 → 1.0.0-alpha.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 (133) hide show
  1. package/CHANGELOG.md +577 -0
  2. package/NOTICE +11 -0
  3. package/README.md +52 -0
  4. package/package.json +33 -7
  5. package/src/fonts.css +20 -0
  6. package/src/index.ts +8 -25
  7. package/src/lib/cn.ts +23 -6
  8. package/src/styles.css +17 -0
  9. package/src/theme.css +132 -0
  10. package/src/tokens/colors.css +112 -0
  11. package/src/tokens/compat.css +36 -0
  12. package/src/tokens/elevation.css +14 -0
  13. package/src/tokens/motion.css +42 -0
  14. package/src/tokens/spacing.css +55 -0
  15. package/src/tokens/typography-cjk.css +42 -0
  16. package/src/tokens/typography.css +81 -0
  17. package/src/actionbar/ActionBar.tsx +0 -837
  18. package/src/actionbar/ActionBarButton.tsx +0 -111
  19. package/src/actionbar/ActionBarMenuGroup.tsx +0 -116
  20. package/src/actionbar/ActionBarModeHandle.tsx +0 -171
  21. package/src/actionbar/ActionBarProvider.tsx +0 -91
  22. package/src/actionbar/actionbar-context.ts +0 -45
  23. package/src/actionbar/index.ts +0 -28
  24. package/src/actionbar/types.ts +0 -147
  25. package/src/actionbar/useActionBarItems.ts +0 -60
  26. package/src/actionbar/useActionBarMode.ts +0 -144
  27. package/src/actionbar/useActionBarPanel.ts +0 -56
  28. package/src/actionbar/useActionBarStatusDot.ts +0 -49
  29. package/src/actionbar/useActionBarToast.ts +0 -33
  30. package/src/ambient/AmbientBackdrop.tsx +0 -74
  31. package/src/ambient/CommandInput.tsx +0 -107
  32. package/src/ambient/SuperbarStrip.tsx +0 -36
  33. package/src/ambient/index.ts +0 -6
  34. package/src/bento/BentoCell.tsx +0 -66
  35. package/src/bento/BentoGrid.tsx +0 -42
  36. package/src/bento/index.ts +0 -2
  37. package/src/command/CommandPalette.tsx +0 -329
  38. package/src/command/CommandProvider.tsx +0 -57
  39. package/src/command/command-context.ts +0 -12
  40. package/src/command/index.ts +0 -6
  41. package/src/command/rank.ts +0 -50
  42. package/src/command/types.ts +0 -37
  43. package/src/command/useCommandSource.ts +0 -37
  44. package/src/dialogs/DialogsProvider.tsx +0 -216
  45. package/src/dialogs/Modal.tsx +0 -260
  46. package/src/dialogs/Toast.tsx +0 -85
  47. package/src/dialogs/dialogs-context.ts +0 -6
  48. package/src/dialogs/index.ts +0 -10
  49. package/src/dialogs/types.ts +0 -37
  50. package/src/dialogs/useDialogs.ts +0 -8
  51. package/src/editorial/EditorialSpotlight.tsx +0 -63
  52. package/src/editorial/Folio.tsx +0 -52
  53. package/src/editorial/PlateMarker.tsx +0 -33
  54. package/src/editorial/RomanSection.tsx +0 -65
  55. package/src/editorial/RunningMarginalia.tsx +0 -65
  56. package/src/editorial/index.ts +0 -10
  57. package/src/hooks/index.ts +0 -5
  58. package/src/hooks/useKeyboardHotkey.ts +0 -93
  59. package/src/hooks/useReducedMotion.ts +0 -20
  60. package/src/hooks/useViewport.ts +0 -61
  61. package/src/layout/Breadcrumb.tsx +0 -74
  62. package/src/layout/LeftNavRail.tsx +0 -139
  63. package/src/layout/MobileTabBar.tsx +0 -101
  64. package/src/layout/NavItem.tsx +0 -128
  65. package/src/layout/NavSearchTrigger.tsx +0 -88
  66. package/src/layout/NavSection.tsx +0 -40
  67. package/src/layout/RightSidebarPanel.tsx +0 -111
  68. package/src/layout/Shell.tsx +0 -91
  69. package/src/layout/SidePanel.tsx +0 -316
  70. package/src/layout/StickyBanner.tsx +0 -83
  71. package/src/layout/Topbar.tsx +0 -68
  72. package/src/layout/index.ts +0 -24
  73. package/src/layout/useNavRailState.ts +0 -69
  74. package/src/lib/time-utils.ts +0 -44
  75. package/src/neobrutalism/Marquee.tsx +0 -81
  76. package/src/neobrutalism/Sticker.tsx +0 -71
  77. package/src/neobrutalism/index.ts +0 -4
  78. package/src/primitives/Avatar.tsx +0 -53
  79. package/src/primitives/Button.tsx +0 -67
  80. package/src/primitives/Card.tsx +0 -41
  81. package/src/primitives/Checkbox.tsx +0 -87
  82. package/src/primitives/Chip.tsx +0 -177
  83. package/src/primitives/Combobox.tsx +0 -475
  84. package/src/primitives/CopyField.tsx +0 -139
  85. package/src/primitives/CountBadge.tsx +0 -50
  86. package/src/primitives/Input.tsx +0 -161
  87. package/src/primitives/Kbd.tsx +0 -45
  88. package/src/primitives/MentionCombobox.tsx +0 -612
  89. package/src/primitives/MultiCombobox.tsx +0 -179
  90. package/src/primitives/PageHeader.tsx +0 -77
  91. package/src/primitives/Radio.tsx +0 -131
  92. package/src/primitives/SegmentedControl.tsx +0 -135
  93. package/src/primitives/Select.tsx +0 -779
  94. package/src/primitives/SignaturePad.tsx +0 -461
  95. package/src/primitives/Tag.tsx +0 -56
  96. package/src/primitives/Textarea.tsx +0 -62
  97. package/src/primitives/ToggleSwitch.tsx +0 -79
  98. package/src/primitives/Tooltip.tsx +0 -213
  99. package/src/primitives/index.ts +0 -48
  100. package/src/pwa/InstallPromptBanner.tsx +0 -132
  101. package/src/pwa/index.ts +0 -4
  102. package/src/pwa/manifest.template.json +0 -20
  103. package/src/pwa/registerSW.ts +0 -55
  104. package/src/riso/Halftone.tsx +0 -85
  105. package/src/riso/Misregister.tsx +0 -63
  106. package/src/riso/RisoStamp.tsx +0 -76
  107. package/src/riso/index.ts +0 -3
  108. package/src/sketch/HandUnderline.tsx +0 -53
  109. package/src/sketch/RoughArrow.tsx +0 -91
  110. package/src/sketch/RoughBox.tsx +0 -73
  111. package/src/sketch/StickyNote.tsx +0 -56
  112. package/src/sketch/index.ts +0 -4
  113. package/src/styles/base.css +0 -186
  114. package/src/styles/chrome-ambient.css +0 -235
  115. package/src/styles/chrome-bento.css +0 -192
  116. package/src/styles/chrome-editorial.css +0 -168
  117. package/src/styles/chrome-neobrutalism.css +0 -326
  118. package/src/styles/chrome-riso.css +0 -339
  119. package/src/styles/chrome-sketch.css +0 -362
  120. package/src/styles/chrome-swiss.css +0 -265
  121. package/src/styles/chrome-terminal.css +0 -272
  122. package/src/styles/fonts.css +0 -1034
  123. package/src/styles/index.css +0 -306
  124. package/src/styles/tokens.css +0 -941
  125. package/src/terminal/AsciiBox.tsx +0 -65
  126. package/src/terminal/BrailleSpinner.tsx +0 -46
  127. package/src/terminal/index.ts +0 -4
  128. package/src/theme/ThemeProvider.tsx +0 -99
  129. package/src/theme/index.ts +0 -6
  130. package/src/theme/inline-script.ts +0 -40
  131. package/src/theme/theme-context.ts +0 -7
  132. package/src/theme/types.ts +0 -39
  133. package/src/theme/useTheme.ts +0 -8
@@ -1,147 +0,0 @@
1
- import type { ReactNode } from "react";
2
-
3
- // Single item in the action bar. Pages register items via
4
- // useActionBarItems() — the bar itself reads from the registry
5
- // and renders them grouped by category. Categories with 2+ items
6
- // fold into one inline-expandable group head.
7
-
8
- export type ActionBarItemVariant = "ghost" | "primary" | "soft";
9
-
10
- // Slot anchor — which edge of the bar the item gravitates toward.
11
- // `leading` (default) keeps existing behavior: items pack from the
12
- // drag grip outward in registration order. `trailing` pins the item
13
- // to the right edge with a flex spacer separating it from the
14
- // leading group, regardless of registration order. Use trailing for
15
- // system status indicators (sync, connection, identity) that need a
16
- // stable visual home and shouldn't be shuffled by page items
17
- // registering after them.
18
- export type ActionBarItemSlot = "leading" | "trailing";
19
-
20
- export interface ActionBarItem {
21
- // React key for the rendered button. Stable across rerenders.
22
- key: string;
23
- label: string;
24
- icon?: ReactNode;
25
- onClick: () => void;
26
- // State indicator — active items render with the accent tone.
27
- active?: boolean;
28
- // Tooltip / aria-label. Falls back to label.
29
- title?: string;
30
- // Disable without removing.
31
- disabled?: boolean;
32
- // Group key — items sharing a category collapse into one
33
- // expandable group head when 2+ are present. Single items
34
- // render inline. Categories are declared at the provider level.
35
- category?: string;
36
- // Visual mode. Default ghost.
37
- variant?: ActionBarItemVariant;
38
- // Optional kbd hint shown beside the label (just visual — the
39
- // shortcut is wired by the caller via useKeyboardHotkey).
40
- hint?: string;
41
- // When inside an expanded group, clicking this item normally
42
- // collapses the group afterward. Set true to keep it open
43
- // (e.g. for items whose post-click state changes the label
44
- // and should stay visible).
45
- keepGroupOpenOnClick?: boolean;
46
- // Slot anchor. Defaults to `leading`. Trailing items render after
47
- // a flex spacer so they pin to the right edge regardless of
48
- // registration order — system status indicators belong here.
49
- slot?: ActionBarItemSlot;
50
- // Marks this item as admin-only. Hidden until the ActionBar's
51
- // admin-mode toggle is switched on. The toggle button auto-appears
52
- // on the bar whenever at least one registered item has adminOnly=
53
- // true; when no consumer registers such items, the toggle stays
54
- // hidden and the bar looks exactly as it always did.
55
- //
56
- // Use for elevated-privilege actions that would confuse regular
57
- // users (version history, retry render, delete forever, etc.).
58
- // Consumer decides eligibility upstream — typically by only
59
- // registering the item when the caller has `capabilities.manage`
60
- // (or an equivalent gate) === true. The kit does not check
61
- // permissions; it only handles the toggle UX.
62
- adminOnly?: boolean;
63
- // Marks a normally-visible item as hidden when admin mode is on.
64
- // Symmetric counterpart to `adminOnly`: pages that want an
65
- // exclusive "different toolset" UX (e.g. Share disappears in favour
66
- // of Manage Share + Activity + History) mark their regular items
67
- // hiddenInAdmin=true so they clear when the toggle flips on.
68
- // Pages that want additive layering (regular items stay + admin
69
- // items appear alongside) just leave this unset.
70
- //
71
- // Per-item because different pages within the same app want
72
- // different behaviour: block_doc viewer wants exclusive; a
73
- // hypothetical PDF admin surface might want additive.
74
- hiddenInAdmin?: boolean;
75
- }
76
-
77
- // Category definition — declared at the provider level. Drives
78
- // the group head's label + icon.
79
- export interface ActionBarCategory {
80
- label: string;
81
- icon?: ReactNode;
82
- }
83
-
84
- export type ActionBarCategories = Record<string, ActionBarCategory>;
85
-
86
- export interface ActionBarSource {
87
- sourceKey: string;
88
- items: ActionBarItem[];
89
- }
90
-
91
- // Status dot — bar-intrinsic chrome rendered at the right edge,
92
- // mirroring the left-edge drag grip. Distinct from `ActionBarItem`
93
- // (which is page-level content registered through the items
94
- // registry): a status dot is a fixed system affordance —
95
- // connection / sync / identity health. The grip on the left says
96
- // "you can move this thing"; the status dot on the right says
97
- // "here's how the system is doing."
98
- //
99
- // Visual: a small coloured dot, optionally pulsing. Hover shows the
100
- // title as a native tooltip. Click fires `onClick` — the bar owns
101
- // no popover behaviour; consumers render their own popover anchored
102
- // to body / wherever fits their UX.
103
- //
104
- // Registered via `useActionBarStatusDot()`. One source of truth at
105
- // any time (last call wins). Pass `null` to clear.
106
- export interface ActionBarStatusDot {
107
- // Colour of the dot. Any CSS colour string — typically a token
108
- // like `var(--ck-status-success)`. The bar applies no defaulting,
109
- // so omit the entry (pass null to the hook) to render no dot.
110
- color: string;
111
- // Hover tooltip + aria-label. Falls back to "Status".
112
- title?: string;
113
- // Click handler. When omitted the dot renders as a non-interactive
114
- // span (no focus ring, no cursor change, no role=button).
115
- onClick?: () => void;
116
- // When true, the dot pulses (CSS animation). Use sparingly —
117
- // pulsing reads as attention-grabbing.
118
- pulse?: boolean;
119
- }
120
-
121
- // Registered via `useActionBarPanel()` — the bar-intrinsic popover
122
- // slot (design#13). The BAR owns positioning (docked above itself,
123
- // following drag natively) and the chrome themes own the panel's
124
- // visual identity via `.ck-actionbar-panel`; the consumer owns only
125
- // the content subtree. One panel at a time (last call wins).
126
- export interface ActionBarPanel {
127
- // Controlled visibility. The bar calls onOpenChange(false) on
128
- // outside-click and Escape; the consumer toggles open from its
129
- // own affordance (typically the status dot's onClick).
130
- open: boolean;
131
- onOpenChange: (open: boolean) => void;
132
- // Content subtree. Portalled into the themed panel container the
133
- // bar renders — it stays in the CONSUMER's React tree (fresh JSX
134
- // identity per render is fine; content never flows through
135
- // context state, which would re-render-loop).
136
- content: ReactNode;
137
- // Panel width in px. Default 320; always viewport-clamped.
138
- width?: number;
139
- // aria-label for the panel region. Default "Panel".
140
- ariaLabel?: string;
141
- }
142
-
143
- // What the context actually stores for the panel slot: the config
144
- // minus `content`. Only stable primitives + the onOpenChange
145
- // callback go through context state, so the registration effect
146
- // re-runs on real changes only.
147
- export type ActionBarPanelMeta = Omit<ActionBarPanel, "content">;
@@ -1,60 +0,0 @@
1
- import { useContext, useEffect } from "react";
2
- import { ActionBarContext } from "./actionbar-context";
3
- import type { ActionBarItem } from "./types";
4
-
5
- // Register a set of items into the global action-bar registry.
6
- // Auto-unregisters on unmount + on sourceKey change.
7
- //
8
- // **Pass a `useMemo`'d array.** The effect re-registers whenever
9
- // the array identity changes; the provider's `register()` then
10
- // dedups by item-by-item identity, so a fresh array with identical
11
- // item refs is cheap (state stays === and no re-render fires) but
12
- // a fresh array with NEW item objects (e.g. consumers whose
13
- // onClick closures captured updated state) does re-register —
14
- // downstream renders pick up the fresh handlers.
15
- //
16
- // Inline-array consumers (`useActionBarItems(key, [{...}])`)
17
- // will allocate new items every render and loop infinitely
18
- // through `register → setState → re-render → register`. The
19
- // useMemo on the caller side is what keeps that loop bounded
20
- // to "items whose deps actually changed".
21
- //
22
- // Previous versions wrapped items in a local `useMemo` keyed by
23
- // `[items.length, items.map(i => i.key).join('|')]`. That gate
24
- // suppressed identity updates whenever the keys + length stayed
25
- // stable, which is precisely the case for a selection-mode
26
- // toolbar whose buttons are fixed but whose onClicks close over
27
- // changing state — they ended up registered ONCE with the first
28
- // render's closures and never refreshed, shipping stale state to
29
- // every subsequent click. The gate is gone; the provider's
30
- // shallow item-identity dedup is the single source of truth.
31
- //
32
- // **Important**: the useEffect deps below intentionally do NOT
33
- // include the ctx object itself — only its stable function refs
34
- // (register/unregister are useCallback'd in the provider).
35
- // Including ctx would cause an infinite render loop: register
36
- // mutates provider state → provider's value memo recomputes →
37
- // ctx ref changes → effect re-runs → cleanup unregisters →
38
- // state mutates again → ctx changes → loop. Manifests as the
39
- // main thread saturating and event clicks never reaching the
40
- // router. Same fix applies in useCommandSource.
41
-
42
- export function useActionBarItems(sourceKey: string, items: ActionBarItem[]) {
43
- const ctx = useContext(ActionBarContext);
44
- if (!ctx) {
45
- throw new Error("useActionBarItems must be used within <ActionBarProvider>");
46
- }
47
- const { register, unregister } = ctx;
48
- useEffect(() => {
49
- register(sourceKey, items);
50
- return () => unregister(sourceKey);
51
- }, [register, unregister, sourceKey, items]);
52
- }
53
-
54
- export function useActionBarItemsContext() {
55
- const ctx = useContext(ActionBarContext);
56
- if (!ctx) {
57
- throw new Error("Action bar context not found");
58
- }
59
- return ctx;
60
- }
@@ -1,144 +0,0 @@
1
- import { useCallback, useEffect, useMemo, useState } from "react";
2
- import { useActionBarItems } from "./useActionBarItems";
3
- import type { ActionBarItem } from "./types";
4
-
5
- // useActionBarMode — one hook that manages a small set of named
6
- // "modes" (each with its own item list) + registers the active mode's
7
- // items into the ActionBar registry. Consumer combines this with
8
- // <ActionBarModeHandle> to give privileged users a peek-out affordance
9
- // that flips between the modes.
10
- //
11
- // Design intent (M4.5 block_doc surface):
12
- // - Default mode: standard viewer actions (comment / download / share)
13
- // - Manage mode: admin-only shortcuts (preview draft / manage share /
14
- // view activity), gated behind `capabilities.manage === true`
15
- // - Draft mode (optional third): appears once the user enters draft
16
- // preview; distinct action set (publish / discard / raw JSON /
17
- // return)
18
- //
19
- // The mode name is the ActionBar's source key, so switching modes
20
- // unregisters the previous set and registers the new set atomically
21
- // (useActionBarItems handles the cleanup).
22
- //
23
- // State persistence: pass `storageKey` for localStorage-backed
24
- // stickiness across reloads. Nil-safe when window is unavailable
25
- // (SSR).
26
-
27
- export type ActionBarModeConfig<K extends string> = {
28
- [key in K]: ActionBarItem[];
29
- };
30
-
31
- export interface UseActionBarModeOptions<K extends string> {
32
- /**
33
- * Map of mode keys → items. Each mode key becomes the source key
34
- * passed to useActionBarItems, so it must be a stable string
35
- * (e.g. "viewer", "manage") — mode registrations will not
36
- * "cross-contaminate" the item set of another mode.
37
- */
38
- modes: ActionBarModeConfig<K>;
39
- /**
40
- * Which mode to start in. Overridden by localStorage when
41
- * `storageKey` is set AND localStorage has a valid value.
42
- */
43
- defaultMode: K;
44
- /**
45
- * Optional localStorage key. When set, the current mode is
46
- * persisted + rehydrated across reloads.
47
- */
48
- storageKey?: string | undefined;
49
- }
50
-
51
- export interface UseActionBarModeResult<K extends string> {
52
- /** The currently active mode key. */
53
- mode: K;
54
- /** Set the mode explicitly (typically wired to the mode Handle). */
55
- setMode: (next: K) => void;
56
- /**
57
- * Toggle between the FIRST two modes in the config's iteration
58
- * order. Convenience for the common two-mode case; a
59
- * three-mode consumer uses setMode directly.
60
- */
61
- toggle: () => void;
62
- /**
63
- * All configured mode keys in registration order — useful for
64
- * building a cycle-through-modes button.
65
- */
66
- allModes: readonly K[];
67
- }
68
-
69
- function loadPersistedMode<K extends string>(
70
- storageKey: string,
71
- allModes: readonly K[],
72
- ): K | null {
73
- if (typeof window === "undefined") return null;
74
- try {
75
- const raw = window.localStorage.getItem(storageKey);
76
- if (raw && (allModes as readonly string[]).includes(raw)) {
77
- return raw as K;
78
- }
79
- } catch {
80
- /* ignore */
81
- }
82
- return null;
83
- }
84
-
85
- function persistMode(storageKey: string, mode: string) {
86
- if (typeof window === "undefined") return;
87
- try {
88
- window.localStorage.setItem(storageKey, mode);
89
- } catch {
90
- /* ignore */
91
- }
92
- }
93
-
94
- export function useActionBarMode<K extends string>(
95
- opts: UseActionBarModeOptions<K>,
96
- ): UseActionBarModeResult<K> {
97
- const { modes, defaultMode, storageKey } = opts;
98
- const allModes = useMemo(() => Object.keys(modes) as K[], [modes]);
99
- const [mode, setModeState] = useState<K>(() => {
100
- if (storageKey) {
101
- const persisted = loadPersistedMode<K>(storageKey, allModes);
102
- if (persisted) return persisted;
103
- }
104
- return defaultMode;
105
- });
106
-
107
- // If the modes config changes (dev / hot reload) so the current
108
- // mode key is no longer valid, drop back to the default.
109
- useEffect(() => {
110
- if (!(allModes as readonly string[]).includes(mode)) {
111
- setModeState(defaultMode);
112
- }
113
- }, [mode, allModes, defaultMode]);
114
-
115
- const setMode = useCallback(
116
- (next: K) => {
117
- setModeState(next);
118
- if (storageKey) persistMode(storageKey, next);
119
- },
120
- [storageKey],
121
- );
122
-
123
- const toggle = useCallback(() => {
124
- if (allModes.length < 2) return;
125
- const idx = allModes.indexOf(mode);
126
- // Cycle to the NEXT mode; wrap-around handles both the "toggle
127
- // between 2" and "cycle through 3+" cases.
128
- const nextIdx = (idx + 1) % allModes.length;
129
- const next = allModes[nextIdx];
130
- if (next !== undefined) {
131
- setMode(next);
132
- }
133
- }, [allModes, mode, setMode]);
134
-
135
- // Register the active mode's items with the ActionBar. Uses the
136
- // mode key as the source key, so switching modes atomically
137
- // unregisters the previous set. Items themselves must be stable
138
- // (useMemo'd) — the ActionBar deduplicates by reference, so passing
139
- // `modes[mode]` re-uses the caller's memoized array.
140
- const activeItems = modes[mode];
141
- useActionBarItems(mode, activeItems);
142
-
143
- return { mode, setMode, toggle, allModes };
144
- }
@@ -1,56 +0,0 @@
1
- import { useContext, useEffect } from "react";
2
- import { createPortal } from "react-dom";
3
- import { ActionBarContext } from "./actionbar-context";
4
- import type { ActionBarPanel } from "./types";
5
- import type { ReactNode } from "react";
6
-
7
- // Push a panel config into the action bar's popover slot (design#13).
8
- //
9
- // The BAR owns positioning: docked above itself, centered, following
10
- // drag natively (it renders the panel inside its own fixed-position
11
- // root — no polling, no rect maths in the consumer) and viewport-
12
- // clamped at the edges. The chrome themes own the panel's visual
13
- // identity via `.ck-actionbar-panel`; consumers must NOT paint their
14
- // own background/border/shadow. The consumer owns only the content
15
- // subtree and the `open` state.
16
- //
17
- // RETURNS a ReactNode the consumer MUST render (a portal into the
18
- // bar's panel container, or null while closed). Content stays in the
19
- // consumer's own React tree — a fresh JSX identity every render is
20
- // fine because only the meta (open/onOpenChange/width/ariaLabel)
21
- // flows through context state; content in state would re-render-loop
22
- // (setPanel → context change → consumer re-render → new JSX →
23
- // setPanel → …).
24
- //
25
- // The bar calls `onOpenChange(false)` on outside-click and Escape.
26
- // Pass `null` to clear (also cleared automatically on unmount).
27
- // Last call wins — one panel at a time, same contract as the status
28
- // dot.
29
- export function useActionBarPanel(config: ActionBarPanel | null): ReactNode {
30
- const ctx = useContext(ActionBarContext);
31
- if (!ctx) {
32
- throw new Error("useActionBarPanel must be used within <ActionBarProvider>");
33
- }
34
- const { setPanel, panelHost } = ctx;
35
- const open = config?.open;
36
- const onOpenChange = config?.onOpenChange;
37
- const width = config?.width;
38
- const ariaLabel = config?.ariaLabel;
39
- useEffect(() => {
40
- if (open === undefined || onOpenChange === undefined) {
41
- setPanel(null);
42
- return;
43
- }
44
- setPanel({
45
- open,
46
- onOpenChange,
47
- ...(width !== undefined ? { width } : {}),
48
- ...(ariaLabel !== undefined ? { ariaLabel } : {}),
49
- });
50
- return () => {
51
- setPanel(null);
52
- };
53
- }, [setPanel, open, onOpenChange, width, ariaLabel]);
54
- if (!config?.open || panelHost === null || config.content == null) return null;
55
- return createPortal(config.content, panelHost);
56
- }
@@ -1,49 +0,0 @@
1
- import { useContext, useEffect } from "react";
2
- import { ActionBarContext } from "./actionbar-context";
3
- import type { ActionBarStatusDot } from "./types";
4
-
5
- // Push a status-dot config into the action bar's right-edge slot.
6
- // The bar renders the dot as bar-intrinsic chrome (mirroring the
7
- // left-edge drag grip), separate from the items registry.
8
- //
9
- // Pass `null` to clear. Last call wins — there's no source-key
10
- // fan-out as with items, because the status dot is a single
11
- // system-level affordance (sync / connection / identity) and
12
- // stacking multiple makes no UX sense.
13
- //
14
- // On unmount, the dot is cleared automatically.
15
- //
16
- // **Important**: pass a stable config object (typically via
17
- // `useMemo`), or omit fields you don't change between renders.
18
- // The effect re-runs when individual fields change — colour
19
- // transitions or pulse toggles re-render the dot but don't thrash
20
- // the bar.
21
- export function useActionBarStatusDot(config: ActionBarStatusDot | null) {
22
- const ctx = useContext(ActionBarContext);
23
- if (!ctx) {
24
- throw new Error("useActionBarStatusDot must be used within <ActionBarProvider>");
25
- }
26
- const { setStatusDot } = ctx;
27
- // Project config to primitive deps so a fresh-but-equivalent
28
- // config object doesn't trigger thrash. onClick identity changes
29
- // are tolerated through a manual ref capture in the bar render.
30
- const color = config?.color;
31
- const title = config?.title;
32
- const onClick = config?.onClick;
33
- const pulse = config?.pulse;
34
- useEffect(() => {
35
- if (color === undefined) {
36
- setStatusDot(null);
37
- return;
38
- }
39
- setStatusDot({
40
- color,
41
- ...(title !== undefined ? { title } : {}),
42
- ...(onClick !== undefined ? { onClick } : {}),
43
- ...(pulse !== undefined ? { pulse } : {}),
44
- });
45
- return () => {
46
- setStatusDot(null);
47
- };
48
- }, [setStatusDot, color, title, onClick, pulse]);
49
- }
@@ -1,33 +0,0 @@
1
- import { useContext, useEffect } from "react";
2
- import { createPortal } from "react-dom";
3
- import { ActionBarContext } from "./actionbar-context";
4
- import type { ReactNode } from "react";
5
-
6
- // Push a transient bubble into the bar's toast slot — rendered above
7
- // the bar (following it when dragged), themed via
8
- // `.ck-actionbar-toast`, suppressed while the panel is open. The
9
- // CONSUMER owns the lifetime: pass the node to show, `null` to hide
10
- // (typically driven by a setTimeout in the consumer).
11
- //
12
- // RETURNS a ReactNode the consumer MUST render (a portal into the
13
- // bar's toast container, or null while hidden). Same portal-host
14
- // contract as useActionBarPanel: only the boolean "active" flag goes
15
- // through context state, so a fresh JSX identity per render can't
16
- // re-render-loop. Pairs with useActionBarPanel for the notification-
17
- // center pattern (design#13).
18
- export function useActionBarToast(content: ReactNode | null): ReactNode {
19
- const ctx = useContext(ActionBarContext);
20
- if (!ctx) {
21
- throw new Error("useActionBarToast must be used within <ActionBarProvider>");
22
- }
23
- const { setToastActive, toastHost } = ctx;
24
- const active = content != null;
25
- useEffect(() => {
26
- setToastActive(active);
27
- return () => {
28
- setToastActive(false);
29
- };
30
- }, [setToastActive, active]);
31
- if (!active || toastHost === null) return null;
32
- return createPortal(content, toastHost);
33
- }
@@ -1,74 +0,0 @@
1
- import type { CSSProperties, ReactNode } from "react";
2
-
3
- // Standalone ambient-mesh backdrop primitive. The ambient chrome
4
- // applies its mesh on `body` automatically, so consumers don't
5
- // usually need this. Use it when you want an ambient look INSIDE
6
- // a single section (a hero banner, a preview region) without
7
- // flipping the whole app's chrome.
8
-
9
- export interface AmbientBackdropProps {
10
- children?: ReactNode;
11
- // Use light variant tints instead of dark. Default auto-detects
12
- // via prefers-color-scheme.
13
- variant?: "dark" | "light";
14
- // Hide the noise overlay. Default false.
15
- noise?: boolean;
16
- className?: string;
17
- style?: CSSProperties;
18
- }
19
-
20
- const DARK_BG = `
21
- radial-gradient(ellipse at 18% 22%, #3A2A66 0%, transparent 55%),
22
- radial-gradient(ellipse at 82% 70%, #2A1E50 0%, transparent 52%),
23
- radial-gradient(ellipse at 50% 110%, #4A2870 0%, transparent 50%),
24
- #1A1525
25
- `;
26
-
27
- const LIGHT_BG = `
28
- radial-gradient(ellipse at 20% 24%, #FCEFE5 0%, transparent 55%),
29
- radial-gradient(ellipse at 78% 70%, #EFEAF8 0%, transparent 52%),
30
- radial-gradient(ellipse at 50% 110%, #FFE8DC 0%, transparent 50%),
31
- #F6F4F0
32
- `;
33
-
34
- export function AmbientBackdrop({
35
- children,
36
- variant,
37
- noise,
38
- className,
39
- style,
40
- }: AmbientBackdropProps) {
41
- // Auto-pick from prefers-color-scheme when not explicit. SSR-
42
- // safe: defaults to dark, the canonical Superlist look.
43
- const isDark =
44
- variant === "dark" ||
45
- (variant === undefined &&
46
- (typeof window === "undefined" ||
47
- window.matchMedia?.("(prefers-color-scheme: dark)").matches));
48
- return (
49
- <div
50
- className={className}
51
- style={{
52
- position: "relative",
53
- background: isDark ? DARK_BG : LIGHT_BG,
54
- ...style,
55
- }}
56
- >
57
- {noise && (
58
- <div
59
- aria-hidden
60
- style={{
61
- position: "absolute",
62
- inset: 0,
63
- pointerEvents: "none",
64
- opacity: 0.025,
65
- backgroundImage:
66
- "url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>\")",
67
- mixBlendMode: "overlay",
68
- }}
69
- />
70
- )}
71
- {children}
72
- </div>
73
- );
74
- }
@@ -1,107 +0,0 @@
1
- import { useState, type CSSProperties, type FormEvent } from "react";
2
-
3
- // The signature ambient component — floating AI / command pill
4
- // at the bottom of the main content. Heavily blurred glass
5
- // backdrop, gradient avatar on the left (signals "AI / live"),
6
- // and a thin Superbar gradient line above it that doubles as a
7
- // section divider. The most distinctive moment in the design
8
- // language.
9
- //
10
- // Works under any chrome — only really "reads" under
11
- // ambient because the glass + Superbar accent are the defining
12
- // gestures. Under other chromes the pill is just a styled
13
- // input + divider.
14
-
15
- export interface CommandInputProps {
16
- placeholder?: string;
17
- onSubmit?: (value: string) => void;
18
- // Width cap on the pill itself. Default 520 px (Superlist-ish).
19
- maxWidth?: number;
20
- // Hide the Superbar divider above. Default false.
21
- hideDivider?: boolean;
22
- className?: string;
23
- style?: CSSProperties;
24
- }
25
-
26
- export function CommandInput({
27
- placeholder = "Ask anything…",
28
- onSubmit,
29
- maxWidth = 520,
30
- hideDivider = false,
31
- className,
32
- style,
33
- }: CommandInputProps) {
34
- const [value, setValue] = useState("");
35
- const submit = (e: FormEvent) => {
36
- e.preventDefault();
37
- if (!value.trim()) return;
38
- onSubmit?.(value.trim());
39
- setValue("");
40
- };
41
-
42
- return (
43
- <div className={className} style={{ width: "100%", ...style }}>
44
- {!hideDivider && (
45
- <div
46
- aria-hidden
47
- style={{
48
- height: 1,
49
- background: "var(--ck-superbar)",
50
- opacity: 0.7,
51
- margin: "0 auto 20px",
52
- maxWidth,
53
- }}
54
- />
55
- )}
56
- <form
57
- onSubmit={submit}
58
- style={{
59
- maxWidth,
60
- margin: "0 auto",
61
- display: "flex",
62
- alignItems: "center",
63
- gap: 10,
64
- padding: "8px 12px",
65
- background: "var(--ck-bg-sidebar, rgba(20,20,28,0.55))",
66
- border: "1px solid var(--ck-border-subtle, rgba(255,255,255,0.08))",
67
- borderRadius: 999,
68
- backdropFilter: "var(--ck-blur-overlay, blur(40px) saturate(180%))",
69
- WebkitBackdropFilter: "var(--ck-blur-overlay, blur(40px) saturate(180%))",
70
- boxShadow: "var(--ck-shadow-2)",
71
- }}
72
- >
73
- {/* Gradient avatar — coral → violet → blue. Marks the
74
- pill as "AI / live", regardless of the consumer's
75
- brand accent. */}
76
- <span
77
- aria-hidden
78
- style={{
79
- width: 24,
80
- height: 24,
81
- borderRadius: "50%",
82
- background: "var(--ck-superbar)",
83
- flexShrink: 0,
84
- boxShadow: "0 0 12px color-mix(in srgb, #B66EFF 40%, transparent)",
85
- }}
86
- />
87
- <input
88
- type="text"
89
- value={value}
90
- onChange={(e) => setValue(e.target.value)}
91
- placeholder={placeholder}
92
- style={{
93
- flex: 1,
94
- minWidth: 0,
95
- height: 32,
96
- padding: 0,
97
- border: "none",
98
- outline: "none",
99
- background: "transparent",
100
- color: "var(--ck-text-primary)",
101
- font: "400 14px/1 var(--ck-font-sans)",
102
- }}
103
- />
104
- </form>
105
- </div>
106
- );
107
- }