@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.
- package/CHANGELOG.md +577 -0
- package/NOTICE +11 -0
- package/README.md +52 -0
- package/package.json +33 -7
- package/src/fonts.css +20 -0
- package/src/index.ts +8 -25
- package/src/lib/cn.ts +23 -6
- package/src/styles.css +17 -0
- package/src/theme.css +132 -0
- package/src/tokens/colors.css +112 -0
- package/src/tokens/compat.css +36 -0
- package/src/tokens/elevation.css +14 -0
- package/src/tokens/motion.css +42 -0
- package/src/tokens/spacing.css +55 -0
- package/src/tokens/typography-cjk.css +42 -0
- package/src/tokens/typography.css +81 -0
- package/src/actionbar/ActionBar.tsx +0 -837
- package/src/actionbar/ActionBarButton.tsx +0 -111
- package/src/actionbar/ActionBarMenuGroup.tsx +0 -116
- package/src/actionbar/ActionBarModeHandle.tsx +0 -171
- package/src/actionbar/ActionBarProvider.tsx +0 -91
- package/src/actionbar/actionbar-context.ts +0 -45
- package/src/actionbar/index.ts +0 -28
- package/src/actionbar/types.ts +0 -147
- package/src/actionbar/useActionBarItems.ts +0 -60
- package/src/actionbar/useActionBarMode.ts +0 -144
- package/src/actionbar/useActionBarPanel.ts +0 -56
- package/src/actionbar/useActionBarStatusDot.ts +0 -49
- package/src/actionbar/useActionBarToast.ts +0 -33
- package/src/ambient/AmbientBackdrop.tsx +0 -74
- package/src/ambient/CommandInput.tsx +0 -107
- package/src/ambient/SuperbarStrip.tsx +0 -36
- package/src/ambient/index.ts +0 -6
- package/src/bento/BentoCell.tsx +0 -66
- package/src/bento/BentoGrid.tsx +0 -42
- package/src/bento/index.ts +0 -2
- package/src/command/CommandPalette.tsx +0 -329
- package/src/command/CommandProvider.tsx +0 -57
- package/src/command/command-context.ts +0 -12
- package/src/command/index.ts +0 -6
- package/src/command/rank.ts +0 -50
- package/src/command/types.ts +0 -37
- package/src/command/useCommandSource.ts +0 -37
- package/src/dialogs/DialogsProvider.tsx +0 -216
- package/src/dialogs/Modal.tsx +0 -260
- package/src/dialogs/Toast.tsx +0 -85
- package/src/dialogs/dialogs-context.ts +0 -6
- package/src/dialogs/index.ts +0 -10
- package/src/dialogs/types.ts +0 -37
- package/src/dialogs/useDialogs.ts +0 -8
- package/src/editorial/EditorialSpotlight.tsx +0 -63
- package/src/editorial/Folio.tsx +0 -52
- package/src/editorial/PlateMarker.tsx +0 -33
- package/src/editorial/RomanSection.tsx +0 -65
- package/src/editorial/RunningMarginalia.tsx +0 -65
- package/src/editorial/index.ts +0 -10
- package/src/hooks/index.ts +0 -5
- package/src/hooks/useKeyboardHotkey.ts +0 -93
- package/src/hooks/useReducedMotion.ts +0 -20
- package/src/hooks/useViewport.ts +0 -61
- package/src/layout/Breadcrumb.tsx +0 -74
- package/src/layout/LeftNavRail.tsx +0 -139
- package/src/layout/MobileTabBar.tsx +0 -101
- package/src/layout/NavItem.tsx +0 -128
- package/src/layout/NavSearchTrigger.tsx +0 -88
- package/src/layout/NavSection.tsx +0 -40
- package/src/layout/RightSidebarPanel.tsx +0 -111
- package/src/layout/Shell.tsx +0 -91
- package/src/layout/SidePanel.tsx +0 -316
- package/src/layout/StickyBanner.tsx +0 -83
- package/src/layout/Topbar.tsx +0 -68
- package/src/layout/index.ts +0 -24
- package/src/layout/useNavRailState.ts +0 -69
- package/src/lib/time-utils.ts +0 -44
- package/src/neobrutalism/Marquee.tsx +0 -81
- package/src/neobrutalism/Sticker.tsx +0 -71
- package/src/neobrutalism/index.ts +0 -4
- package/src/primitives/Avatar.tsx +0 -53
- package/src/primitives/Button.tsx +0 -67
- package/src/primitives/Card.tsx +0 -41
- package/src/primitives/Checkbox.tsx +0 -87
- package/src/primitives/Chip.tsx +0 -177
- package/src/primitives/Combobox.tsx +0 -475
- package/src/primitives/CopyField.tsx +0 -139
- package/src/primitives/CountBadge.tsx +0 -50
- package/src/primitives/Input.tsx +0 -161
- package/src/primitives/Kbd.tsx +0 -45
- package/src/primitives/MentionCombobox.tsx +0 -612
- package/src/primitives/MultiCombobox.tsx +0 -179
- package/src/primitives/PageHeader.tsx +0 -77
- package/src/primitives/Radio.tsx +0 -131
- package/src/primitives/SegmentedControl.tsx +0 -135
- package/src/primitives/Select.tsx +0 -779
- package/src/primitives/SignaturePad.tsx +0 -461
- package/src/primitives/Tag.tsx +0 -56
- package/src/primitives/Textarea.tsx +0 -62
- package/src/primitives/ToggleSwitch.tsx +0 -79
- package/src/primitives/Tooltip.tsx +0 -213
- package/src/primitives/index.ts +0 -48
- package/src/pwa/InstallPromptBanner.tsx +0 -132
- package/src/pwa/index.ts +0 -4
- package/src/pwa/manifest.template.json +0 -20
- package/src/pwa/registerSW.ts +0 -55
- package/src/riso/Halftone.tsx +0 -85
- package/src/riso/Misregister.tsx +0 -63
- package/src/riso/RisoStamp.tsx +0 -76
- package/src/riso/index.ts +0 -3
- package/src/sketch/HandUnderline.tsx +0 -53
- package/src/sketch/RoughArrow.tsx +0 -91
- package/src/sketch/RoughBox.tsx +0 -73
- package/src/sketch/StickyNote.tsx +0 -56
- package/src/sketch/index.ts +0 -4
- package/src/styles/base.css +0 -186
- package/src/styles/chrome-ambient.css +0 -235
- package/src/styles/chrome-bento.css +0 -192
- package/src/styles/chrome-editorial.css +0 -168
- package/src/styles/chrome-neobrutalism.css +0 -326
- package/src/styles/chrome-riso.css +0 -339
- package/src/styles/chrome-sketch.css +0 -362
- package/src/styles/chrome-swiss.css +0 -265
- package/src/styles/chrome-terminal.css +0 -272
- package/src/styles/fonts.css +0 -1034
- package/src/styles/index.css +0 -306
- package/src/styles/tokens.css +0 -941
- package/src/terminal/AsciiBox.tsx +0 -65
- package/src/terminal/BrailleSpinner.tsx +0 -46
- package/src/terminal/index.ts +0 -4
- package/src/theme/ThemeProvider.tsx +0 -99
- package/src/theme/index.ts +0 -6
- package/src/theme/inline-script.ts +0 -40
- package/src/theme/theme-context.ts +0 -7
- package/src/theme/types.ts +0 -39
- 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);
|
package/src/actionbar/index.ts
DELETED
|
@@ -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";
|