@cosxai/ui 1.0.0-alpha.1 → 1.0.0-alpha.10
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 +89 -0
- package/package.json +10 -4
- package/src/brand.css +1 -1
- package/src/components/ActionBar.tsx +607 -0
- package/src/components/ActivityTimeline.tsx +77 -0
- package/src/components/AsyncSelect.tsx +205 -0
- package/src/components/AuthLayout.tsx +127 -0
- package/src/components/BrandLoader.tsx +122 -0
- package/src/components/Breadcrumb.tsx +129 -0
- package/src/components/Button.tsx +48 -40
- package/src/components/ChoiceCards.tsx +68 -0
- package/src/components/CodeInput.tsx +150 -0
- package/src/components/CommandPalette.tsx +228 -0
- package/src/components/ConfirmDialog.tsx +294 -0
- package/src/components/CopyField.tsx +104 -0
- package/src/components/DateInput.tsx +366 -0
- package/src/components/Dialog.tsx +27 -3
- package/src/components/Dropzone.tsx +187 -0
- package/src/components/FileCard.tsx +233 -0
- package/src/components/FileRow.tsx +159 -0
- package/src/components/FolderTree.tsx +377 -0
- package/src/components/ListToolbar.tsx +177 -0
- package/src/components/MentionInput.tsx +233 -0
- package/src/components/Menu.tsx +109 -0
- package/src/components/PageHeader.tsx +62 -0
- package/src/components/PageState.tsx +84 -0
- package/src/components/Progress.tsx +97 -0
- package/src/components/PulseDot.tsx +31 -0
- package/src/components/RecipientsInput.tsx +252 -0
- package/src/components/SearchField.tsx +137 -0
- package/src/components/SegmentedControl.tsx +95 -0
- package/src/components/SidePanel.tsx +130 -0
- package/src/components/Skeleton.tsx +73 -0
- package/src/components/StageProgress.tsx +51 -0
- package/src/components/Steps.tsx +58 -0
- package/src/components/SyncStatus.tsx +56 -0
- package/src/components/ThemeSwitch.tsx +99 -0
- package/src/components/Toast.tsx +45 -8
- package/src/components/UploadCheck.tsx +118 -0
- package/src/components/UploadList.tsx +176 -0
- package/src/components/VirtualList.tsx +90 -0
- package/src/components/WindowDrop.tsx +131 -0
- package/src/components/WorkspaceRow.tsx +80 -0
- package/src/components/inputs-aria.ts +5 -0
- package/src/components/inputs-date.ts +92 -0
- package/src/components/inputs-listbox.tsx +154 -0
- package/src/components/inputs-search.ts +60 -0
- package/src/components/status-files.ts +124 -0
- package/src/components/status-motion.ts +30 -0
- package/src/components/useSelection.ts +142 -0
- package/src/index.ts +45 -1
- package/src/theme-base.css +123 -0
- package/src/theme-reset.css +14 -0
- package/src/theme.css +2 -125
- package/src/components/core.test.tsx +0 -242
- package/src/components/forms.test.tsx +0 -280
- package/src/components/overlays.test.tsx +0 -245
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Shared by Dropzone, WindowDrop and UploadCheck: what was dropped, and
|
|
2
|
+
// whether each item may upload.
|
|
3
|
+
|
|
4
|
+
/** A file with its path inside a dropped folder ("Q3/Financials/a.pdf"). */
|
|
5
|
+
export type DroppedFile = { file: File; path: string };
|
|
6
|
+
|
|
7
|
+
export type RejectReason = 'type' | 'size' | 'depth';
|
|
8
|
+
export type Rejected = DroppedFile & { reason: RejectReason };
|
|
9
|
+
|
|
10
|
+
export type FileRules = {
|
|
11
|
+
/** e.g. ".pdf,.docx,image/*" — empty accepts everything. */
|
|
12
|
+
accept?: string | undefined;
|
|
13
|
+
/** Per-file limit in bytes. @default 2 GB */
|
|
14
|
+
maxSize?: number | undefined;
|
|
15
|
+
/** Deepest folder level accepted (a/b/c = 3). @default 6 */
|
|
16
|
+
maxDepth?: number | undefined;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_MAX_SIZE = 2 * 1000 ** 3; // "2 GB"
|
|
20
|
+
export const DEFAULT_MAX_DEPTH = 6;
|
|
21
|
+
|
|
22
|
+
/** A drag carries files (not text, links or cards dragged on the page). */
|
|
23
|
+
export function hasFiles(dt: DataTransfer | null | undefined): boolean {
|
|
24
|
+
if (!dt) return false;
|
|
25
|
+
return Array.from(dt.types ?? []).includes('Files');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Decimal units, as the spec writes sizes (318 MB, 1.2 of 3.4 MB) and as macOS shows them. */
|
|
29
|
+
export function formatBytes(n: number): string {
|
|
30
|
+
if (n < 1000) return `${n} B`;
|
|
31
|
+
const units = ['KB', 'MB', 'GB', 'TB'];
|
|
32
|
+
let v = n / 1000;
|
|
33
|
+
let i = 0;
|
|
34
|
+
while (v >= 1000 && i < units.length - 1) {
|
|
35
|
+
v /= 1000;
|
|
36
|
+
i++;
|
|
37
|
+
}
|
|
38
|
+
return `${v >= 10 || Number.isInteger(v) ? Math.round(v) : v.toFixed(1)} ${units[i]}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function matchesAccept(file: File, accept: string | undefined): boolean {
|
|
42
|
+
if (!accept?.trim()) return true;
|
|
43
|
+
const name = file.name.toLowerCase();
|
|
44
|
+
const type = (file.type || '').toLowerCase();
|
|
45
|
+
return accept
|
|
46
|
+
.split(',')
|
|
47
|
+
.map((a) => a.trim().toLowerCase())
|
|
48
|
+
.filter(Boolean)
|
|
49
|
+
.some((a) => (a.startsWith('.') ? name.endsWith(a) : a.endsWith('/*') ? type.startsWith(a.slice(0, -1)) : type === a));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Folder depth of a path: "a.pdf" → 0, "Q3/a.pdf" → 1. */
|
|
53
|
+
export function depthOf(path: string): number {
|
|
54
|
+
return Math.max(0, path.split('/').length - 1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Hidden items (a segment starting with "."), unticked by default. */
|
|
58
|
+
export function isHidden(path: string): boolean {
|
|
59
|
+
return path.split('/').some((s) => s.startsWith('.'));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function checkFiles(items: DroppedFile[], rules: FileRules = {}): { accepted: DroppedFile[]; rejected: Rejected[] } {
|
|
63
|
+
const maxSize = rules.maxSize ?? DEFAULT_MAX_SIZE;
|
|
64
|
+
const maxDepth = rules.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
65
|
+
const accepted: DroppedFile[] = [];
|
|
66
|
+
const rejected: Rejected[] = [];
|
|
67
|
+
for (const it of items) {
|
|
68
|
+
if (depthOf(it.path) > maxDepth) rejected.push({ ...it, reason: 'depth' });
|
|
69
|
+
else if (!matchesAccept(it.file, rules.accept)) rejected.push({ ...it, reason: 'type' });
|
|
70
|
+
else if (it.file.size > maxSize) rejected.push({ ...it, reason: 'size' });
|
|
71
|
+
else accepted.push(it);
|
|
72
|
+
}
|
|
73
|
+
return { accepted, rejected };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Minimal shapes of the (non-standard, universally shipped) entry API.
|
|
77
|
+
type Entry = { isFile: boolean; isDirectory: boolean; name: string; fullPath?: string };
|
|
78
|
+
type FileEntry = Entry & { file: (ok: (f: File) => void, fail: (e: unknown) => void) => void };
|
|
79
|
+
type DirEntry = Entry & { createReader: () => { readEntries: (ok: (e: Entry[]) => void, fail: (e: unknown) => void) => void } };
|
|
80
|
+
|
|
81
|
+
async function walk(entry: Entry, prefix: string, out: DroppedFile[]): Promise<void> {
|
|
82
|
+
const path = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
83
|
+
if (entry.isFile) {
|
|
84
|
+
const file = await new Promise<File>((ok, fail) => (entry as FileEntry).file(ok, fail));
|
|
85
|
+
out.push({ file, path });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (!entry.isDirectory) return;
|
|
89
|
+
const reader = (entry as DirEntry).createReader();
|
|
90
|
+
// readEntries returns batches (≤100 in Chrome) until an empty one.
|
|
91
|
+
for (;;) {
|
|
92
|
+
const batch = await new Promise<Entry[]>((ok, fail) => reader.readEntries(ok, fail));
|
|
93
|
+
if (!batch.length) break;
|
|
94
|
+
for (const child of batch) await walk(child, path, out);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Everything in a drop, folders read recursively into files with paths. */
|
|
99
|
+
export async function readDataTransfer(dt: DataTransfer): Promise<DroppedFile[]> {
|
|
100
|
+
const items = Array.from(dt.items ?? []);
|
|
101
|
+
const entries = items
|
|
102
|
+
.filter((i) => i.kind === 'file')
|
|
103
|
+
.map((i) => (i as DataTransferItem & { webkitGetAsEntry?: () => Entry | null }).webkitGetAsEntry?.() ?? null);
|
|
104
|
+
if (entries.length && entries.every(Boolean)) {
|
|
105
|
+
const out: DroppedFile[] = [];
|
|
106
|
+
for (const e of entries) await walk(e!, '', out);
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
return Array.from(dt.files ?? []).map((file) => ({ file, path: file.name }));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Files from an <input type="file"> (webkitdirectory gives relative paths). */
|
|
113
|
+
export function fromInput(files: FileList | null): DroppedFile[] {
|
|
114
|
+
return Array.from(files ?? []).map((file) => ({
|
|
115
|
+
file,
|
|
116
|
+
path: (file as File & { webkitRelativePath?: string }).webkitRelativePath || file.name,
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** "3 files · 1 folder" for what is being dragged or dropped. */
|
|
121
|
+
export function describeDrop(items: DroppedFile[]): { files: number; folders: number } {
|
|
122
|
+
const folders = new Set(items.map((i) => i.path.split('/')).filter((p) => p.length > 1).map((p) => p[0]));
|
|
123
|
+
return { files: items.filter((i) => depthOf(i.path) === 0).length, folders: folders.size };
|
|
124
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useEffect, useState, type RefObject } from 'react';
|
|
2
|
+
|
|
3
|
+
const REDUCE = '(prefers-reduced-motion: reduce)';
|
|
4
|
+
|
|
5
|
+
/** Whether the person asked for reduced motion (live). */
|
|
6
|
+
export function useReducedMotion(): boolean {
|
|
7
|
+
const [reduce, setReduce] = useState(() => typeof window !== 'undefined' && window.matchMedia?.(REDUCE).matches === true);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const mq = window.matchMedia?.(REDUCE);
|
|
10
|
+
if (!mq) return;
|
|
11
|
+
const on = (e: MediaQueryListEvent) => setReduce(e.matches);
|
|
12
|
+
mq.addEventListener('change', on);
|
|
13
|
+
return () => mq.removeEventListener('change', on);
|
|
14
|
+
}, []);
|
|
15
|
+
return reduce;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A looping Web Animation on ref — none under reduced motion (the
|
|
19
|
+
* element keeps its still style) and none where the API is missing. */
|
|
20
|
+
export function useLoop(ref: RefObject<HTMLElement | null>, keyframes: Keyframe[], options: KeyframeAnimationOptions, on = true): void {
|
|
21
|
+
const reduce = useReducedMotion();
|
|
22
|
+
const key = JSON.stringify([keyframes, options]);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const el = ref.current;
|
|
25
|
+
if (!on || reduce || !el || typeof el.animate !== 'function') return;
|
|
26
|
+
const a = el.animate(keyframes, { iterations: Infinity, ...options });
|
|
27
|
+
return () => a.cancel();
|
|
28
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
29
|
+
}, [on, reduce, key]);
|
|
30
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { useCallback, useMemo, useRef, useState, type KeyboardEvent, type MouseEvent } from 'react';
|
|
2
|
+
|
|
3
|
+
export type SelectionOptions = {
|
|
4
|
+
/** Every selectable id, in display order (ranges follow this order). */
|
|
5
|
+
ids: readonly string[];
|
|
6
|
+
/** Controlled selection. */
|
|
7
|
+
selected?: ReadonlySet<string> | readonly string[] | undefined;
|
|
8
|
+
/** Uncontrolled starting selection. */
|
|
9
|
+
defaultSelected?: readonly string[] | undefined;
|
|
10
|
+
onChange?: ((selected: Set<string>) => void) | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Selection mode: a plain click toggles instead of opening. Unset: on
|
|
13
|
+
* while anything is selected (ticking one box enters it, clearing leaves).
|
|
14
|
+
*/
|
|
15
|
+
selectionMode?: boolean | undefined;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type Selection = {
|
|
19
|
+
selected: Set<string>;
|
|
20
|
+
count: number;
|
|
21
|
+
isSelected: (id: string) => boolean;
|
|
22
|
+
/** True when a plain click toggles (see SelectionOptions.selectionMode). */
|
|
23
|
+
selectionMode: boolean;
|
|
24
|
+
toggle: (id: string) => void;
|
|
25
|
+
/** Select from the anchor (last clicked) to id, keeping what is selected. */
|
|
26
|
+
selectRange: (id: string) => void;
|
|
27
|
+
selectAll: () => void;
|
|
28
|
+
clear: () => void;
|
|
29
|
+
set: (ids: Iterable<string>) => void;
|
|
30
|
+
/**
|
|
31
|
+
* The click on an item: ⇧ selects a range, ⌘/Ctrl toggles, a plain click
|
|
32
|
+
* toggles in selection mode. Returns true when it handled the click —
|
|
33
|
+
* otherwise the caller opens the item.
|
|
34
|
+
*/
|
|
35
|
+
handleClick: (id: string, event: Pick<MouseEvent, 'shiftKey' | 'metaKey' | 'ctrlKey'>) => boolean;
|
|
36
|
+
/** For the list container: ⌘/Ctrl-A selects all, Esc clears. */
|
|
37
|
+
onKeyDown: (event: KeyboardEvent) => void;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function toSet(v: ReadonlySet<string> | readonly string[] | undefined): Set<string> | undefined {
|
|
41
|
+
if (v === undefined) return undefined;
|
|
42
|
+
return new Set(v);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* useSelection — the selection model every list and grid shares: click,
|
|
47
|
+
* ⇧-click range, ⌘/Ctrl-click toggle, ⌘A, Esc. In selection mode the whole
|
|
48
|
+
* card or row is the checkbox.
|
|
49
|
+
*/
|
|
50
|
+
export function useSelection({ ids, selected, defaultSelected, onChange, selectionMode }: SelectionOptions): Selection {
|
|
51
|
+
const [inner, setInner] = useState<Set<string>>(() => new Set(defaultSelected ?? []));
|
|
52
|
+
const controlled = toSet(selected);
|
|
53
|
+
const current = useMemo(() => controlled ?? inner, [controlled, inner]);
|
|
54
|
+
const anchor = useRef<string | null>(null);
|
|
55
|
+
|
|
56
|
+
const commit = useCallback(
|
|
57
|
+
(next: Set<string>) => {
|
|
58
|
+
if (!controlled) setInner(next);
|
|
59
|
+
onChange?.(next);
|
|
60
|
+
},
|
|
61
|
+
[controlled, onChange],
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const toggle = useCallback(
|
|
65
|
+
(id: string) => {
|
|
66
|
+
const next = new Set(current);
|
|
67
|
+
if (next.has(id)) next.delete(id);
|
|
68
|
+
else next.add(id);
|
|
69
|
+
anchor.current = id;
|
|
70
|
+
commit(next);
|
|
71
|
+
},
|
|
72
|
+
[current, commit],
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const selectRange = useCallback(
|
|
76
|
+
(id: string) => {
|
|
77
|
+
const from = anchor.current === null ? -1 : ids.indexOf(anchor.current);
|
|
78
|
+
const to = ids.indexOf(id);
|
|
79
|
+
if (to < 0) return;
|
|
80
|
+
const next = new Set(current);
|
|
81
|
+
if (from < 0) next.add(id);
|
|
82
|
+
else for (let i = Math.min(from, to); i <= Math.max(from, to); i++) next.add(ids[i]!);
|
|
83
|
+
anchor.current = id;
|
|
84
|
+
commit(next);
|
|
85
|
+
},
|
|
86
|
+
[ids, current, commit],
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const selectAll = useCallback(() => commit(new Set(ids)), [ids, commit]);
|
|
90
|
+
const clear = useCallback(() => {
|
|
91
|
+
anchor.current = null;
|
|
92
|
+
commit(new Set());
|
|
93
|
+
}, [commit]);
|
|
94
|
+
const set = useCallback((next: Iterable<string>) => commit(new Set(next)), [commit]);
|
|
95
|
+
|
|
96
|
+
const mode = selectionMode ?? current.size > 0;
|
|
97
|
+
|
|
98
|
+
const handleClick = useCallback<Selection['handleClick']>(
|
|
99
|
+
(id, e) => {
|
|
100
|
+
if (e.shiftKey) {
|
|
101
|
+
selectRange(id);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
if (e.metaKey || e.ctrlKey || mode) {
|
|
105
|
+
toggle(id);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
anchor.current = id;
|
|
109
|
+
return false;
|
|
110
|
+
},
|
|
111
|
+
[mode, selectRange, toggle],
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const onKeyDown = useCallback(
|
|
115
|
+
(e: KeyboardEvent) => {
|
|
116
|
+
const target = e.target as HTMLElement | null;
|
|
117
|
+
const typing = target?.closest('input, textarea, [contenteditable="true"]');
|
|
118
|
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'a' && !typing) {
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
selectAll();
|
|
121
|
+
} else if (e.key === 'Escape' && current.size > 0) {
|
|
122
|
+
e.preventDefault();
|
|
123
|
+
clear();
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
[selectAll, clear, current.size],
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
selected: current,
|
|
131
|
+
count: current.size,
|
|
132
|
+
isSelected: (id) => current.has(id),
|
|
133
|
+
selectionMode: mode,
|
|
134
|
+
toggle,
|
|
135
|
+
selectRange,
|
|
136
|
+
selectAll,
|
|
137
|
+
clear,
|
|
138
|
+
set,
|
|
139
|
+
handleClick,
|
|
140
|
+
onKeyDown,
|
|
141
|
+
};
|
|
142
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export { Logo, type LogoProps } from './components/Logo';
|
|
|
17
17
|
export { Marker, type MarkerProps } from './components/Marker';
|
|
18
18
|
export { MetaLabel, type MetaLabelProps } from './components/MetaLabel';
|
|
19
19
|
export { Spinner, type SpinnerProps } from './components/Spinner';
|
|
20
|
+
export { BrandLoader, loaderState, type BrandLoaderProps } from './components/BrandLoader';
|
|
20
21
|
export { Tag, type TagProps } from './components/Tag';
|
|
21
22
|
export { ThemeProvider, useTheme, type ThemeMode, type ThemeProviderProps } from './components/ThemeProvider';
|
|
22
23
|
export { useButtonAction, type ButtonAction } from './components/useButtonAction';
|
|
@@ -31,5 +32,48 @@ export { Switch, type SwitchProps } from './components/Switch';
|
|
|
31
32
|
export { Table, type TableColumn, type TableProps, type TableRowBase, type TableSort, type TableSortDirection } from './components/Table';
|
|
32
33
|
export { Tab, TabList, TabPanel, Tabs, type TabItem, type TabListProps, type TabPanelProps, type TabProps, type TabsProps, type TabsVariant } from './components/Tabs';
|
|
33
34
|
export { Textarea, type TextareaProps } from './components/Textarea';
|
|
34
|
-
export { Toaster, toast, type ToasterProps, type ToastLabels, type ToastOptions, type ToastStatus } from './components/Toast';
|
|
35
|
+
export { ToastCard, Toaster, toast, type ToastCardProps, type ToasterProps, type ToastLabels, type ToastOptions, type ToastStatus } from './components/Toast';
|
|
35
36
|
export { Tooltip, TooltipProvider, type TooltipProps } from './components/Tooltip';
|
|
37
|
+
|
|
38
|
+
// Stage 2 — product patterns
|
|
39
|
+
export { ActionBar, ActionBarProvider, arrange, formatShortcut, useActionBarActivity, useActionBarHidden, useActionBarItems, useActionBarMode, useActionBarSelection, type ActionBarAction, type ActionBarLabels, type ActionBarMode, type ActionBarPresentation, type ActionBarProps, type ActionBarProviderProps, type ActionBarSelection, type ActionBarState } from './components/ActionBar';
|
|
40
|
+
export { ActivityTimeline, initialsOf, type ActivityEvent, type ActivityGroup, type ActivityTimelineProps } from './components/ActivityTimeline';
|
|
41
|
+
export { AsyncSelect, type AsyncSelectProps } from './components/AsyncSelect';
|
|
42
|
+
export { Breadcrumb, type BreadcrumbItem, type BreadcrumbProps } from './components/Breadcrumb';
|
|
43
|
+
export { ChoiceCards, type Choice, type ChoiceCardsProps } from './components/ChoiceCards';
|
|
44
|
+
export { CodeInput, type CodeInputProps } from './components/CodeInput';
|
|
45
|
+
export { CommandPalette, useCommandPaletteHotkey, type CommandItem, type CommandPaletteLabels, type CommandPaletteProps, type CommandSource } from './components/CommandPalette';
|
|
46
|
+
export { ConfirmDialog, PromptDialog, StepUpDialog, type ConfirmDialogProps, type PromptDialogProps, type StepUpDialogProps } from './components/ConfirmDialog';
|
|
47
|
+
export { CopyField, type CopyFieldProps } from './components/CopyField';
|
|
48
|
+
export { DateInput, FuzzyDateInput, type DateInputProps, type DatePrecision, type FuzzyDateInputProps } from './components/DateInput';
|
|
49
|
+
export { Dropzone, refusedText, type DropzoneLabels, type DropzoneProps } from './components/Dropzone';
|
|
50
|
+
export { FileCard, type FileCardProps, type FileCardState } from './components/FileCard';
|
|
51
|
+
export { FileList, FileRow, type FileListProps, type FileRowProps } from './components/FileRow';
|
|
52
|
+
export { FolderTree, type FolderTreeProps, type TreeNode } from './components/FolderTree';
|
|
53
|
+
export { type ListItem } from './components/inputs-listbox';
|
|
54
|
+
export { ListToolbar, type ListFilter, type ListToolbarLabels, type ListToolbarProps, type ListView, type SearchScope } from './components/ListToolbar';
|
|
55
|
+
export { MentionInput, mentionAt, type Mention, type MentionInputProps } from './components/MentionInput';
|
|
56
|
+
export { Menu, MenuCheckboxItem, MenuContent, MenuGroup, MenuItem, MenuSeparator, MenuSub, MenuSubContent, MenuSubTrigger, MenuTrigger, type MenuCheckboxItemProps, type MenuContentProps, type MenuItemProps, type MenuSubTriggerProps } from './components/Menu';
|
|
57
|
+
export { PageHeader, type PageHeaderFigure, type PageHeaderProps } from './components/PageHeader';
|
|
58
|
+
export { PageNotice, PageState, type PageNoticeProps, type PageStateKind, type PageStateProps } from './components/PageState';
|
|
59
|
+
export { Progress, progressStep, type ProgressProps, type ProgressSegment } from './components/Progress';
|
|
60
|
+
export { PulseDot, type PulseDotProps } from './components/PulseDot';
|
|
61
|
+
export { RecipientsInput, type Recipient, type RecipientStatus, type RecipientsInputProps } from './components/RecipientsInput';
|
|
62
|
+
export { SearchField, type SearchFieldProps } from './components/SearchField';
|
|
63
|
+
export { SegmentedControl, type Segment, type SegmentedControlProps } from './components/SegmentedControl';
|
|
64
|
+
export { SidePanel, SidePanelProvider, SidePanelSlot, useSidePanel, type SidePanelProps } from './components/SidePanel';
|
|
65
|
+
export { Skeleton, SkeletonCards, SkeletonList, SkeletonViewer, type SkeletonProps, type SkeletonViewerProps } from './components/Skeleton';
|
|
66
|
+
export { StageProgress, type Stage, type StageProgressProps } from './components/StageProgress';
|
|
67
|
+
export { checkFiles, formatBytes, hasFiles, readDataTransfer, type DroppedFile, type FileRules, type Rejected, type RejectReason } from './components/status-files';
|
|
68
|
+
export { Steps, type Step, type StepState, type StepsProps } from './components/Steps';
|
|
69
|
+
export { SyncStatus, type SyncState, type SyncStatusProps } from './components/SyncStatus';
|
|
70
|
+
export { UploadCheck, groupByFolder, type UploadCheckLabels, type UploadCheckProps } from './components/UploadCheck';
|
|
71
|
+
export { UploadFolderRow, UploadList, UploadRow, useBeforeUnloadWhile, type UploadItem, type UploadRowLabels, type UploadRowProps, type UploadState } from './components/UploadList';
|
|
72
|
+
export { useSelection, type Selection, type SelectionOptions } from './components/useSelection';
|
|
73
|
+
export { InfiniteLoader, VirtualList, type InfiniteLoaderProps, type VirtualListProps } from './components/VirtualList';
|
|
74
|
+
export { WindowDrop, useWindowDrop, type WindowDropProps } from './components/WindowDrop';
|
|
75
|
+
|
|
76
|
+
// Stage 3 — sign-in (Metaroom sign-in, design.cosx.co)
|
|
77
|
+
export { AuthIconTile, AuthLayout, AuthPanel, AuthStepHead, type AuthIconTileProps, type AuthLayoutProps, type AuthPanelProps, type AuthStepHeadProps } from './components/AuthLayout';
|
|
78
|
+
export { ThemeSwitch, type ThemeSwitchLabels, type ThemeSwitchProps } from './components/ThemeSwitch';
|
|
79
|
+
export { WorkspaceMark, WorkspaceRow, type WorkspaceMarkProps, type WorkspaceRowProps } from './components/WorkspaceRow';
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/* @cosxai/ui — the design system's Tailwind utilities, without clearing
|
|
2
|
+
* Tailwind's own palette, sizes, radii, shadows, fonts and easings.
|
|
3
|
+
*
|
|
4
|
+
* For an app moving to the kit screen by screen (product-meta during the
|
|
5
|
+
* move): its existing classes keep working, and the kit's are added. Where
|
|
6
|
+
* both define a name (radius, font-sans, ease-in/out), the kit's value wins.
|
|
7
|
+
* New apps import theme.css, which is this plus the reset.
|
|
8
|
+
*/
|
|
9
|
+
/* ink: — styles for ink mode (data-mode="ink" on <html>, or inside
|
|
10
|
+
.ink-mode). The semantic colours switch by themselves; this is for the
|
|
11
|
+
rules that flip on ink (the primary becomes the yellow). */
|
|
12
|
+
@custom-variant ink (&:where([data-mode="ink"], [data-mode="ink"] *, .ink-mode, .ink-mode *));
|
|
13
|
+
|
|
14
|
+
@theme inline {
|
|
15
|
+
|
|
16
|
+
/* Materials — fixed, do not switch with ink mode */
|
|
17
|
+
--color-paper: var(--paper);
|
|
18
|
+
--color-linen: var(--linen);
|
|
19
|
+
--color-sunk-2: var(--sunk-2);
|
|
20
|
+
--color-sunk-3: var(--sunk-3);
|
|
21
|
+
--color-ink: var(--ink);
|
|
22
|
+
--color-ink-raised: var(--ink-raised);
|
|
23
|
+
--color-grey: var(--grey);
|
|
24
|
+
--color-grey-inverse: var(--grey-inverse);
|
|
25
|
+
|
|
26
|
+
/* The yellow — one hue, by area */
|
|
27
|
+
--color-yellow: var(--yellow);
|
|
28
|
+
--color-yellow-accent: var(--yellow-accent);
|
|
29
|
+
--color-yellow-accent-hover: var(--yellow-accent-hover);
|
|
30
|
+
--color-yellow-light: var(--yellow-light);
|
|
31
|
+
--color-yellow-hover: var(--yellow-hover);
|
|
32
|
+
--color-yellow-1: var(--yellow-1);
|
|
33
|
+
--color-yellow-2: var(--yellow-2);
|
|
34
|
+
--color-yellow-3: var(--yellow-3);
|
|
35
|
+
--color-yellow-4: var(--yellow-4);
|
|
36
|
+
--color-yellow-5: var(--yellow-5);
|
|
37
|
+
--color-yellow-wash-12: var(--yellow-12);
|
|
38
|
+
--color-yellow-wash-20: var(--yellow-20);
|
|
39
|
+
--color-yellow-wash-32: var(--yellow-32);
|
|
40
|
+
--color-yellow-wash-50: var(--yellow-50);
|
|
41
|
+
--color-marker: var(--marker);
|
|
42
|
+
|
|
43
|
+
/* Semantic — switch with ink mode. Product code uses these. */
|
|
44
|
+
--color-page: var(--bg-page);
|
|
45
|
+
--color-sunk: var(--bg-sunk);
|
|
46
|
+
--color-well: var(--bg-well);
|
|
47
|
+
--color-chrome: var(--bg-chrome);
|
|
48
|
+
--color-field: var(--bg-field);
|
|
49
|
+
--color-fg: var(--text-primary);
|
|
50
|
+
--color-fg-secondary: var(--text-secondary);
|
|
51
|
+
--color-fg-label: var(--text-label);
|
|
52
|
+
--color-fg-on-yellow-secondary: var(--text-on-yellow-secondary);
|
|
53
|
+
--color-rule: var(--rule);
|
|
54
|
+
--color-rule-soft: var(--rule-soft);
|
|
55
|
+
--color-hover: var(--hover);
|
|
56
|
+
--color-scrim: var(--scrim);
|
|
57
|
+
|
|
58
|
+
/* Workspace brand (brand.css): the COSX yellow unless a customer's
|
|
59
|
+
colour is injected. Selection and grounds use field; dots and
|
|
60
|
+
progress use mark. Never for text. */
|
|
61
|
+
--color-brand-field: var(--brand-field);
|
|
62
|
+
--color-brand-mark: var(--brand-mark);
|
|
63
|
+
|
|
64
|
+
/* On an ink surface inside a paper page */
|
|
65
|
+
--color-inv: var(--inv-bg);
|
|
66
|
+
--color-inv-raised: var(--inv-bg-raised);
|
|
67
|
+
--color-inv-fg: var(--inv-text);
|
|
68
|
+
--color-inv-fg-secondary: var(--inv-text-secondary);
|
|
69
|
+
--color-inv-rule: var(--inv-rule);
|
|
70
|
+
|
|
71
|
+
/* Product status: yellow, ink and one red. Progress is an ink outline and
|
|
72
|
+
complete a grey dot — they take no colour of their own. */
|
|
73
|
+
--color-attention: var(--status-attention);
|
|
74
|
+
--color-attention-wash: var(--status-attention-wash);
|
|
75
|
+
--color-error: var(--status-error);
|
|
76
|
+
--color-error-text: var(--status-error-text);
|
|
77
|
+
--color-error-wash: var(--status-error-wash);
|
|
78
|
+
|
|
79
|
+
--color-transparent: transparent;
|
|
80
|
+
--color-current: currentColor;
|
|
81
|
+
--color-white: #fff;
|
|
82
|
+
|
|
83
|
+
/* Radius ladder — same names and values as 3.0 spacing.css */
|
|
84
|
+
--radius-xs: 4px;
|
|
85
|
+
--radius-sm: 6px;
|
|
86
|
+
--radius-md: 8px;
|
|
87
|
+
--radius-lg: 16px;
|
|
88
|
+
--radius-xl: 24px;
|
|
89
|
+
--radius-pill: 999px;
|
|
90
|
+
|
|
91
|
+
/* One family. Same stacks as fonts.css (the ones in effect): font-sans
|
|
92
|
+
for Latin, font-sans-cjk for Chinese pages (Geist for Latin and
|
|
93
|
+
figures, Noto Sans SC for hanzi). */
|
|
94
|
+
--font-sans: "Geist Variable", "Geist", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
95
|
+
--font-sans-cjk: "Geist Variable", "Geist", "Noto Sans SC Variable", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
96
|
+
|
|
97
|
+
--font-weight-normal: 400;
|
|
98
|
+
--font-weight-medium: 500;
|
|
99
|
+
--font-weight-semibold: 600;
|
|
100
|
+
--font-weight-bold: 700;
|
|
101
|
+
|
|
102
|
+
/* Type scale (typography.css). Product UI body is 14px, content 15px. */
|
|
103
|
+
--text-meta: 12px;
|
|
104
|
+
--text-small: 13.5px;
|
|
105
|
+
--text-ui: 14px;
|
|
106
|
+
--text-body: 15px;
|
|
107
|
+
--text-h3: 18px;
|
|
108
|
+
--text-lede: 20px;
|
|
109
|
+
--text-title: 22px;
|
|
110
|
+
--text-figure: 32px;
|
|
111
|
+
--text-h2: var(--size-h2);
|
|
112
|
+
--text-h1: var(--size-h1);
|
|
113
|
+
--text-display: var(--size-display);
|
|
114
|
+
|
|
115
|
+
--tracking-display: -0.025em;
|
|
116
|
+
--tracking-heading: -0.015em;
|
|
117
|
+
--tracking-figure: -0.03em;
|
|
118
|
+
|
|
119
|
+
/* Motion — flat for state, ease-out for entry; nothing bounces */
|
|
120
|
+
--ease-standard: cubic-bezier(.2,0,.2,1);
|
|
121
|
+
--ease-out: cubic-bezier(.16,1,.3,1);
|
|
122
|
+
--ease-in: cubic-bezier(.4,0,1,1);
|
|
123
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* @cosxai/ui — clears Tailwind's defaults so only the design system's
|
|
2
|
+
* values exist (part of theme.css; see theme-base.css for incremental use). */
|
|
3
|
+
@theme {
|
|
4
|
+
--color-*: initial;
|
|
5
|
+
--radius-*: initial;
|
|
6
|
+
--shadow-*: initial;
|
|
7
|
+
--inset-shadow-*: initial;
|
|
8
|
+
--drop-shadow-*: initial;
|
|
9
|
+
--font-*: initial;
|
|
10
|
+
--ease-*: initial;
|
|
11
|
+
--text-*: initial;
|
|
12
|
+
--font-weight-*: initial;
|
|
13
|
+
--tracking-*: initial;
|
|
14
|
+
}
|
package/src/theme.css
CHANGED
|
@@ -16,128 +16,5 @@
|
|
|
16
16
|
* (theme.test.ts). In particular font sizes avoid --text-primary /
|
|
17
17
|
* --text-secondary / --text-label, which are colours in 3.0.
|
|
18
18
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
rules that flip on ink (the primary becomes the yellow). */
|
|
22
|
-
@custom-variant ink (&:where([data-mode="ink"], [data-mode="ink"] *, .ink-mode, .ink-mode *));
|
|
23
|
-
|
|
24
|
-
@theme inline {
|
|
25
|
-
--color-*: initial;
|
|
26
|
-
--radius-*: initial;
|
|
27
|
-
--shadow-*: initial;
|
|
28
|
-
--inset-shadow-*: initial;
|
|
29
|
-
--drop-shadow-*: initial;
|
|
30
|
-
--font-*: initial;
|
|
31
|
-
--ease-*: initial;
|
|
32
|
-
--text-*: initial;
|
|
33
|
-
--font-weight-*: initial;
|
|
34
|
-
--tracking-*: initial;
|
|
35
|
-
|
|
36
|
-
/* Materials — fixed, do not switch with ink mode */
|
|
37
|
-
--color-paper: var(--paper);
|
|
38
|
-
--color-linen: var(--linen);
|
|
39
|
-
--color-sunk-2: var(--sunk-2);
|
|
40
|
-
--color-sunk-3: var(--sunk-3);
|
|
41
|
-
--color-ink: var(--ink);
|
|
42
|
-
--color-ink-raised: var(--ink-raised);
|
|
43
|
-
--color-grey: var(--grey);
|
|
44
|
-
--color-grey-inverse: var(--grey-inverse);
|
|
45
|
-
|
|
46
|
-
/* The yellow — one hue, by area */
|
|
47
|
-
--color-yellow: var(--yellow);
|
|
48
|
-
--color-yellow-accent: var(--yellow-accent);
|
|
49
|
-
--color-yellow-accent-hover: var(--yellow-accent-hover);
|
|
50
|
-
--color-yellow-light: var(--yellow-light);
|
|
51
|
-
--color-yellow-hover: var(--yellow-hover);
|
|
52
|
-
--color-yellow-1: var(--yellow-1);
|
|
53
|
-
--color-yellow-2: var(--yellow-2);
|
|
54
|
-
--color-yellow-3: var(--yellow-3);
|
|
55
|
-
--color-yellow-4: var(--yellow-4);
|
|
56
|
-
--color-yellow-5: var(--yellow-5);
|
|
57
|
-
--color-yellow-wash-12: var(--yellow-12);
|
|
58
|
-
--color-yellow-wash-20: var(--yellow-20);
|
|
59
|
-
--color-yellow-wash-32: var(--yellow-32);
|
|
60
|
-
--color-yellow-wash-50: var(--yellow-50);
|
|
61
|
-
--color-marker: var(--marker);
|
|
62
|
-
|
|
63
|
-
/* Semantic — switch with ink mode. Product code uses these. */
|
|
64
|
-
--color-page: var(--bg-page);
|
|
65
|
-
--color-sunk: var(--bg-sunk);
|
|
66
|
-
--color-well: var(--bg-well);
|
|
67
|
-
--color-chrome: var(--bg-chrome);
|
|
68
|
-
--color-field: var(--bg-field);
|
|
69
|
-
--color-fg: var(--text-primary);
|
|
70
|
-
--color-fg-secondary: var(--text-secondary);
|
|
71
|
-
--color-fg-label: var(--text-label);
|
|
72
|
-
--color-fg-on-yellow-secondary: var(--text-on-yellow-secondary);
|
|
73
|
-
--color-rule: var(--rule);
|
|
74
|
-
--color-rule-soft: var(--rule-soft);
|
|
75
|
-
--color-hover: var(--hover);
|
|
76
|
-
--color-scrim: var(--scrim);
|
|
77
|
-
|
|
78
|
-
/* Workspace brand (brand.css): the COSX yellow unless a customer's
|
|
79
|
-
colour is injected. Selection and grounds use field; dots and
|
|
80
|
-
progress use mark. Never for text. */
|
|
81
|
-
--color-brand-field: var(--brand-field);
|
|
82
|
-
--color-brand-mark: var(--brand-mark);
|
|
83
|
-
|
|
84
|
-
/* On an ink surface inside a paper page */
|
|
85
|
-
--color-inv: var(--inv-bg);
|
|
86
|
-
--color-inv-raised: var(--inv-bg-raised);
|
|
87
|
-
--color-inv-fg: var(--inv-text);
|
|
88
|
-
--color-inv-fg-secondary: var(--inv-text-secondary);
|
|
89
|
-
--color-inv-rule: var(--inv-rule);
|
|
90
|
-
|
|
91
|
-
/* Product status: yellow, ink and one red. Progress is an ink outline and
|
|
92
|
-
complete a grey dot — they take no colour of their own. */
|
|
93
|
-
--color-attention: var(--status-attention);
|
|
94
|
-
--color-attention-wash: var(--status-attention-wash);
|
|
95
|
-
--color-error: var(--status-error);
|
|
96
|
-
--color-error-text: var(--status-error-text);
|
|
97
|
-
--color-error-wash: var(--status-error-wash);
|
|
98
|
-
|
|
99
|
-
--color-transparent: transparent;
|
|
100
|
-
--color-current: currentColor;
|
|
101
|
-
--color-white: #fff;
|
|
102
|
-
|
|
103
|
-
/* Radius ladder — same names and values as 3.0 spacing.css */
|
|
104
|
-
--radius-xs: 4px;
|
|
105
|
-
--radius-sm: 6px;
|
|
106
|
-
--radius-md: 8px;
|
|
107
|
-
--radius-lg: 16px;
|
|
108
|
-
--radius-xl: 24px;
|
|
109
|
-
--radius-pill: 999px;
|
|
110
|
-
|
|
111
|
-
/* One family. Same stacks as fonts.css (the ones in effect): font-sans
|
|
112
|
-
for Latin, font-sans-cjk for Chinese pages (Geist for Latin and
|
|
113
|
-
figures, Noto Sans SC for hanzi). */
|
|
114
|
-
--font-sans: "Geist Variable", "Geist", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
115
|
-
--font-sans-cjk: "Geist Variable", "Geist", "Noto Sans SC Variable", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
116
|
-
|
|
117
|
-
--font-weight-normal: 400;
|
|
118
|
-
--font-weight-medium: 500;
|
|
119
|
-
--font-weight-semibold: 600;
|
|
120
|
-
--font-weight-bold: 700;
|
|
121
|
-
|
|
122
|
-
/* Type scale (typography.css). Product UI body is 14px, content 15px. */
|
|
123
|
-
--text-meta: 12px;
|
|
124
|
-
--text-small: 13.5px;
|
|
125
|
-
--text-ui: 14px;
|
|
126
|
-
--text-body: 15px;
|
|
127
|
-
--text-h3: 18px;
|
|
128
|
-
--text-lede: 20px;
|
|
129
|
-
--text-title: 22px;
|
|
130
|
-
--text-figure: 32px;
|
|
131
|
-
--text-h2: var(--size-h2);
|
|
132
|
-
--text-h1: var(--size-h1);
|
|
133
|
-
--text-display: var(--size-display);
|
|
134
|
-
|
|
135
|
-
--tracking-display: -0.025em;
|
|
136
|
-
--tracking-heading: -0.015em;
|
|
137
|
-
--tracking-figure: -0.03em;
|
|
138
|
-
|
|
139
|
-
/* Motion — flat for state, ease-out for entry; nothing bounces */
|
|
140
|
-
--ease-standard: cubic-bezier(.2,0,.2,1);
|
|
141
|
-
--ease-out: cubic-bezier(.16,1,.3,1);
|
|
142
|
-
--ease-in: cubic-bezier(.4,0,1,1);
|
|
143
|
-
}
|
|
19
|
+
@import "./theme-reset.css";
|
|
20
|
+
@import "./theme-base.css";
|