@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,111 +0,0 @@
1
- import type { ReactNode, CSSProperties } from "react";
2
- import { cn } from "../lib/cn";
3
-
4
- // Single button inside the action bar — used both for top-level
5
- // items and for entries inside expanded group menus.
6
- //
7
- // Three visual variants:
8
- // - ghost → default. Transparent bg, neutral text. Active
9
- // state promotes to accent muted bg.
10
- // - primary → solid accent fill, white text. For items whose
11
- // "this mode is on" signal should beat the "this
12
- // group is open" signal (e.g. an editing-mode toggle
13
- // nested in a Manage group, while Manage is open).
14
- // - soft → transparent bg, accent-coloured text. Used for
15
- // open group HEADS where the wrapper supplies a
16
- // muted-accent bg; the head just needs the
17
- // foreground to read as accent.
18
-
19
- // Optional props use `| undefined` (rather than bare `T?`) so that callers
20
- // can forward potentially-undefined values from upstream optional sources
21
- // (e.g. `ActionBarItem.active`, which is itself optional). Compatible with
22
- // `exactOptionalPropertyTypes: true` consumers.
23
- export interface ActionBarButtonProps {
24
- icon?: ReactNode | undefined;
25
- label: string;
26
- onClick: () => void;
27
- active?: boolean | undefined;
28
- title?: string | undefined;
29
- variant?: "ghost" | "primary" | "soft" | undefined;
30
- disabled?: boolean | undefined;
31
- hint?: string | undefined;
32
- // Optional chevron — used by group heads to signal disclosure.
33
- chevron?: "right" | undefined;
34
- // 180° rotation when true (▸ becomes ◂ — reads as "close").
35
- chevronRotated?: boolean | undefined;
36
- // Hide label below md viewport (768 px). Default true.
37
- responsiveLabel?: boolean | undefined;
38
- style?: CSSProperties | undefined;
39
- }
40
-
41
- export function ActionBarButton({
42
- icon,
43
- label,
44
- onClick,
45
- active,
46
- title,
47
- variant = "ghost",
48
- disabled,
49
- hint,
50
- chevron,
51
- chevronRotated,
52
- responsiveLabel = true,
53
- style,
54
- }: ActionBarButtonProps) {
55
- const isPrimary = active || variant === "primary";
56
- const isSoft = !isPrimary && variant === "soft";
57
- return (
58
- <button
59
- type="button"
60
- onClick={onClick}
61
- disabled={disabled}
62
- title={title ?? label}
63
- className={cn(
64
- "ck-actionbar-btn",
65
- isPrimary && "ck-actionbar-btn--primary",
66
- !isPrimary && "ck-actionbar-btn--ghost",
67
- )}
68
- style={{
69
- ...(isSoft ? { color: "var(--ck-accent)" } : undefined),
70
- ...style,
71
- }}
72
- >
73
- {icon != null && <span className="ck-actionbar-icon">{icon}</span>}
74
- <span className={responsiveLabel ? "ck-actionbar-label" : undefined}>
75
- {label}
76
- </span>
77
- {hint && (
78
- <span
79
- className="ck-actionbar-hint"
80
- style={{
81
- color: isPrimary ? "rgba(255,255,255,0.7)" : "var(--ck-text-tertiary)",
82
- font: "500 11px/1 var(--ck-font-mono)",
83
- marginLeft: 4,
84
- }}
85
- >
86
- {hint}
87
- </span>
88
- )}
89
- {chevron && (
90
- <svg
91
- width="10"
92
- height="10"
93
- viewBox="0 0 24 24"
94
- fill="none"
95
- stroke="currentColor"
96
- strokeWidth="2.2"
97
- strokeLinecap="round"
98
- strokeLinejoin="round"
99
- style={{
100
- flexShrink: 0,
101
- opacity: 0.7,
102
- transform: chevronRotated ? "rotate(180deg)" : "rotate(0deg)",
103
- transition: "transform 220ms cubic-bezier(0.4, 0, 0.2, 1)",
104
- }}
105
- >
106
- <polyline points="9 6 15 12 9 18" />
107
- </svg>
108
- )}
109
- </button>
110
- );
111
- }
@@ -1,116 +0,0 @@
1
- import { useEffect, useRef, type ReactNode } from "react";
2
- import { ActionBarButton } from "./ActionBarButton";
3
- import type { ActionBarItem } from "./types";
4
-
5
- // Inline-expandable group inside the action bar. Head button +
6
- // disclosure region of child items. When open:
7
- // - head gets a "soft" foreground (or "primary" if a child is
8
- // active), wrapped in a muted-accent pill that extends across
9
- // the children too
10
- // - children animate in via max-width + opacity transition
11
- // (no mount/unmount so the open/close has motion)
12
- // - ESC + outside-click both close
13
-
14
- export interface ActionBarMenuGroupProps {
15
- label: string;
16
- icon?: ReactNode;
17
- hasActiveChild: boolean;
18
- isOpen: boolean;
19
- onToggle: () => void;
20
- items: ActionBarItem[];
21
- onItemClicked: (item: ActionBarItem) => void;
22
- }
23
-
24
- export function ActionBarMenuGroup({
25
- label,
26
- icon,
27
- hasActiveChild,
28
- isOpen,
29
- onToggle,
30
- items,
31
- onItemClicked,
32
- }: ActionBarMenuGroupProps) {
33
- const headVariant: "ghost" | "primary" | "soft" = hasActiveChild
34
- ? "primary"
35
- : isOpen
36
- ? "soft"
37
- : "ghost";
38
-
39
- // Outside-click closes the group via a capture-phase listener so
40
- // the menu dismisses before the click reaches any other handler.
41
- const wrapperRef = useRef<HTMLDivElement>(null);
42
- useEffect(() => {
43
- if (!isOpen) return;
44
- const onMouseDown = (e: MouseEvent) => {
45
- if (!wrapperRef.current) return;
46
- if (wrapperRef.current.contains(e.target as Node)) return;
47
- onToggle();
48
- };
49
- document.addEventListener("mousedown", onMouseDown, true);
50
- return () => document.removeEventListener("mousedown", onMouseDown, true);
51
- }, [isOpen, onToggle]);
52
-
53
- return (
54
- <div
55
- ref={wrapperRef}
56
- // ck-actionbar-group--open scopes a deeper hover tint to the
57
- // children while the disclosure is open. The default
58
- // .ck-actionbar-btn:hover uses --ck-bg-muted (gray) — fine when
59
- // a button sits on app background, but reads muddy when stacked
60
- // on top of this wrapper's --ck-accent-muted pill. The chrome
61
- // sheet replaces the gray with an accent-blended overlay only
62
- // for that scope so the hover signal stays in the accent hue.
63
- className={
64
- isOpen ? "ck-actionbar-group ck-actionbar-group--open" : "ck-actionbar-group"
65
- }
66
- style={{
67
- display: "inline-flex",
68
- alignItems: "center",
69
- gap: 6,
70
- background: isOpen ? "var(--ck-accent-muted)" : "transparent",
71
- borderRadius: 999,
72
- transition: "background 200ms ease",
73
- }}
74
- >
75
- <ActionBarButton
76
- icon={icon}
77
- label={label}
78
- title={isOpen ? `Close ${label}` : label}
79
- variant={headVariant}
80
- chevron="right"
81
- chevronRotated={isOpen}
82
- onClick={onToggle}
83
- />
84
- <div
85
- aria-hidden={!isOpen}
86
- style={{
87
- display: "flex",
88
- alignItems: "center",
89
- gap: 6,
90
- overflow: "hidden",
91
- maxWidth: isOpen ? 1000 : 0,
92
- opacity: isOpen ? 1 : 0,
93
- marginLeft: isOpen ? 0 : -6,
94
- paddingRight: isOpen ? 4 : 0,
95
- pointerEvents: isOpen ? "auto" : "none",
96
- transition:
97
- "max-width 240ms cubic-bezier(0.4, 0, 0.2, 1), opacity 180ms ease, margin-left 240ms cubic-bezier(0.4, 0, 0.2, 1), padding-right 240ms cubic-bezier(0.4, 0, 0.2, 1)",
98
- }}
99
- >
100
- {items.map((it) => (
101
- <ActionBarButton
102
- key={it.key}
103
- icon={it.icon}
104
- label={it.label}
105
- title={it.title}
106
- active={it.active}
107
- disabled={it.disabled || !isOpen}
108
- variant={it.variant}
109
- hint={it.hint}
110
- onClick={() => onItemClicked(it)}
111
- />
112
- ))}
113
- </div>
114
- </div>
115
- );
116
- }
@@ -1,171 +0,0 @@
1
- import { forwardRef, type CSSProperties } from "react";
2
-
3
- // ActionBarModeHandle — the top-edge peek button that lets a user
4
- // switch the ActionBar between two content sets ("modes"). Sits
5
- // above the ActionBar's centred bottom position, showing only a
6
- // slim 6px sliver at rest; grows on hover / focus to a full 28px
7
- // tappable disc with a tooltip beneath the pointer.
8
- //
9
- // Compose with: `useActionBarMode` hook (or your own state) to swap
10
- // the items registered via useActionBarItems when the handle fires
11
- // onClick. Gate visibility on a permission bit (e.g.
12
- // `document.capabilities.manage`) — passing `visible={false}` hides
13
- // the handle entirely so non-privileged users never see the
14
- // affordance.
15
- //
16
- // Visual: fixed positioning centred at the bottom of the viewport,
17
- // stacked ABOVE the ActionBar (which lives at `bottom: 24px +
18
- // tabbar-height`). The handle sits ~6px above the ActionBar's top
19
- // edge at rest and animates upward on hover.
20
- //
21
- // Positioning contract: consumers may pass `bottom` to override the
22
- // default 66px (24 gutter + 48 bar height - 6 overlap). Otherwise
23
- // this matches the default ActionBar's centred bottom placement.
24
- //
25
- // Forwards ref to the underlying <button>.
26
- export interface ActionBarModeHandleProps {
27
- /**
28
- * Called when the handle is clicked / activated. Consumer should
29
- * flip the active mode state here (which drives
30
- * useActionBarItems(sourceKey, items) with a different sourceKey).
31
- */
32
- onClick: () => void;
33
- /**
34
- * Human-readable label describing what the click will do
35
- * ("Switch to manage mode"). Used as both aria-label and the
36
- * hover tooltip.
37
- */
38
- label: string;
39
- /**
40
- * When false, the handle renders nothing. Consumers wire this to
41
- * their capability check (e.g. `capabilities.manage === true`)
42
- * so principals without the permission never see the affordance.
43
- * Default: true.
44
- */
45
- visible?: boolean | undefined;
46
- /**
47
- * Optional bottom offset in px (default: matches the ActionBar's
48
- * default centred position — 66px = 24 gutter + 48 bar height - 6
49
- * overlap, plus the same tabbar + safe-area terms). Override when
50
- * the ActionBar is docked non-default.
51
- */
52
- bottom?: number | string | undefined;
53
- /**
54
- * Extra className appended to the handle's root class. Handy for
55
- * tests + one-off styling. Default className: `ck-actionbar-handle`.
56
- */
57
- className?: string | undefined;
58
- /**
59
- * Extra inline styles merged onto the root. Only use for one-off
60
- * z-index / offset overrides; the visual language should stay
61
- * consistent across surfaces.
62
- */
63
- style?: CSSProperties | undefined;
64
- }
65
-
66
- const HANDLE_REST_WIDTH = 44;
67
- const HANDLE_REST_HEIGHT = 6;
68
- const HANDLE_HOVER_WIDTH = 68;
69
- const HANDLE_HOVER_HEIGHT = 24;
70
- // Overlap the ActionBar's top edge by 4px so the handle looks
71
- // tethered to the bar rather than floating above it.
72
- const HANDLE_BOTTOM_DEFAULT = "calc(24px + 48px - 4px + var(--ck-tabbar-height, 0px) + env(safe-area-inset-bottom, 0px))";
73
-
74
- export const ActionBarModeHandle = forwardRef<HTMLButtonElement, ActionBarModeHandleProps>(
75
- function ActionBarModeHandle(
76
- {
77
- onClick,
78
- label,
79
- visible = true,
80
- bottom,
81
- className,
82
- style,
83
- },
84
- ref,
85
- ) {
86
- if (!visible) return null;
87
- const resolvedBottom =
88
- typeof bottom === "number"
89
- ? `${bottom}px`
90
- : typeof bottom === "string"
91
- ? bottom
92
- : HANDLE_BOTTOM_DEFAULT;
93
- return (
94
- <button
95
- ref={ref}
96
- type="button"
97
- onClick={onClick}
98
- aria-label={label}
99
- title={label}
100
- className={`ck-actionbar-handle${className ? ` ${className}` : ""}`}
101
- style={{
102
- position: "fixed",
103
- left: "50%",
104
- bottom: resolvedBottom,
105
- transform: "translateX(-50%)",
106
- // Rest state: narrow slim strip at the top edge of the bar.
107
- width: `var(--ck-actionbar-handle-w, ${HANDLE_REST_WIDTH}px)`,
108
- height: `var(--ck-actionbar-handle-h, ${HANDLE_REST_HEIGHT}px)`,
109
- padding: 0,
110
- margin: 0,
111
- border: 0,
112
- borderRadius: 999,
113
- background: "var(--ck-accent, #4f46e5)",
114
- color: "var(--ck-accent-fg, #fff)",
115
- fontSize: 11,
116
- fontWeight: 500,
117
- letterSpacing: 0.2,
118
- lineHeight: 1,
119
- overflow: "hidden",
120
- whiteSpace: "nowrap",
121
- cursor: "pointer",
122
- // Sit above the ActionBar (z-index 80 in ActionBar.tsx).
123
- zIndex: 85,
124
- boxShadow: "0 2px 8px rgba(0,0,0,0.14)",
125
- transition:
126
- "width 220ms cubic-bezier(0.34, 1.56, 0.64, 1)," +
127
- "height 220ms cubic-bezier(0.34, 1.56, 0.64, 1)," +
128
- "box-shadow 220ms ease-out",
129
- display: "inline-flex",
130
- alignItems: "center",
131
- justifyContent: "center",
132
- ...style,
133
- }}
134
- // Inline hover/focus expansion — pure CSS on the same element
135
- // via CSSProperties gives us the animation without a global
136
- // stylesheet dependency in the kit.
137
- onMouseEnter={(e) => {
138
- const el = e.currentTarget;
139
- el.style.setProperty("--ck-actionbar-handle-w", `${HANDLE_HOVER_WIDTH}px`);
140
- el.style.setProperty("--ck-actionbar-handle-h", `${HANDLE_HOVER_HEIGHT}px`);
141
- el.style.boxShadow = "0 4px 14px rgba(0,0,0,0.22)";
142
- }}
143
- onMouseLeave={(e) => {
144
- const el = e.currentTarget;
145
- el.style.setProperty("--ck-actionbar-handle-w", `${HANDLE_REST_WIDTH}px`);
146
- el.style.setProperty("--ck-actionbar-handle-h", `${HANDLE_REST_HEIGHT}px`);
147
- el.style.boxShadow = "0 2px 8px rgba(0,0,0,0.14)";
148
- }}
149
- onFocus={(e) => {
150
- const el = e.currentTarget;
151
- el.style.setProperty("--ck-actionbar-handle-w", `${HANDLE_HOVER_WIDTH}px`);
152
- el.style.setProperty("--ck-actionbar-handle-h", `${HANDLE_HOVER_HEIGHT}px`);
153
- el.style.boxShadow = "0 4px 14px rgba(0,0,0,0.22)";
154
- }}
155
- onBlur={(e) => {
156
- const el = e.currentTarget;
157
- el.style.setProperty("--ck-actionbar-handle-w", `${HANDLE_REST_WIDTH}px`);
158
- el.style.setProperty("--ck-actionbar-handle-h", `${HANDLE_REST_HEIGHT}px`);
159
- el.style.boxShadow = "0 2px 8px rgba(0,0,0,0.14)";
160
- }}
161
- >
162
- {/* Only visible when expanded — CSS `overflow: hidden` on the
163
- button clips it at rest. Kept as visible text (not aria-
164
- hidden) so screen readers can still hear it via title. */}
165
- <span aria-hidden style={{ opacity: 0.95 }}>
166
- {label}
167
- </span>
168
- </button>
169
- );
170
- },
171
- );
@@ -1,91 +0,0 @@
1
- import { useCallback, useMemo, useState, type ReactNode } from "react";
2
- import { ActionBarContext } from "./actionbar-context";
3
- import type { ActionBarItem, ActionBarCategories, ActionBarStatusDot, ActionBarPanelMeta } from "./types";
4
-
5
- // Provider for the action-bar registry + expansion state +
6
- // category catalog. Mount once near the app root, inside the
7
- // router so consumer hooks (useNavigate etc.) work but outside
8
- // any per-route boundary.
9
-
10
- export interface ActionBarProviderProps {
11
- children: ReactNode;
12
- // Declare the categories your app uses, keyed by category id.
13
- // Items with `category: "create"` look up { label, icon } here
14
- // for the group head. If you don't declare a category that an
15
- // item references, that item still renders — it just falls
16
- // back to its own label as the group head.
17
- categories?: ActionBarCategories;
18
- }
19
-
20
- export function ActionBarProvider({
21
- children,
22
- categories = {},
23
- }: ActionBarProviderProps) {
24
- const [sources, setSources] = useState<Record<string, ActionBarItem[]>>({});
25
- const [expandedKey, setExpandedKey] = useState<string | null>(null);
26
- const [statusDot, setStatusDot] = useState<ActionBarStatusDot | null>(null);
27
- const [panel, setPanel] = useState<ActionBarPanelMeta | null>(null);
28
- const [panelHost, setPanelHost] = useState<HTMLDivElement | null>(null);
29
- const [toastActive, setToastActive] = useState(false);
30
- const [toastHost, setToastHost] = useState<HTMLDivElement | null>(null);
31
-
32
- const register = useCallback((sourceKey: string, items: ActionBarItem[]) => {
33
- setSources((prev) => {
34
- const cur = prev[sourceKey];
35
- if (cur && cur.length === items.length && cur.every((it, i) => it === items[i])) {
36
- return prev;
37
- }
38
- return { ...prev, [sourceKey]: items };
39
- });
40
- }, []);
41
-
42
- const unregister = useCallback((sourceKey: string) => {
43
- setSources((prev) => {
44
- if (!(sourceKey in prev)) return prev;
45
- const next = { ...prev };
46
- delete next[sourceKey];
47
- return next;
48
- });
49
- }, []);
50
-
51
- // `__builtins__` pinned to front. Convention used by consumers
52
- // that want a stable "Create" group regardless of which page
53
- // registered first.
54
- const items = useMemo(() => {
55
- const all: ActionBarItem[] = [];
56
- if (sources["__builtins__"]) all.push(...sources["__builtins__"]);
57
- for (const [key, list] of Object.entries(sources)) {
58
- if (key === "__builtins__") continue;
59
- all.push(...list);
60
- }
61
- return all;
62
- }, [sources]);
63
-
64
- const value = useMemo(
65
- () => ({
66
- register,
67
- unregister,
68
- items,
69
- categories,
70
- expandedKey,
71
- setExpandedKey,
72
- statusDot,
73
- setStatusDot,
74
- panel,
75
- setPanel,
76
- panelHost,
77
- setPanelHost,
78
- toastActive,
79
- setToastActive,
80
- toastHost,
81
- setToastHost,
82
- }),
83
- [register, unregister, items, categories, expandedKey, statusDot, panel, panelHost, toastActive, toastHost],
84
- );
85
-
86
- return (
87
- <ActionBarContext.Provider value={value}>
88
- {children}
89
- </ActionBarContext.Provider>
90
- );
91
- }
@@ -1,45 +0,0 @@
1
- import { createContext } from "react";
2
- import type { ActionBarItem, ActionBarCategories, ActionBarStatusDot, ActionBarPanelMeta } from "./types";
3
-
4
- // Registry + expansion + category catalog + status-dot slot. Items are
5
- // pushed by pages through useActionBarItems(); the status dot is
6
- // pushed by a separate hook (useActionBarStatusDot) because it's
7
- // bar-intrinsic chrome, not page-level content. expandedKey tracks
8
- // which group is currently open (one at a time).
9
-
10
- export interface ActionBarContextValue {
11
- register: (sourceKey: string, items: ActionBarItem[]) => void;
12
- unregister: (sourceKey: string) => void;
13
- // Flat list assembled from all sources, with `__builtins__`
14
- // pinned to the front (so consumer-declared "Create" items
15
- // always land before page contributions).
16
- items: ActionBarItem[];
17
- // Static category catalog from the provider — `{ create: {label, icon}, ... }`.
18
- categories: ActionBarCategories;
19
- // Which group head is currently expanded. null = none.
20
- expandedKey: string | null;
21
- setExpandedKey: (key: string | null) => void;
22
- // Bar-intrinsic right-edge status indicator. null when no consumer
23
- // has registered one. Last call to setStatusDot wins.
24
- statusDot: ActionBarStatusDot | null;
25
- setStatusDot: (dot: ActionBarStatusDot | null) => void;
26
- // Bar-intrinsic popover slot (design#13). useActionBarPanel
27
- // registers META only (open/onOpenChange/width/ariaLabel); the bar
28
- // renders an empty themed container and publishes its DOM node via
29
- // panelHost, into which the hook PORTALS the consumer's content.
30
- // Content never enters context state — a fresh JSX identity per
31
- // consumer render would otherwise re-render-loop through setPanel.
32
- panel: ActionBarPanelMeta | null;
33
- setPanel: (panel: ActionBarPanelMeta | null) => void;
34
- panelHost: HTMLDivElement | null;
35
- setPanelHost: (el: HTMLDivElement | null) => void;
36
- // Transient bubble above the bar (completion toasts etc.) — same
37
- // portal-host pattern: consumers register only "active", content
38
- // portals into toastHost.
39
- toastActive: boolean;
40
- setToastActive: (active: boolean) => void;
41
- toastHost: HTMLDivElement | null;
42
- setToastHost: (el: HTMLDivElement | null) => void;
43
- }
44
-
45
- export const ActionBarContext = createContext<ActionBarContextValue | null>(null);
@@ -1,28 +0,0 @@
1
- export { ActionBarProvider } from "./ActionBarProvider";
2
- export type { ActionBarProviderProps } from "./ActionBarProvider";
3
- export { ActionBar } from "./ActionBar";
4
- export type { ActionBarProps } from "./ActionBar";
5
- export { ActionBarButton } from "./ActionBarButton";
6
- export type { ActionBarButtonProps } from "./ActionBarButton";
7
- export { useActionBarItems, useActionBarItemsContext } from "./useActionBarItems";
8
- export { useActionBarStatusDot } from "./useActionBarStatusDot";
9
- export { useActionBarPanel } from "./useActionBarPanel";
10
- export { useActionBarToast } from "./useActionBarToast";
11
- export { ActionBarModeHandle } from "./ActionBarModeHandle";
12
- export type { ActionBarModeHandleProps } from "./ActionBarModeHandle";
13
- export { useActionBarMode } from "./useActionBarMode";
14
- export type {
15
- ActionBarModeConfig,
16
- UseActionBarModeOptions,
17
- UseActionBarModeResult,
18
- } from "./useActionBarMode";
19
- export type {
20
- ActionBarItem,
21
- ActionBarItemVariant,
22
- ActionBarItemSlot,
23
- ActionBarStatusDot,
24
- ActionBarPanel,
25
- ActionBarPanelMeta,
26
- ActionBarCategory,
27
- ActionBarCategories,
28
- } from "./types";