@artkit-ai/react 0.0.0-stage → 0.2.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.
Files changed (103) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +145 -2
  3. package/dist/cache.d.ts +31 -0
  4. package/dist/cache.d.ts.map +1 -0
  5. package/dist/cache.js +95 -0
  6. package/dist/cache.js.map +1 -0
  7. package/dist/client.d.ts +68 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +233 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/components/card.d.ts +22 -0
  12. package/dist/components/card.d.ts.map +1 -0
  13. package/dist/components/card.js +16 -0
  14. package/dist/components/card.js.map +1 -0
  15. package/dist/components/editor.d.ts +41 -0
  16. package/dist/components/editor.d.ts.map +1 -0
  17. package/dist/components/editor.js +111 -0
  18. package/dist/components/editor.js.map +1 -0
  19. package/dist/components/icons.d.ts +8 -0
  20. package/dist/components/icons.d.ts.map +1 -0
  21. package/dist/components/icons.js +14 -0
  22. package/dist/components/icons.js.map +1 -0
  23. package/dist/components/panel.d.ts +31 -0
  24. package/dist/components/panel.d.ts.map +1 -0
  25. package/dist/components/panel.js +132 -0
  26. package/dist/components/panel.js.map +1 -0
  27. package/dist/components/shared.d.ts +67 -0
  28. package/dist/components/shared.d.ts.map +1 -0
  29. package/dist/components/shared.js +100 -0
  30. package/dist/components/shared.js.map +1 -0
  31. package/dist/components/view.d.ts +39 -0
  32. package/dist/components/view.d.ts.map +1 -0
  33. package/dist/components/view.js +76 -0
  34. package/dist/components/view.js.map +1 -0
  35. package/dist/context.d.ts +64 -0
  36. package/dist/context.d.ts.map +1 -0
  37. package/dist/context.js +13 -0
  38. package/dist/context.js.map +1 -0
  39. package/dist/controls.d.ts +145 -0
  40. package/dist/controls.d.ts.map +1 -0
  41. package/dist/controls.js +188 -0
  42. package/dist/controls.js.map +1 -0
  43. package/dist/download.d.ts +4 -0
  44. package/dist/download.d.ts.map +1 -0
  45. package/dist/download.js +20 -0
  46. package/dist/download.js.map +1 -0
  47. package/dist/editor-session.d.ts +96 -0
  48. package/dist/editor-session.d.ts.map +1 -0
  49. package/dist/editor-session.js +192 -0
  50. package/dist/editor-session.js.map +1 -0
  51. package/dist/hooks.d.ts +64 -0
  52. package/dist/hooks.d.ts.map +1 -0
  53. package/dist/hooks.js +205 -0
  54. package/dist/hooks.js.map +1 -0
  55. package/dist/i18n.d.ts +135 -0
  56. package/dist/i18n.d.ts.map +1 -0
  57. package/dist/i18n.js +145 -0
  58. package/dist/i18n.js.map +1 -0
  59. package/dist/index.d.ts +19 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +18 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/kit-css.d.ts +3 -0
  64. package/dist/kit-css.d.ts.map +1 -0
  65. package/dist/kit-css.js +5 -0
  66. package/dist/kit-css.js.map +1 -0
  67. package/dist/partial-json.d.ts +8 -0
  68. package/dist/partial-json.d.ts.map +1 -0
  69. package/dist/partial-json.js +176 -0
  70. package/dist/partial-json.js.map +1 -0
  71. package/dist/provider.d.ts +42 -0
  72. package/dist/provider.d.ts.map +1 -0
  73. package/dist/provider.js +95 -0
  74. package/dist/provider.js.map +1 -0
  75. package/dist/styles.d.ts +25 -0
  76. package/dist/styles.d.ts.map +1 -0
  77. package/dist/styles.js +203 -0
  78. package/dist/styles.js.map +1 -0
  79. package/dist/toolbar.d.ts +90 -0
  80. package/dist/toolbar.d.ts.map +1 -0
  81. package/dist/toolbar.js +233 -0
  82. package/dist/toolbar.js.map +1 -0
  83. package/package.json +50 -3
  84. package/src/cache.ts +109 -0
  85. package/src/client.ts +342 -0
  86. package/src/components/card.tsx +86 -0
  87. package/src/components/editor.tsx +292 -0
  88. package/src/components/icons.tsx +14 -0
  89. package/src/components/panel.tsx +334 -0
  90. package/src/components/shared.tsx +198 -0
  91. package/src/components/view.tsx +196 -0
  92. package/src/context.ts +87 -0
  93. package/src/controls.tsx +587 -0
  94. package/src/download.ts +23 -0
  95. package/src/editor-session.ts +260 -0
  96. package/src/hooks.ts +296 -0
  97. package/src/i18n.ts +155 -0
  98. package/src/index.ts +128 -0
  99. package/src/kit-css.ts +5 -0
  100. package/src/partial-json.ts +160 -0
  101. package/src/provider.tsx +176 -0
  102. package/src/styles.ts +222 -0
  103. package/src/toolbar.tsx +536 -0
