@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,65 @@
|
|
|
1
|
+
import type { ReactNode, CSSProperties } from "react";
|
|
2
|
+
|
|
3
|
+
// Bordered block with an ASCII-style title cut into the top
|
|
4
|
+
// border — `╭─ logs ──╮`. Implementation uses a regular CSS
|
|
5
|
+
// border + an absolutely-positioned title that masks a slice of
|
|
6
|
+
// the border with the page background.
|
|
7
|
+
//
|
|
8
|
+
// Looks best under terminal chrome but works under any —
|
|
9
|
+
// consumers can use it as a "labelled block" anywhere.
|
|
10
|
+
|
|
11
|
+
export interface AsciiBoxProps {
|
|
12
|
+
// Title text shown cut into the top border.
|
|
13
|
+
title?: string;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
// px padding inside the body. Default 14.
|
|
16
|
+
padding?: number;
|
|
17
|
+
// Background colour to use for the title mask. Defaults to
|
|
18
|
+
// the surrounding canvas. Override to match a parent surface
|
|
19
|
+
// (e.g. when the box sits on a card).
|
|
20
|
+
maskBg?: string;
|
|
21
|
+
className?: string;
|
|
22
|
+
style?: CSSProperties;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function AsciiBox({
|
|
26
|
+
title,
|
|
27
|
+
children,
|
|
28
|
+
padding = 14,
|
|
29
|
+
maskBg = "var(--ck-bg-canvas)",
|
|
30
|
+
className,
|
|
31
|
+
style,
|
|
32
|
+
}: AsciiBoxProps) {
|
|
33
|
+
return (
|
|
34
|
+
<div
|
|
35
|
+
className={className}
|
|
36
|
+
style={{
|
|
37
|
+
position: "relative",
|
|
38
|
+
border: "1px solid var(--ck-border-subtle)",
|
|
39
|
+
borderRadius: 2,
|
|
40
|
+
padding,
|
|
41
|
+
fontFamily: "var(--ck-font-mono)",
|
|
42
|
+
...style,
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
{title && (
|
|
46
|
+
<span
|
|
47
|
+
aria-hidden
|
|
48
|
+
style={{
|
|
49
|
+
position: "absolute",
|
|
50
|
+
top: -8,
|
|
51
|
+
left: 12,
|
|
52
|
+
padding: "0 6px",
|
|
53
|
+
background: maskBg,
|
|
54
|
+
color: "var(--ck-text-secondary)",
|
|
55
|
+
font: "500 11px/1 var(--ck-font-mono)",
|
|
56
|
+
letterSpacing: 0,
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
─ {title} ─
|
|
60
|
+
</span>
|
|
61
|
+
)}
|
|
62
|
+
{children}
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
// Braille-pattern spinner — the canonical TUI activity indicator.
|
|
4
|
+
// 10-frame cycle at 80 ms per frame ≈ 12.5 fps, which reads as
|
|
5
|
+
// the same gentle rotation you see in `npm install` or
|
|
6
|
+
// `gum spin`. Render inline anywhere you'd otherwise reach for a
|
|
7
|
+
// CSS circular spinner — they don't belong under this chrome.
|
|
8
|
+
|
|
9
|
+
const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
10
|
+
|
|
11
|
+
export interface BrailleSpinnerProps {
|
|
12
|
+
// Frame interval in ms. Default 80.
|
|
13
|
+
intervalMs?: number;
|
|
14
|
+
// Stop / start cycling. Default true.
|
|
15
|
+
active?: boolean;
|
|
16
|
+
className?: string;
|
|
17
|
+
style?: React.CSSProperties;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function BrailleSpinner({
|
|
21
|
+
intervalMs = 80,
|
|
22
|
+
active = true,
|
|
23
|
+
className,
|
|
24
|
+
style,
|
|
25
|
+
}: BrailleSpinnerProps) {
|
|
26
|
+
const [i, setI] = useState(0);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!active) return;
|
|
29
|
+
const t = setInterval(() => setI((x) => (x + 1) % FRAMES.length), intervalMs);
|
|
30
|
+
return () => clearInterval(t);
|
|
31
|
+
}, [intervalMs, active]);
|
|
32
|
+
return (
|
|
33
|
+
<span
|
|
34
|
+
aria-label="loading"
|
|
35
|
+
className={className}
|
|
36
|
+
style={{
|
|
37
|
+
fontFamily: "var(--ck-font-mono)",
|
|
38
|
+
display: "inline-block",
|
|
39
|
+
width: "1ch",
|
|
40
|
+
...style,
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
{FRAMES[i]}
|
|
44
|
+
</span>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useState,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
} from "react";
|
|
8
|
+
import { ThemeContext } from "./theme-context";
|
|
9
|
+
import type { Theme, Chrome, ThemeContextValue } from "./types";
|
|
10
|
+
|
|
11
|
+
const DEFAULT_THEME_KEY = "ck-theme";
|
|
12
|
+
const DEFAULT_CHROME_KEY = "ck-chrome";
|
|
13
|
+
|
|
14
|
+
function readStored<T extends string>(
|
|
15
|
+
key: string,
|
|
16
|
+
allowed: readonly T[],
|
|
17
|
+
): T | null {
|
|
18
|
+
if (typeof window === "undefined") return null;
|
|
19
|
+
const v = window.localStorage.getItem(key);
|
|
20
|
+
return allowed.includes(v as T) ? (v as T) : null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function preferredTheme(defaultTheme: Theme | "system"): Theme {
|
|
24
|
+
if (defaultTheme !== "system") return defaultTheme;
|
|
25
|
+
if (typeof window === "undefined") return "light";
|
|
26
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function applyTheme(theme: Theme, storageKey: string) {
|
|
30
|
+
document.documentElement.setAttribute("data-ck-theme", theme);
|
|
31
|
+
window.localStorage.setItem(storageKey, theme);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function applyChrome(chrome: Chrome, storageKey: string) {
|
|
35
|
+
document.documentElement.setAttribute("data-ck-chrome", chrome);
|
|
36
|
+
window.localStorage.setItem(storageKey, chrome);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ThemeProviderProps {
|
|
40
|
+
children: ReactNode;
|
|
41
|
+
// Initial theme if nothing's in localStorage. "system" reads
|
|
42
|
+
// `prefers-color-scheme`; explicit "light" / "dark" forces it.
|
|
43
|
+
defaultTheme?: Theme | "system";
|
|
44
|
+
defaultChrome?: Chrome;
|
|
45
|
+
// localStorage keys — override these per-consumer-app so a host
|
|
46
|
+
// page with multiple sub-apps doesn't collide.
|
|
47
|
+
themeStorageKey?: string;
|
|
48
|
+
chromeStorageKey?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function ThemeProvider({
|
|
52
|
+
children,
|
|
53
|
+
defaultTheme = "system",
|
|
54
|
+
defaultChrome = "seamless",
|
|
55
|
+
themeStorageKey = DEFAULT_THEME_KEY,
|
|
56
|
+
chromeStorageKey = DEFAULT_CHROME_KEY,
|
|
57
|
+
}: ThemeProviderProps) {
|
|
58
|
+
// Initial values match what getInlineThemeScript() would have
|
|
59
|
+
// painted on the <html> element pre-mount, so the React tree
|
|
60
|
+
// hydrates without a flash.
|
|
61
|
+
const [theme, setThemeState] = useState<Theme>(() => {
|
|
62
|
+
const stored = readStored<Theme>(themeStorageKey, ["light", "dark"]);
|
|
63
|
+
return stored ?? preferredTheme(defaultTheme);
|
|
64
|
+
});
|
|
65
|
+
const [chrome, setChromeState] = useState<Chrome>(() => {
|
|
66
|
+
const stored = readStored<Chrome>(chromeStorageKey, ["classic", "seamless"]);
|
|
67
|
+
return stored ?? defaultChrome;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Mirror state → DOM. Inline script already set the attribute
|
|
71
|
+
// pre-mount; this useEffect handles subsequent runtime changes.
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
applyTheme(theme, themeStorageKey);
|
|
74
|
+
}, [theme, themeStorageKey]);
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
applyChrome(chrome, chromeStorageKey);
|
|
78
|
+
}, [chrome, chromeStorageKey]);
|
|
79
|
+
|
|
80
|
+
const setTheme = useCallback((next: Theme) => setThemeState(next), []);
|
|
81
|
+
const setChrome = useCallback((next: Chrome) => setChromeState(next), []);
|
|
82
|
+
const toggleTheme = useCallback(
|
|
83
|
+
() => setThemeState((t) => (t === "dark" ? "light" : "dark")),
|
|
84
|
+
[],
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const value = useMemo<ThemeContextValue>(
|
|
88
|
+
() => ({ theme, setTheme, toggleTheme, chrome, setChrome }),
|
|
89
|
+
[theme, setTheme, toggleTheme, chrome, setChrome],
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
|
|
93
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { ThemeProvider } from "./ThemeProvider";
|
|
2
|
+
export type { ThemeProviderProps } from "./ThemeProvider";
|
|
3
|
+
export { useTheme } from "./useTheme";
|
|
4
|
+
export { getInlineThemeScript } from "./inline-script";
|
|
5
|
+
export type { Theme, Chrome, ThemeContextValue } from "./types";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Theme, Chrome } from "./types";
|
|
2
|
+
|
|
3
|
+
// Pre-mount inline script generator. Paste the returned string
|
|
4
|
+
// into a <script> tag in your index.html <head> so the data-ck-*
|
|
5
|
+
// attributes are set before React hydrates — prevents a white-
|
|
6
|
+
// flash on dark sessions. Pass the same keys you pass to
|
|
7
|
+
// <ThemeProvider> so localStorage stays in sync.
|
|
8
|
+
//
|
|
9
|
+
// Lives in its own file (not co-located with ThemeProvider) so
|
|
10
|
+
// React Fast Refresh stays happy — Vite's plugin-react requires
|
|
11
|
+
// modules to export only components, and mixing this helper into
|
|
12
|
+
// the same file would silently degrade HMR into full reloads (and
|
|
13
|
+
// take the data router's in-memory state with it on every save).
|
|
14
|
+
|
|
15
|
+
export function getInlineThemeScript(opts: {
|
|
16
|
+
themeStorageKey?: string;
|
|
17
|
+
chromeStorageKey?: string;
|
|
18
|
+
defaultTheme?: Theme | "system";
|
|
19
|
+
defaultChrome?: Chrome;
|
|
20
|
+
} = {}): string {
|
|
21
|
+
const t = JSON.stringify(opts.themeStorageKey ?? "ck-theme");
|
|
22
|
+
const c = JSON.stringify(opts.chromeStorageKey ?? "ck-chrome");
|
|
23
|
+
const dt = JSON.stringify(opts.defaultTheme ?? "system");
|
|
24
|
+
const dc = JSON.stringify(opts.defaultChrome ?? "seamless");
|
|
25
|
+
return `(function(){try{
|
|
26
|
+
var t=localStorage.getItem(${t});
|
|
27
|
+
if(t!=="light"&&t!=="dark"){
|
|
28
|
+
var d=${dt};
|
|
29
|
+
t=d==="system"?(matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"):d;
|
|
30
|
+
}
|
|
31
|
+
document.documentElement.setAttribute("data-ck-theme",t);
|
|
32
|
+
var c=localStorage.getItem(${c});
|
|
33
|
+
if(c!=="classic"&&c!=="seamless")c=${dc};
|
|
34
|
+
document.documentElement.setAttribute("data-ck-chrome",c);
|
|
35
|
+
}catch(e){}})();`;
|
|
36
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
import type { ThemeContextValue } from "./types";
|
|
3
|
+
|
|
4
|
+
// React Fast Refresh requires component modules to export only
|
|
5
|
+
// components. The context object + the hook must therefore live
|
|
6
|
+
// in their own files; ThemeProvider.tsx imports from here.
|
|
7
|
+
export const ThemeContext = createContext<ThemeContextValue | null>(null);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Theme axes. Light/dark is the conventional dark-mode toggle.
|
|
2
|
+
// Chrome ("classic" vs "seamless") is an orthogonal style — classic
|
|
3
|
+
// has visible borders + subtle dividers, seamless flattens them and
|
|
4
|
+
// relies on background shifts for layering. Both axes are applied
|
|
5
|
+
// as data attributes on <html>: data-ck-theme, data-ck-chrome.
|
|
6
|
+
|
|
7
|
+
export type Theme = "light" | "dark";
|
|
8
|
+
|
|
9
|
+
// Built-in chromes ship with the kit; custom strings are also
|
|
10
|
+
// allowed at runtime (the kit just stamps the value onto
|
|
11
|
+
// `data-ck-chrome`, your CSS supplies the overrides). The
|
|
12
|
+
// `(string & {})` trick preserves autocomplete for the built-ins
|
|
13
|
+
// while widening the union to any string for custom chromes.
|
|
14
|
+
export type Chrome = "classic" | "seamless" | "editorial" | "neobrutalism" | "ambient" | "swiss" | "terminal" | "bento" | "frutiger" | "riso" | "sketch" | (string & {});
|
|
15
|
+
|
|
16
|
+
export interface ThemeContextValue {
|
|
17
|
+
theme: Theme;
|
|
18
|
+
setTheme: (next: Theme) => void;
|
|
19
|
+
toggleTheme: () => void;
|
|
20
|
+
chrome: Chrome;
|
|
21
|
+
setChrome: (next: Chrome) => void;
|
|
22
|
+
}
|