@cosxai/ui 1.0.0-alpha.3 → 1.0.0-alpha.4

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 CHANGED
@@ -1,3 +1,11 @@
1
+ ## 1.0.0-alpha.4 (2026-09-29)
2
+
3
+ - ToastCard: one toast drawn in place (documentation specimens, previews).
4
+ - theme.css is now theme-reset.css + theme-base.css. An app moving to the
5
+ kit screen by screen imports `@cosxai/ui/theme-base.css`: the kit's
6
+ utilities are added and Tailwind's own palette and sizes keep working.
7
+ New apps keep importing theme.css (unchanged result).
8
+
1
9
  ## 1.0.0-alpha.3 (2026-09-29)
2
10
 
3
11
  - Button: busy and done draw over the idle content, kept in place and
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cosxai/ui",
3
- "version": "1.0.0-alpha.3",
4
- "description": "COSX Design System 3.0 — tokens, Tailwind v4 theme and React components",
3
+ "version": "1.0.0-alpha.4",
4
+ "description": "COSX Design System 3.0 \u2014 tokens, Tailwind v4 theme and React components",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "main": "./src/index.ts",
@@ -10,7 +10,8 @@
10
10
  ".": "./src/index.ts",
11
11
  "./styles.css": "./src/styles.css",
12
12
  "./theme.css": "./src/theme.css",
13
- "./tokens/*": "./src/tokens/*"
13
+ "./tokens/*": "./src/tokens/*",
14
+ "./theme-base.css": "./src/theme-base.css"
14
15
  },