package/src/styles.ts ADDED
@@ -0,0 +1,222 @@
1
+ import {
2
+ COLOR_TOKENS,
3
+ defaultDarkTheme,
4
+ defaultTheme,
5
+ normalizeHex,
6
+ type Theme,
7
+ themeCssVariables,
8
+ } from '@artkit-ai/core';
9
+ import { kitCss } from './kit-css.js';
10
+
11
+ function channels(hex: string): [number, number, number] {
12
+ const value = normalizeHex(hex).slice(1);
13
+ return [0, 2, 4].map((i) => Number.parseInt(value.slice(i, i + 2), 16)) as [number, number, number];
14
+ }
15
+
16
+ function toHex([r, g, b]: [number, number, number]): string {
17
+ return `#${[r, g, b]
18
+ .map((c) =>
19
+ Math.round(Math.min(255, Math.max(0, c)))
20
+ .toString(16)
21
+ .padStart(2, '0'),
22
+ )
23
+ .join('')
24
+ .toUpperCase()}`;
25
+ }
26
+
27
+ /** Linear mix of two hex colors; `amount` is the share of `b`. */
28
+ export function mixColors(a: string, b: string, amount: number): string {
29
+ const ca = channels(a);
30
+ const cb = channels(b);
31
+ return toHex([0, 1, 2].map((i) => ca[i]! + (cb[i]! - ca[i]!) * amount) as [number, number, number]);
32
+ }
33
+
34
+ /**
35
+ * Dark variant of a theme (spec §13.5). The default (Mouts) theme has an official dark version; other
36
+ * themes are derived: text and background swap, surfaces sit between them and status colors are
37
+ * lightened so they keep contrast on the dark background.
38
+ */
39
+ export function darkTheme(theme: Theme): Theme {
40
+ if (theme.id === defaultTheme.id) return defaultDarkTheme;
41
+ const { text, background } = theme.colors;
42
+ const darkBackground = mixColors(text, '#000000', 0.55);
43
+ const lighten = (color: string) => mixColors(color, '#FFFFFF', 0.3);
44
+ return {
45
+ ...theme,
46
+ id: `${theme.id}-dark`,
47
+ colors: {
48
+ text: mixColors(background, text, 0.08),
49
+ muted: mixColors(theme.colors.muted, background, 0.35),
50
+ accent: lighten(theme.colors.accent),
51
+ background: darkBackground,
52
+ surface: mixColors(darkBackground, background, 0.08),
53
+ info: lighten(theme.colors.info),
54
+ warning: lighten(theme.colors.warning),
55
+ success: lighten(theme.colors.success),
56
+ danger: lighten(theme.colors.danger),
57
+ },
58
+ chartPalette: theme.chartPalette.map(lighten),
59
+ ...(theme.brand
60
+ ? {
61
+ brand: {
62
+ ...theme.brand,
63
+ cardFrom: mixColors(darkBackground, background, 0.04),
64
+ cardTo: mixColors(darkBackground, theme.brand.primaryDeep, 0.2),
65
+ focus: theme.brand.primarySoft,
66
+ },
67
+ }
68
+ : {}),
69
+ };
70
+ }
71
+
72
+ // Theme values end up inside a <style> element. Colors are validated hex and fonts are sanitized by
73
+ // the core (safeFontFamily), gradients and radii are built from those values only.
74
+ function declarations(theme: Theme): string {
75
+ const parts: string[] = [];
76
+ for (const [name, value] of Object.entries(themeCssVariables(theme))) parts.push(`${name}:${value};`);
77
+ // Diff highlights used by the version comparison.
78
+ parts.push(`--ak-color-added-bg:${mixColors(theme.colors.background, theme.colors.success, 0.14)};`);
79
+ parts.push(`--ak-color-removed-bg:${mixColors(theme.colors.background, theme.colors.danger, 0.12)};`);
80
+ return parts.join('');
81
+ }
82
+
83
+ function attr(value: string): string {
84
+ return value.replace(/[\\"]/g, (c) => `\\${c}`).replace(/[\n\r]/g, ' ');
85
+ }
86
+
87
+ /**
88
+ * Theme variables for every known theme, light and dark. `.ak-root` takes the application theme;
89
+ * `[data-ak-theme="<id>"]` takes an artifact theme. Dark applies with `prefers-color-scheme: dark` unless
90
+ * `data-theme="light"` is set on the root or an ancestor, and always with `data-theme="dark"`.
91
+ */
92
+ export function themeStyles(appTheme: Theme, themes: Theme[], dark: Map<string, Theme> = new Map()): string {
93
+ const rules: string[] = [];
94
+ const darkOf = (theme: Theme) => dark.get(theme.id) ?? darkTheme(theme);
95
+ const entries: { selector: string; theme: Theme }[] = [
96
+ { selector: '.ak-root', theme: appTheme },
97
+ ...themes.map((theme) => ({ selector: `.ak-root[data-ak-theme="${attr(theme.id)}"]`, theme })),
98
+ ];
99
+ for (const { selector, theme } of entries)
100
+ rules.push(`${selector}{${declarations(theme)}color-scheme:light;}`);
101
+ const darkRules = entries.map(({ selector, theme }) => ({
102
+ selector,
103
+ body: `${declarations(darkOf(theme))}color-scheme:dark;`,
104
+ }));
105
+ rules.push(
106
+ `@media (prefers-color-scheme: dark){${darkRules
107
+ .map(
108
+ ({ selector, body }) => `${selector}:not([data-theme="light"]):not([data-theme="light"] *){${body}}`,
109
+ )
110
+ .join('')}}`,
111
+ );
112
+ for (const { selector, body } of darkRules) {
113
+ rules.push(`${selector}[data-theme="dark"],[data-theme="dark"] ${selector}{${body}}`);
114
+ }
115
+ return rules.join('\n');
116
+ }
117
+
118
+ /** Stable short hash used to key style resources (React 19 dedupes `<style href>` by key). */
119
+ export function hashString(text: string): string {
120
+ let hash = 5381;
121
+ for (let i = 0; i < text.length; i++) hash = ((hash << 5) + hash + text.charCodeAt(i)) | 0;
122
+ return (hash >>> 0).toString(36);
123
+ }
124
+
125
+ export const COLOR_VARIABLES = COLOR_TOKENS.map((token) => `--ak-color-${token}`);
126
+
127
+ /**
128
+ * Base CSS of the host components, following the Mouts design system: Sora typography, 1px hairlines,
129
+ * no shadows, 32px controls with 6px corners, a single filled (gradient) action per area, 2px focus
130
+ * ring with a 2px offset, underlined tabs and pill badges. Every value comes from theme variables.
131
+ */
132
+ export const artkitCss = `
133
+ .ak-root{color:var(--ak-color-text);background:var(--ak-color-background);font-family:var(--ak-font-body);font-size:14px;line-height:1.36;-webkit-font-smoothing:antialiased;box-sizing:border-box}
134
+ .ak-root *,.ak-root *::before,.ak-root *::after{box-sizing:inherit}
135
+ .ak-root :focus-visible{outline:2px solid var(--ak-color-focus);outline-offset:2px}
136
+ .ak-root svg.lucide{width:16px;height:16px;flex-shrink:0}
137
+ .ak-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
138
+ .ak-button{display:inline-flex;align-items:center;justify-content:center;gap:8px;height:32px;padding:0 12px;font:inherit;font-weight:600;font-size:14px;white-space:nowrap;border:1px solid var(--ak-color-border);border-radius:var(--ak-radius-sm);background:var(--ak-color-background);color:var(--ak-color-text);cursor:pointer;transition:background-color .15s,border-color .15s,color .15s}
139
+ .ak-button:hover:not(:disabled){background:var(--ak-color-hover);border-color:var(--ak-color-border-strong)}
140
+ .ak-button:active:not(:disabled){background:var(--ak-color-active)}
141
+ .ak-button:disabled{opacity:.4;cursor:not-allowed}
142
+ .ak-button[data-variant="primary"]{border-color:transparent;background:var(--ak-gradient-button);color:var(--ak-color-on-primary);border-radius:var(--ak-radius-pill)}
143
+ .ak-button[data-variant="primary"]:hover:not(:disabled){background:var(--ak-gradient-button-hover);border-color:transparent}
144
+ .ak-button[data-variant="primary"]:active:not(:disabled){background:var(--ak-gradient-button-active)}
145
+ .ak-button[data-variant="quiet"]{border-color:transparent;background:transparent;color:var(--ak-color-muted)}
146
+ .ak-button[data-variant="quiet"]:hover:not(:disabled){color:var(--ak-color-text);border-color:transparent}
147
+ .ak-button[data-variant="danger"]{border-color:transparent;background:transparent;color:var(--ak-color-danger)}
148
+ .ak-button[data-variant="danger"]:hover:not(:disabled){background:var(--ak-color-fill-danger);border-color:transparent}
149
+ .ak-button[data-size="sm"]{height:28px;padding:0 10px;font-size:12px}
150
+ .ak-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:999px;font-size:12px;font-weight:600;line-height:16px;border:1px solid var(--ak-color-border);background:var(--ak-color-hover);color:var(--ak-color-muted);white-space:nowrap}
151
+ .ak-badge::before{content:"";display:none;width:6px;height:6px;border-radius:50%;background:currentColor}
152
+ .ak-badge[data-status]::before{display:inline-block}
153
+ .ak-badge[data-status="pending_approval"]{color:var(--ak-color-warning);border-color:var(--ak-color-warning);background:var(--ak-color-fill-warning)}
154
+ .ak-badge[data-status="published"]{color:var(--ak-color-success);border-color:var(--ak-color-success);background:var(--ak-color-fill-accent)}
155
+ .ak-badge[data-status="archived"]{border-style:dashed;background:transparent}
156
+ .ak-badge[data-variant="provisional"]{color:var(--ak-color-accent);border-color:var(--ak-color-accent);background:var(--ak-color-fill-accent)}
157
+ .ak-alert{display:flex;gap:8px;align-items:flex-start;padding:12px;border-radius:var(--ak-radius-sm);border:1px solid var(--ak-color-danger);color:var(--ak-color-danger);background:var(--ak-color-fill-danger)}
158
+ .ak-notice{display:flex;gap:8px;align-items:flex-start;padding:12px;border-radius:var(--ak-radius-sm);border:1px solid var(--ak-color-border);color:var(--ak-color-text);background:var(--ak-color-surface)}
159
+ .ak-loading{display:flex;align-items:center;gap:8px;padding:16px;color:var(--ak-color-muted)}
160
+ .ak-loading::before{content:"";width:16px;height:16px;border-radius:50%;border:2px solid var(--ak-color-border);border-top-color:var(--ak-color-accent);animation:ak-spin .8s linear infinite}
161
+ @keyframes ak-spin{to{transform:rotate(360deg)}}
162
+ @media (prefers-reduced-motion:reduce){.ak-loading::before{animation:none}}
163
+ .ak-code{margin:0;padding:16px;overflow:auto;font-family:var(--ak-font-mono);font-size:12px;line-height:1.5;background:var(--ak-color-surface);border:1px solid var(--ak-color-border);border-radius:var(--ak-radius-md);white-space:pre}
164
+ .ak-view{position:relative}
165
+ .ak-view[data-provisional="true"] .ak-view-body{opacity:.85}
166
+ .ak-view-badge{position:absolute;top:8px;right:8px;z-index:1}
167
+ .ak-card{display:flex;align-items:center;gap:12px;padding:12px 16px;border:1px solid var(--ak-color-border);border-radius:var(--ak-radius);background:var(--ak-gradient-card);max-width:480px;transition:border-color .15s}
168
+ .ak-card:hover{border-color:var(--ak-color-border-strong)}
169
+ .ak-card-icon{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;flex-shrink:0;border-radius:var(--ak-radius-md);background:var(--ak-color-fill-accent);color:var(--ak-color-accent)}
170
+ .ak-card-icon svg{width:18px;height:18px}
171
+ .ak-card-body{flex:1;min-width:0}
172
+ .ak-card-title{font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin:0 0 4px;font-size:14px;font-family:var(--ak-font-heading)}
173
+ .ak-card-meta{display:flex;flex-wrap:wrap;gap:6px;align-items:center;color:var(--ak-color-muted);font-size:12px}
174
+ .ak-panel{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--ak-color-background);border:1px solid var(--ak-color-border);border-radius:var(--ak-radius);overflow:hidden}
175
+ .ak-panel-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:16px;border-bottom:1px solid var(--ak-color-border-soft)}
176
+ .ak-panel-title{flex:1;min-width:120px;margin:0;font-size:16px;line-height:22px;font-weight:700;font-family:var(--ak-font-heading)}
177
+ .ak-panel-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:12px 16px;border-bottom:1px solid var(--ak-color-border-soft)}
178
+ .ak-panel-body{flex:1;min-height:0;overflow:auto;padding:16px}
179
+ .ak-tabs{display:flex;gap:4px;padding:0 16px;border-bottom:1px solid var(--ak-color-border-soft);overflow-x:auto;scrollbar-width:none}
180
+ .ak-tab{display:inline-flex;align-items:center;gap:8px;height:36px;padding:0 12px;font:inherit;font-size:14px;font-weight:600;border:0;border-bottom:2px solid transparent;background:none;color:var(--ak-color-muted);cursor:pointer;white-space:nowrap}
181
+ .ak-tab:hover{color:var(--ak-color-text)}
182
+ .ak-tab[aria-selected="true"]{color:var(--ak-color-text);border-bottom-color:var(--ak-color-primary)}
183
+ .ak-select{height:32px;font:inherit;font-size:14px;padding:0 10px;border:1px solid var(--ak-color-border-strong);border-radius:var(--ak-radius-sm);background:var(--ak-color-background);color:var(--ak-color-text);cursor:pointer}
184
+ .ak-select:hover{border-color:var(--ak-color-muted)}
185
+ .ak-menu{position:relative;display:inline-block}
186
+ .ak-menu-list{position:absolute;right:0;z-index:10;min-width:180px;margin:4px 0 0;padding:4px;list-style:none;background:var(--ak-color-background);border:1px solid var(--ak-color-border-strong);border-radius:var(--ak-radius-md)}
187
+ .ak-menu-item{display:flex;align-items:center;gap:8px;width:100%;text-align:left;font:inherit;padding:8px 10px;border:0;border-radius:var(--ak-radius-sm);background:none;color:var(--ak-color-text);cursor:pointer}
188
+ .ak-menu-item:hover,.ak-menu-item:focus{background:var(--ak-color-hover)}
189
+ .ak-history{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}
190
+ .ak-history-entry{padding:12px;border:1px solid var(--ak-color-border);border-radius:var(--ak-radius-md);background:var(--ak-color-background)}
191
+ .ak-history-entry[aria-current="true"]{border-color:var(--ak-color-accent);background:var(--ak-color-fill-accent)}
192
+ .ak-history-head{display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-weight:600}
193
+ .ak-history-meta{color:var(--ak-color-muted);font-size:12px;font-weight:400}
194
+ .ak-history-actions{display:flex;gap:6px;margin-left:auto}
195
+ .ak-changes{list-style:none;margin:8px 0 0;padding:0;display:flex;flex-direction:column;gap:4px}
196
+ .ak-change{display:flex;gap:8px;align-items:baseline;font-size:13px}
197
+ .ak-change-type{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;min-width:72px}
198
+ .ak-change[data-type="added"] .ak-change-type{color:var(--ak-color-success)}
199
+ .ak-change[data-type="removed"] .ak-change-type{color:var(--ak-color-danger)}
200
+ .ak-change[data-type="changed"] .ak-change-type{color:var(--ak-color-warning)}
201
+ .ak-change[data-type="moved"] .ak-change-type{color:var(--ak-color-info)}
202
+ .ak-change-target{font-family:var(--ak-font-mono);font-size:12px;color:var(--ak-color-muted)}
203
+ .ak-diff{display:flex;flex-direction:column;gap:12px}
204
+ .ak-diff-toolbar{display:flex;gap:4px;border:0;margin:0;padding:0;min-inline-size:0}
205
+ .ak-diff-lines{width:100%;border-collapse:collapse;font-family:var(--ak-font-mono);font-size:12px;table-layout:fixed;border:1px solid var(--ak-color-border);border-radius:var(--ak-radius-md)}
206
+ .ak-diff-lines td,.ak-diff-lines th{padding:2px 8px;vertical-align:top;white-space:pre-wrap;word-break:break-word}
207
+ .ak-diff-lines th{font-weight:normal;color:var(--ak-color-muted);text-align:right;width:3.5em;user-select:none}
208
+ .ak-diff-marker{width:1.5em;user-select:none;color:var(--ak-color-muted)}
209
+ .ak-diff-line[data-type="insert"],.ak-diff-cell[data-type="insert"]{background:var(--ak-color-added-bg)}
210
+ .ak-diff-line[data-type="delete"],.ak-diff-cell[data-type="delete"]{background:var(--ak-color-removed-bg)}
211
+ .ak-approval{display:flex;flex-direction:column;gap:16px}
212
+ .ak-approval-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-start}
213
+ .ak-textarea{font:inherit;font-size:14px;width:100%;min-height:72px;padding:8px 12px;border:1px solid var(--ak-color-border-strong);border-radius:var(--ak-radius-sm);background:var(--ak-color-background);color:var(--ak-color-text);resize:vertical}
214
+ .ak-textarea:hover{border-color:var(--ak-color-muted)}
215
+ .ak-form{display:flex;flex-direction:column;gap:8px;width:100%}
216
+ .ak-editor{display:flex;flex-direction:column;gap:12px}
217
+ .ak-editor-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
218
+ .ak-editor-status{display:inline-flex;align-items:center;gap:6px;margin-left:auto;color:var(--ak-color-muted);font-size:12px}
219
+ .ak-conflict{padding:12px 16px;border:1px solid var(--ak-color-warning);border-radius:var(--ak-radius-md);background:var(--ak-color-fill-warning);display:flex;flex-direction:column;gap:8px}
220
+ .ak-conflict-title{margin:0;font-size:14px;font-weight:700}
221
+ ${kitCss}
222
+ `;