@cosxai/ui 0.1.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/package.json +38 -0
- package/src/actionbar/ActionBar.tsx +436 -0
- package/src/actionbar/ActionBarButton.tsx +110 -0
- package/src/actionbar/ActionBarMenuGroup.tsx +106 -0
- package/src/actionbar/ActionBarProvider.tsx +76 -0
- package/src/actionbar/actionbar-context.ts +23 -0
- package/src/actionbar/index.ts +13 -0
- package/src/actionbar/types.ts +50 -0
- package/src/actionbar/useActionBarItems.ts +47 -0
- package/src/ambient/AmbientBackdrop.tsx +74 -0
- package/src/ambient/CommandInput.tsx +107 -0
- package/src/ambient/SuperbarStrip.tsx +36 -0
- package/src/ambient/index.ts +6 -0
- package/src/bento/BentoCell.tsx +66 -0
- package/src/bento/BentoGrid.tsx +42 -0
- package/src/bento/index.ts +2 -0
- package/src/command/CommandPalette.tsx +277 -0
- package/src/command/CommandProvider.tsx +57 -0
- package/src/command/command-context.ts +12 -0
- package/src/command/index.ts +6 -0
- package/src/command/rank.ts +45 -0
- package/src/command/types.ts +26 -0
- package/src/command/useCommandSource.ts +37 -0
- package/src/dialogs/DialogsProvider.tsx +216 -0
- package/src/dialogs/Modal.tsx +204 -0
- package/src/dialogs/Toast.tsx +85 -0
- package/src/dialogs/dialogs-context.ts +6 -0
- package/src/dialogs/index.ts +10 -0
- package/src/dialogs/types.ts +37 -0
- package/src/dialogs/useDialogs.ts +8 -0
- package/src/editorial/EditorialSpotlight.tsx +63 -0
- package/src/editorial/Folio.tsx +52 -0
- package/src/editorial/PlateMarker.tsx +33 -0
- package/src/editorial/RomanSection.tsx +65 -0
- package/src/editorial/RunningMarginalia.tsx +65 -0
- package/src/editorial/index.ts +10 -0
- package/src/frutiger/GlossyOrb.tsx +79 -0
- package/src/frutiger/SkyBackdrop.tsx +114 -0
- package/src/frutiger/index.ts +2 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useKeyboardHotkey.ts +80 -0
- package/src/hooks/useReducedMotion.ts +20 -0
- package/src/hooks/useViewport.ts +61 -0
- package/src/index.ts +26 -0
- package/src/layout/Breadcrumb.tsx +74 -0
- package/src/layout/LeftNavRail.tsx +126 -0
- package/src/layout/MobileTabBar.tsx +101 -0
- package/src/layout/NavItem.tsx +128 -0
- package/src/layout/NavSearchTrigger.tsx +88 -0
- package/src/layout/NavSection.tsx +40 -0
- package/src/layout/RightSidebarPanel.tsx +111 -0
- package/src/layout/Shell.tsx +91 -0
- package/src/layout/StickyBanner.tsx +83 -0
- package/src/layout/Topbar.tsx +68 -0
- package/src/layout/index.ts +22 -0
- package/src/layout/useNavRailState.ts +69 -0
- package/src/lib/cn.ts +7 -0
- package/src/lib/time-utils.ts +44 -0
- package/src/neobrutalism/Marquee.tsx +81 -0
- package/src/neobrutalism/Sticker.tsx +71 -0
- package/src/neobrutalism/index.ts +4 -0
- package/src/primitives/Avatar.tsx +53 -0
- package/src/primitives/Button.tsx +30 -0
- package/src/primitives/Card.tsx +41 -0
- package/src/primitives/Checkbox.tsx +78 -0
- package/src/primitives/CountBadge.tsx +50 -0
- package/src/primitives/Input.tsx +71 -0
- package/src/primitives/Kbd.tsx +45 -0
- package/src/primitives/PageHeader.tsx +77 -0
- package/src/primitives/Tag.tsx +56 -0
- package/src/primitives/Textarea.tsx +62 -0
- package/src/primitives/ToggleSwitch.tsx +79 -0
- package/src/primitives/Tooltip.tsx +171 -0
- package/src/primitives/index.ts +24 -0
- package/src/pwa/InstallPromptBanner.tsx +132 -0
- package/src/pwa/index.ts +4 -0
- package/src/pwa/manifest.template.json +20 -0
- package/src/pwa/registerSW.ts +55 -0
- package/src/riso/Halftone.tsx +85 -0
- package/src/riso/Misregister.tsx +63 -0
- package/src/riso/RisoStamp.tsx +76 -0
- package/src/riso/index.ts +3 -0
- package/src/sketch/HandUnderline.tsx +53 -0
- package/src/sketch/RoughArrow.tsx +91 -0
- package/src/sketch/RoughBox.tsx +73 -0
- package/src/sketch/StickyNote.tsx +56 -0
- package/src/sketch/index.ts +4 -0
- package/src/styles/base.css +80 -0
- package/src/styles/chrome-ambient.css +222 -0
- package/src/styles/chrome-bento.css +184 -0
- package/src/styles/chrome-editorial.css +145 -0
- package/src/styles/chrome-frutiger.css +364 -0
- package/src/styles/chrome-neobrutalism.css +315 -0
- package/src/styles/chrome-riso.css +328 -0
- package/src/styles/chrome-sketch.css +351 -0
- package/src/styles/chrome-swiss.css +232 -0
- package/src/styles/chrome-terminal.css +235 -0
- package/src/styles/fonts.css +22 -0
- package/src/styles/index.css +198 -0
- package/src/styles/tokens.css +976 -0
- package/src/terminal/AsciiBox.tsx +65 -0
- package/src/terminal/BrailleSpinner.tsx +46 -0
- package/src/terminal/index.ts +4 -0
- package/src/theme/ThemeProvider.tsx +93 -0
- package/src/theme/index.ts +5 -0
- package/src/theme/inline-script.ts +36 -0
- package/src/theme/theme-context.ts +7 -0
- package/src/theme/types.ts +22 -0
- package/src/theme/useTheme.ts +8 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState, type ReactNode } from "react";
|
|
2
|
+
import { ActionBarContext } from "./actionbar-context";
|
|
3
|
+
import type { ActionBarItem, ActionBarCategories } 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
|
+
|
|
27
|
+
const register = useCallback((sourceKey: string, items: ActionBarItem[]) => {
|
|
28
|
+
setSources((prev) => {
|
|
29
|
+
const cur = prev[sourceKey];
|
|
30
|
+
if (cur && cur.length === items.length && cur.every((it, i) => it === items[i])) {
|
|
31
|
+
return prev;
|
|
32
|
+
}
|
|
33
|
+
return { ...prev, [sourceKey]: items };
|
|
34
|
+
});
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const unregister = useCallback((sourceKey: string) => {
|
|
38
|
+
setSources((prev) => {
|
|
39
|
+
if (!(sourceKey in prev)) return prev;
|
|
40
|
+
const next = { ...prev };
|
|
41
|
+
delete next[sourceKey];
|
|
42
|
+
return next;
|
|
43
|
+
});
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
// `__builtins__` pinned to front. Convention used by consumers
|
|
47
|
+
// that want a stable "Create" group regardless of which page
|
|
48
|
+
// registered first.
|
|
49
|
+
const items = useMemo(() => {
|
|
50
|
+
const all: ActionBarItem[] = [];
|
|
51
|
+
if (sources["__builtins__"]) all.push(...sources["__builtins__"]);
|
|
52
|
+
for (const [key, list] of Object.entries(sources)) {
|
|
53
|
+
if (key === "__builtins__") continue;
|
|
54
|
+
all.push(...list);
|
|
55
|
+
}
|
|
56
|
+
return all;
|
|
57
|
+
}, [sources]);
|
|
58
|
+
|
|
59
|
+
const value = useMemo(
|
|
60
|
+
() => ({
|
|
61
|
+
register,
|
|
62
|
+
unregister,
|
|
63
|
+
items,
|
|
64
|
+
categories,
|
|
65
|
+
expandedKey,
|
|
66
|
+
setExpandedKey,
|
|
67
|
+
}),
|
|
68
|
+
[register, unregister, items, categories, expandedKey],
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<ActionBarContext.Provider value={value}>
|
|
73
|
+
{children}
|
|
74
|
+
</ActionBarContext.Provider>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
import type { ActionBarItem, ActionBarCategories } from "./types";
|
|
3
|
+
|
|
4
|
+
// Registry + expansion + category catalog. Items are pushed by
|
|
5
|
+
// pages through useActionBarItems(); categories are declared at
|
|
6
|
+
// the provider level (passed as a prop). expandedKey tracks which
|
|
7
|
+
// group is currently open (one at a time).
|
|
8
|
+
|
|
9
|
+
export interface ActionBarContextValue {
|
|
10
|
+
register: (sourceKey: string, items: ActionBarItem[]) => void;
|
|
11
|
+
unregister: (sourceKey: string) => void;
|
|
12
|
+
// Flat list assembled from all sources, with `__builtins__`
|
|
13
|
+
// pinned to the front (so consumer-declared "Create" items
|
|
14
|
+
// always land before page contributions).
|
|
15
|
+
items: ActionBarItem[];
|
|
16
|
+
// Static category catalog from the provider — `{ create: {label, icon}, ... }`.
|
|
17
|
+
categories: ActionBarCategories;
|
|
18
|
+
// Which group head is currently expanded. null = none.
|
|
19
|
+
expandedKey: string | null;
|
|
20
|
+
setExpandedKey: (key: string | null) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const ActionBarContext = createContext<ActionBarContextValue | null>(null);
|
|
@@ -0,0 +1,13 @@
|
|
|
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 type {
|
|
9
|
+
ActionBarItem,
|
|
10
|
+
ActionBarItemVariant,
|
|
11
|
+
ActionBarCategory,
|
|
12
|
+
ActionBarCategories,
|
|
13
|
+
} from "./types";
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
export interface ActionBarItem {
|
|
11
|
+
// React key for the rendered button. Stable across rerenders.
|
|
12
|
+
key: string;
|
|
13
|
+
label: string;
|
|
14
|
+
icon?: ReactNode;
|
|
15
|
+
onClick: () => void;
|
|
16
|
+
// State indicator — active items render with the accent tone.
|
|
17
|
+
active?: boolean;
|
|
18
|
+
// Tooltip / aria-label. Falls back to label.
|
|
19
|
+
title?: string;
|
|
20
|
+
// Disable without removing.
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
// Group key — items sharing a category collapse into one
|
|
23
|
+
// expandable group head when 2+ are present. Single items
|
|
24
|
+
// render inline. Categories are declared at the provider level.
|
|
25
|
+
category?: string;
|
|
26
|
+
// Visual mode. Default ghost.
|
|
27
|
+
variant?: ActionBarItemVariant;
|
|
28
|
+
// Optional kbd hint shown beside the label (just visual — the
|
|
29
|
+
// shortcut is wired by the caller via useKeyboardHotkey).
|
|
30
|
+
hint?: string;
|
|
31
|
+
// When inside an expanded group, clicking this item normally
|
|
32
|
+
// collapses the group afterward. Set true to keep it open
|
|
33
|
+
// (e.g. for items whose post-click state changes the label
|
|
34
|
+
// and should stay visible).
|
|
35
|
+
keepGroupOpenOnClick?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Category definition — declared at the provider level. Drives
|
|
39
|
+
// the group head's label + icon.
|
|
40
|
+
export interface ActionBarCategory {
|
|
41
|
+
label: string;
|
|
42
|
+
icon?: ReactNode;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type ActionBarCategories = Record<string, ActionBarCategory>;
|
|
46
|
+
|
|
47
|
+
export interface ActionBarSource {
|
|
48
|
+
sourceKey: string;
|
|
49
|
+
items: ActionBarItem[];
|
|
50
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useContext, useEffect, useMemo } 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
|
+
// `items` is the live array — pass a useMemo'd array to avoid
|
|
9
|
+
// re-registration thrash. The provider's register() does shallow
|
|
10
|
+
// equality, so passing a NEW array with identical refs is fine,
|
|
11
|
+
// but a NEW array with all-new item objects (from inline JSX
|
|
12
|
+
// blocks) will re-register every render.
|
|
13
|
+
//
|
|
14
|
+
// **Important**: the useEffect deps below intentionally do NOT
|
|
15
|
+
// include the ctx object itself — only its stable function refs
|
|
16
|
+
// (register/unregister are useCallback'd in the provider).
|
|
17
|
+
// Including ctx would cause an infinite render loop: register
|
|
18
|
+
// mutates provider state → provider's value memo recomputes →
|
|
19
|
+
// ctx ref changes → effect re-runs → cleanup unregisters →
|
|
20
|
+
// state mutates again → ctx changes → loop. Manifests as the
|
|
21
|
+
// main thread saturating and event clicks never reaching the
|
|
22
|
+
// router. Same fix applies in useCommandSource.
|
|
23
|
+
|
|
24
|
+
export function useActionBarItems(sourceKey: string, items: ActionBarItem[]) {
|
|
25
|
+
const ctx = useContext(ActionBarContext);
|
|
26
|
+
if (!ctx) {
|
|
27
|
+
throw new Error("useActionBarItems must be used within <ActionBarProvider>");
|
|
28
|
+
}
|
|
29
|
+
const { register, unregister } = ctx;
|
|
30
|
+
const memo = useMemo(
|
|
31
|
+
() => items,
|
|
32
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
33
|
+
[items.length, items.map((i) => i.key).join("|")],
|
|
34
|
+
);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
register(sourceKey, memo);
|
|
37
|
+
return () => unregister(sourceKey);
|
|
38
|
+
}, [register, unregister, sourceKey, memo]);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function useActionBarItemsContext() {
|
|
42
|
+
const ctx = useContext(ActionBarContext);
|
|
43
|
+
if (!ctx) {
|
|
44
|
+
throw new Error("Action bar context not found");
|
|
45
|
+
}
|
|
46
|
+
return ctx;
|
|
47
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
|
|
3
|
+
// Thin gradient strip — the Superbar pattern reusable as a
|
|
4
|
+
// horizontal divider, a tab underline, or a progress bar fill.
|
|
5
|
+
// 1-2 px tall by default; override via `thickness`.
|
|
6
|
+
|
|
7
|
+
export interface SuperbarStripProps {
|
|
8
|
+
// px height. Default 1 (hairline divider).
|
|
9
|
+
thickness?: number;
|
|
10
|
+
// CSS width — defaults to 100%.
|
|
11
|
+
width?: string | number;
|
|
12
|
+
className?: string;
|
|
13
|
+
style?: CSSProperties;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function SuperbarStrip({
|
|
17
|
+
thickness = 1,
|
|
18
|
+
width = "100%",
|
|
19
|
+
className,
|
|
20
|
+
style,
|
|
21
|
+
}: SuperbarStripProps) {
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
aria-hidden
|
|
25
|
+
className={className}
|
|
26
|
+
style={{
|
|
27
|
+
width,
|
|
28
|
+
height: thickness,
|
|
29
|
+
background: "var(--ck-superbar)",
|
|
30
|
+
borderRadius: thickness >= 2 ? thickness / 2 : 0,
|
|
31
|
+
opacity: 0.85,
|
|
32
|
+
...style,
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { CommandInput } from "./CommandInput";
|
|
2
|
+
export type { CommandInputProps } from "./CommandInput";
|
|
3
|
+
export { SuperbarStrip } from "./SuperbarStrip";
|
|
4
|
+
export type { SuperbarStripProps } from "./SuperbarStrip";
|
|
5
|
+
export { AmbientBackdrop } from "./AmbientBackdrop";
|
|
6
|
+
export type { AmbientBackdropProps } from "./AmbientBackdrop";
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { CSSProperties, MouseEventHandler, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
// One cell inside a <BentoGrid>. Spans configurable column + row
|
|
4
|
+
// counts; surface styling matches the chrome (22px radius +
|
|
5
|
+
// soft multi-layer shadow under bento). `interactive` enables the
|
|
6
|
+
// hover-lift signature animation defined in chrome-bento.css.
|
|
7
|
+
|
|
8
|
+
export interface BentoCellProps {
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
colSpan?: number;
|
|
11
|
+
rowSpan?: number;
|
|
12
|
+
// Override the cell's background. Useful for media + gradient
|
|
13
|
+
// cells. Defaults to var(--ck-bg-surface).
|
|
14
|
+
background?: string;
|
|
15
|
+
// Override text color (for high-contrast gradient cells).
|
|
16
|
+
color?: string;
|
|
17
|
+
// Padding inside the cell. Defaults to 20px.
|
|
18
|
+
padding?: number | string;
|
|
19
|
+
// Apply the hover-lift effect. Defaults to false.
|
|
20
|
+
interactive?: boolean;
|
|
21
|
+
onClick?: MouseEventHandler<HTMLDivElement>;
|
|
22
|
+
className?: string;
|
|
23
|
+
style?: CSSProperties;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function BentoCell({
|
|
27
|
+
children,
|
|
28
|
+
colSpan = 1,
|
|
29
|
+
rowSpan = 1,
|
|
30
|
+
background,
|
|
31
|
+
color,
|
|
32
|
+
padding = 20,
|
|
33
|
+
interactive = false,
|
|
34
|
+
onClick,
|
|
35
|
+
className,
|
|
36
|
+
style,
|
|
37
|
+
}: BentoCellProps) {
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
data-ck-bento-cell=""
|
|
41
|
+
data-interactive={interactive ? "true" : undefined}
|
|
42
|
+
onClick={onClick}
|
|
43
|
+
className={className}
|
|
44
|
+
role={onClick ? "button" : undefined}
|
|
45
|
+
tabIndex={onClick ? 0 : undefined}
|
|
46
|
+
style={{
|
|
47
|
+
gridColumn: `span ${colSpan} / span ${colSpan}`,
|
|
48
|
+
gridRow: `span ${rowSpan} / span ${rowSpan}`,
|
|
49
|
+
background: background ?? "var(--ck-bg-surface)",
|
|
50
|
+
color,
|
|
51
|
+
border: "1px solid var(--ck-border-subtle)",
|
|
52
|
+
borderRadius: "var(--ck-radius-lg, 22px)",
|
|
53
|
+
boxShadow: "var(--ck-shadow-2)",
|
|
54
|
+
padding,
|
|
55
|
+
overflow: "hidden",
|
|
56
|
+
position: "relative",
|
|
57
|
+
cursor: onClick ? "pointer" : undefined,
|
|
58
|
+
display: "flex",
|
|
59
|
+
flexDirection: "column",
|
|
60
|
+
...style,
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
{children}
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
// 12-column CSS grid wrapper. Cells set their own colSpan/rowSpan
|
|
4
|
+
// via <BentoCell>. The grid clamps span values to the column count
|
|
5
|
+
// so a 4-wide cell inside an 8-col grid won't overflow.
|
|
6
|
+
|
|
7
|
+
export interface BentoGridProps {
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
// Column count. Defaults to 12. Pass 6 / 8 for tighter sections.
|
|
10
|
+
cols?: number;
|
|
11
|
+
// Gap between cells. Defaults to 16px.
|
|
12
|
+
gap?: number;
|
|
13
|
+
// Minimum row height. Cells span discrete rows of this height.
|
|
14
|
+
rowMinHeight?: number;
|
|
15
|
+
className?: string;
|
|
16
|
+
style?: CSSProperties;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function BentoGrid({
|
|
20
|
+
children,
|
|
21
|
+
cols = 12,
|
|
22
|
+
gap = 16,
|
|
23
|
+
rowMinHeight = 120,
|
|
24
|
+
className,
|
|
25
|
+
style,
|
|
26
|
+
}: BentoGridProps) {
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
data-ck-bento-grid=""
|
|
30
|
+
className={className}
|
|
31
|
+
style={{
|
|
32
|
+
display: "grid",
|
|
33
|
+
gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
|
|
34
|
+
gridAutoRows: `minmax(${rowMinHeight}px, auto)`,
|
|
35
|
+
gap,
|
|
36
|
+
...style,
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
{children}
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|