15
16
  "files": [
16
17
  "src",
@@ -91,6 +91,47 @@ const DEFAULT_LABELS: ToastLabels = {
91
91
  region: 'Notifications',
92
92
  };
93
93
 
94
+ const CARD = 'flex w-80 max-w-full items-start gap-3 rounded-[12px] bg-ink px-4 py-3.5 font-sans text-linen ink:border ink:border-inv-rule ink:bg-ink-raised';
95
+ const ACTION =
96
+ 'mt-2.5 inline-flex h-8 cursor-pointer items-center rounded-md border border-inv-rule bg-transparent px-3 text-[13px] font-semibold text-linen outline-none hover:border-linen focus-visible:shadow-[0_0_0_3px_var(--ink),0_0_0_5px_var(--yellow)]';
97
+ const CLOSE = 'flex cursor-pointer rounded-xs border-0 bg-transparent p-0.5 text-grey-inverse outline-none hover:text-linen focus-visible:shadow-[0_0_0_2px_var(--yellow)]';
98
+ const statusLabelClass = (status: ToastStatus) => cn('mb-0.5 text-meta font-medium', status === 'attention' ? 'text-yellow-accent' : 'text-grey-inverse');
99
+
100
+ export type ToastCardProps = Omit<ToastOptions, 'duration' | 'action'> & {
101
+ /** A label, or the full action (label + onClick). */
102
+ action?: string | ToastOptions['action'];
103
+ onClose?: (() => void) | undefined;
104
+ labels?: Partial<ToastLabels> | undefined;
105
+ className?: string | undefined;
106
+ };
107
+
108
+ /**
109
+ * ToastCard — one toast drawn in place, not queued: documentation
110
+ * specimens and previews. In the product, call toast() instead.
111
+ */
112
+ export function ToastCard({ title, description, status = 'neutral', action, onClose, labels, className }: ToastCardProps) {
113
+ const l = { ...DEFAULT_LABELS, ...labels };
114
+ const act = typeof action === 'string' ? { label: action, onClick: () => {} } : action;
115
+ return (
116
+ <div className={cn(CARD, className)}>
117
+ <span aria-hidden className={cn('mt-[5px] size-2 shrink-0 rounded-pill', DOT[status])} />
118
+ <div className="min-w-0 flex-1">
119
+ <div className={statusLabelClass(status)}>{l[status]}</div>
120
+ <div className="text-ui leading-[1.4] font-medium">{title}</div>
121
+ {description && <div className="mt-0.5 text-[13px] leading-normal text-grey-inverse">{description}</div>}
122
+ {act && (
123
+ <button type="button" onClick={act.onClick} className={ACTION}>
124
+ {act.label}
125
+ </button>
126
+ )}
127
+ </div>
128
+ <button type="button" aria-label={l.dismiss} onClick={onClose} className={CLOSE}>
129
+ <X size={14} strokeWidth={2} aria-hidden />
130
+ </button>
131
+ </div>
132
+ );
133
+ }
134
+
94
135
  export type ToasterProps = {
95
136
  /** Wording of the status labels and controls (e.g. Chinese). */
96
137
  labels?: Partial<ToastLabels> | undefined;
@@ -120,22 +161,18 @@ export function Toaster({ labels, offset = 24 }: ToasterProps) {
120
161
  }}
121
162
  duration={t.duration ?? (t.action ? 8000 : 4000)}
122
163
  type={status === 'error' ? 'foreground' : 'background'}
123
- className={cn(
124
- 'flex w-80 max-w-full items-start gap-3 rounded-[12px] bg-ink px-4 py-3.5 font-sans text-linen',
125
- 'ink:border ink:border-inv-rule ink:bg-ink-raised',
126
- 'data-[swipe=move]:translate-x-(--radix-toast-swipe-move-x) data-[swipe=end]:hidden',
127
- )}
164
+ className={cn(CARD, 'data-[swipe=move]:translate-x-(--radix-toast-swipe-move-x) data-[swipe=end]:hidden')}
128
165
  >
129
166
  <span aria-hidden className={cn('mt-[5px] size-2 shrink-0 rounded-pill', DOT[status])} />
130
167
  <div className="min-w-0 flex-1">
131
- <div className={cn('mb-0.5 text-meta font-medium', status === 'attention' ? 'text-yellow-accent' : 'text-grey-inverse')}>{l[status]}</div>
168
+ <div className={statusLabelClass(status)}>{l[status]}</div>
132
169
  <ToastPrimitive.Title className="text-ui leading-[1.4] font-medium">{t.title}</ToastPrimitive.Title>
133
170
  {t.description && <ToastPrimitive.Description className="mt-0.5 text-[13px] leading-normal text-grey-inverse">{t.description}</ToastPrimitive.Description>}
134
171
  {t.action && (
135
172
  <ToastPrimitive.Action
136
173
  altText={t.action.altText ?? t.action.label}
137
174
  onClick={t.action.onClick}
138
- className="mt-2.5 inline-flex h-8 cursor-pointer items-center rounded-md border border-inv-rule bg-transparent px-3 text-[13px] font-semibold text-linen outline-none hover:border-linen focus-visible:shadow-[0_0_0_3px_var(--ink),0_0_0_5px_var(--yellow)]"
175
+ className={ACTION}
139
176
  >
140
177
  {t.action.label}
141
178
  </ToastPrimitive.Action>
@@ -143,7 +180,7 @@ export function Toaster({ labels, offset = 24 }: ToasterProps) {
143
180
  </div>
144
181
  <ToastPrimitive.Close
145
182
  aria-label={l.dismiss}
146
- className="flex cursor-pointer rounded-xs border-0 bg-transparent p-0.5 text-grey-inverse outline-none hover:text-linen focus-visible:shadow-[0_0_0_2px_var(--yellow)]"
183
+ className={CLOSE}
147
184
  >
148
185
  <X size={14} strokeWidth={2} aria-hidden />
149
186
  </ToastPrimitive.Close>
package/src/index.ts CHANGED
@@ -31,7 +31,7 @@ export { Switch, type SwitchProps } from './components/Switch';
31
31
  export { Table, type TableColumn, type TableProps, type TableRowBase, type TableSort, type TableSortDirection } from './components/Table';
32
32
  export { Tab, TabList, TabPanel, Tabs, type TabItem, type TabListProps, type TabPanelProps, type TabProps, type TabsProps, type TabsVariant } from './components/Tabs';
33
33
  export { Textarea, type TextareaProps } from './components/Textarea';
34
- export { Toaster, toast, type ToasterProps, type ToastLabels, type ToastOptions, type ToastStatus } from './components/Toast';
34
+ export { ToastCard, Toaster, toast, type ToastCardProps, type ToasterProps, type ToastLabels, type ToastOptions, type ToastStatus } from './components/Toast';
35
35
  export { Tooltip, TooltipProvider, type TooltipProps } from './components/Tooltip';
36
36
 
37
37
  // Stage 2 — product patterns
@@ -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
- /* ink: — styles for ink mode (data-mode="ink" on <html>, or inside
20
- .ink-mode). The semantic colours switch by themselves; this is for the
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";