@digithings-ai/design 0.0.0-stage → 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.
@@ -0,0 +1,59 @@
1
+ /* ==========================================================================
2
+ @digithings-ai/design — app-shell-terminal/slash-commands
3
+ --------------------------------------------------------------------------
4
+ Registry + parser + dispatcher for slash commands. Used by the app shell's
5
+ input bar and Cmd+K palette.
6
+ ========================================================================== */
7
+
8
+ export class SlashCommandRegistry {
9
+ constructor() {
10
+ this._commands = new Map();
11
+ }
12
+
13
+ /** Register a command.
14
+ * @param {string} name — e.g. "help" (no leading slash).
15
+ * @param {{ description?: string, handler: (args: string[]) => unknown }} spec
16
+ */
17
+ register(name, spec) {
18
+ if (!name || typeof name !== 'string') throw new Error('register: name required');
19
+ if (!spec || typeof spec.handler !== 'function') throw new Error('register: handler required');
20
+ const clean = name.replace(/^\//, '');
21
+ this._commands.set(clean, {
22
+ description: spec.description || '',
23
+ handler: spec.handler,
24
+ });
25
+ }
26
+
27
+ /** Unregister a command by name. */
28
+ unregister(name) {
29
+ this._commands.delete(name.replace(/^\//, ''));
30
+ }
31
+
32
+ /** List all commands for a palette. */
33
+ list() {
34
+ return Array.from(this._commands.entries()).map(([name, spec]) => ({
35
+ name,
36
+ description: spec.description,
37
+ }));
38
+ }
39
+
40
+ /** Parse `/cmd arg1 arg2` → { name, args } or null if not a command. */
41
+ parse(input) {
42
+ if (typeof input !== 'string') return null;
43
+ const trimmed = input.trim();
44
+ if (!trimmed.startsWith('/')) return null;
45
+ const parts = trimmed.slice(1).split(/\s+/);
46
+ const name = parts.shift();
47
+ if (!name) return null;
48
+ return { name, args: parts };
49
+ }
50
+
51
+ /** Parse + invoke. Returns the handler's result, or undefined if no match. */
52
+ dispatch(input) {
53
+ const parsed = this.parse(input);
54
+ if (!parsed) return undefined;
55
+ const spec = this._commands.get(parsed.name);
56
+ if (!spec) return undefined;
57
+ return spec.handler(parsed.args);
58
+ }
59
+ }
@@ -0,0 +1,234 @@
1
+ /* ==========================================================================
2
+ @digithings-ai/design — app-shell-terminal/styles.css
3
+ Scoped under `.app-shell*`, `.app-sidebar*`, `.app-main*`, `.app-topbar*`,
4
+ `.app-input*`, `.slash-hint`, `.shell-cmd-ref`. Consumes tokens.
5
+ ========================================================================== */
6
+
7
+ /* Component sizing tokens. The shell, its bars and the palette share one
8
+ four-value rhythm (0.625rem bar padding, 0.375rem chip padding, 0.75rem
9
+ palette gap); none of those steps exist on the global --space-* scale, so
10
+ they live here rather than inventing a second global scale. Declared on the
11
+ host, which wraps the sidebar, top bar, input bar and palette. */
12
+ .app-shell {
13
+ --app-sidebar-min: 13.75rem;
14
+ --app-sidebar-max: 16.25rem;
15
+ --app-bar-pad-block: 0.625rem;
16
+ --app-bar-pad-inline: var(--space-3);
17
+ --app-chip-pad-block: 0.375rem;
18
+ --app-input-max-h: 12.5rem;
19
+ --app-palette-w: 35rem;
20
+ --app-palette-pad-block: 0.875rem;
21
+ --app-palette-gap: 0.75rem;
22
+
23
+ display: grid;
24
+ grid-template-columns: minmax(var(--app-sidebar-min), var(--app-sidebar-max)) minmax(0, 1fr);
25
+ min-height: 100dvh;
26
+ background: var(--bg);
27
+ color: var(--ink);
28
+ font-family: var(--font-family);
29
+ transition: grid-template-columns var(--dur) var(--ease);
30
+ }
31
+ .app-shell.app-shell-sidebar-collapsed {
32
+ grid-template-columns: 0 minmax(0, 1fr);
33
+ }
34
+ .app-shell.app-shell-reduced-motion {
35
+ transition: none;
36
+ }
37
+
38
+ /* --- Sidebar ----------------------------------------------------------- */
39
+ .app-sidebar {
40
+ border-right: 1px solid var(--hair);
41
+ background: var(--surface);
42
+ overflow: hidden auto;
43
+ font-family: var(--font-family-mono);
44
+ font-size: var(--text-sm);
45
+ color: var(--ink-soft);
46
+ }
47
+ .app-shell-sidebar-collapsed .app-sidebar {
48
+ border-right: none;
49
+ }
50
+ .app-sidebar-body { padding: var(--space-4); }
51
+
52
+ .app-sidebar-section {
53
+ margin-bottom: var(--space-4);
54
+ }
55
+ .app-sidebar-section h3 {
56
+ margin: 0 0 var(--space-2);
57
+ font-size: var(--text-xs);
58
+ letter-spacing: 0.14em;
59
+ text-transform: uppercase;
60
+ color: var(--ink-soft);
61
+ font-weight: 600;
62
+ }
63
+ .app-sidebar-section ul {
64
+ list-style: none;
65
+ padding: 0;
66
+ margin: 0;
67
+ }
68
+ .app-sidebar-section li {
69
+ padding: var(--space-1) 0;
70
+ color: var(--ink);
71
+ }
72
+
73
+ /* --- Main column ------------------------------------------------------- */
74
+ .app-shell-main-col {
75
+ display: grid;
76
+ grid-template-rows: auto minmax(0, 1fr) auto;
77
+ min-height: 100dvh;
78
+ min-width: 0;
79
+ }
80
+
81
+ /* --- Top bar ----------------------------------------------------------- */
82
+ .app-topbar {
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: space-between;
86
+ padding: var(--app-bar-pad-block) var(--app-bar-pad-inline);
87
+ border-bottom: 1px solid var(--hair);
88
+ /* The terminal ground, not a raw near-black: the literal was invisible-ink
89
+ in the light theme, where a fixed dark scrim under `--ink-soft` measured
90
+ 1.28:1. */
91
+ background: var(--term-bg);
92
+ font-family: var(--font-family-mono);
93
+ font-size: 0.75rem;
94
+ color: var(--ink-soft);
95
+ }
96
+ .app-topbar-title { color: var(--ink); letter-spacing: 0.02em; }
97
+ .app-topbar-meta { color: var(--ink-soft); }
98
+
99
+ /* --- Main slot --------------------------------------------------------- */
100
+ .app-main {
101
+ padding: var(--space-5);
102
+ overflow: auto;
103
+ }
104
+
105
+ /* --- Input bar --------------------------------------------------------- */
106
+ .app-input {
107
+ display: grid;
108
+ grid-template-columns: auto minmax(0, 1fr) auto;
109
+ align-items: center;
110
+ gap: var(--app-bar-pad-block);
111
+ padding: var(--app-bar-pad-block) var(--app-bar-pad-inline);
112
+ border-top: 1px solid var(--hair);
113
+ background: var(--surface);
114
+ font-family: var(--font-family-mono);
115
+ }
116
+ .app-input-marker {
117
+ color: var(--accent, var(--ink-soft));
118
+ user-select: none;
119
+ }
120
+ .app-input-field {
121
+ width: 100%;
122
+ min-height: 1.8em;
123
+ max-height: var(--app-input-max-h);
124
+ padding: var(--app-chip-pad-block) var(--space-2);
125
+ background: transparent;
126
+ border: 1px solid transparent;
127
+ border-radius: var(--radius-sm);
128
+ color: var(--ink);
129
+ font-family: var(--font-family-mono);
130
+ font-size: 0.875rem;
131
+ line-height: 1.5;
132
+ resize: none;
133
+ outline: none;
134
+ }
135
+ .app-input-field:focus {
136
+ border-color: var(--accent, var(--hair));
137
+ }
138
+ /* `outline: none` above needs a keyboard equivalent: a 1px border tint is not
139
+ a focus indicator at a glance, and it is the same weight as the idle border. */
140
+ .app-input-field:focus-visible {
141
+ outline: 2px solid var(--accent, var(--ink));
142
+ outline-offset: 2px;
143
+ }
144
+ .slash-hint {
145
+ font-size: var(--text-xs);
146
+ color: var(--ink-soft);
147
+ }
148
+ .slash-hint kbd,
149
+ .shell-cmd-ref {
150
+ font-family: var(--font-family-mono);
151
+ font-size: var(--text-xs);
152
+ padding: 0.125rem 0.375rem;
153
+ border-radius: 0;
154
+ background: var(--term-fill);
155
+ border: 1px solid var(--hair);
156
+ color: var(--ink);
157
+ }
158
+
159
+ /* --- Command palette --------------------------------------------------- */
160
+ .app-shell-palette {
161
+ position: fixed;
162
+ inset: 0;
163
+ display: grid;
164
+ place-items: start center;
165
+ padding-top: 12vh;
166
+ background: rgba(0, 0, 0, 0.55);
167
+ backdrop-filter: blur(6px);
168
+ z-index: 100;
169
+ }
170
+ .app-shell-palette[hidden] { display: none; }
171
+
172
+ .app-shell-palette-inner {
173
+ width: min(var(--app-palette-w), 92vw);
174
+ background: var(--surface);
175
+ border: 1px solid var(--hair);
176
+ border-radius: var(--radius-lg);
177
+ box-shadow: 0 40px 120px rgba(0, 0, 0, 0.55);
178
+ overflow: hidden;
179
+ }
180
+ .app-shell-palette-input {
181
+ width: 100%;
182
+ padding: var(--app-palette-pad-block) var(--app-bar-pad-inline);
183
+ border: none;
184
+ border-bottom: 1px solid var(--hair);
185
+ background: transparent;
186
+ color: var(--ink);
187
+ font-family: var(--font-family-mono);
188
+ font-size: 0.875rem;
189
+ outline: none;
190
+ }
191
+ /* The palette input cleared its outline and declared no focus style at all, so
192
+ a keyboard user could not see where focus was inside the dialog. */
193
+ .app-shell-palette-input:focus-visible {
194
+ outline: 2px solid var(--accent, var(--ink));
195
+ outline-offset: -2px;
196
+ }
197
+ .app-shell-palette-list {
198
+ list-style: none;
199
+ margin: 0;
200
+ padding: var(--app-chip-pad-block);
201
+ max-height: 50vh;
202
+ overflow: auto;
203
+ }
204
+ .app-shell-palette-item {
205
+ display: grid;
206
+ grid-template-columns: auto minmax(0, 1fr);
207
+ gap: var(--app-palette-gap);
208
+ padding: var(--space-2) var(--app-bar-pad-block);
209
+ border-radius: var(--radius-sm);
210
+ cursor: pointer;
211
+ font-family: var(--font-family-mono);
212
+ font-size: var(--text-sm);
213
+ color: var(--ink);
214
+ outline: none;
215
+ }
216
+ .app-shell-palette-item.is-active,
217
+ .app-shell-palette-item:hover,
218
+ .app-shell-palette-item:focus-visible {
219
+ background: color-mix(in srgb, var(--accent, var(--ink)) 14%, transparent);
220
+ }
221
+ /* The tint above is a fill shift under the row; a focused row also carries a
222
+ ring, so keyboard focus is not signalled by a low-contrast fill alone. */
223
+ .app-shell-palette-item:focus-visible {
224
+ outline: 2px solid var(--accent, var(--ink));
225
+ outline-offset: -2px;
226
+ }
227
+ .app-shell-palette-desc {
228
+ color: var(--ink-soft);
229
+ }
230
+
231
+ /* --- Reduced motion ---------------------------------------------------- */
232
+ @media (prefers-reduced-motion: reduce) {
233
+ .app-shell { transition: none; }
234
+ }
package/fonts.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { Geist_Mono } from "next/font/google";
2
+
3
+ /**
4
+ * The one font loader. Each app re-exports `fontVariables` and puts that
5
+ * class on `<html>`. Geist Mono is the face. next/font
6
+ * self-hosts the file at build time, so no request leaves for a font host.
7
+ */
8
+ export const mono = Geist_Mono({
9
+ subsets: ["latin"],
10
+ variable: "--font-mono-face",
11
+ display: "swap",
12
+ });
13
+
14
+ /** The class a root layout puts on `<html>`. */
15
+ export const fontVariables = mono.variable;
package/html-escape.js ADDED
@@ -0,0 +1,19 @@
1
+ /** Escape text for safe inclusion in integrator HTML slots (SIMP-030 / DESLOP-027). */
2
+ export function escapeHtml(s) {
3
+ return String(s).replace(/[&<>"']/g, (c) => ({
4
+ '&': '&amp;',
5
+ '<': '&lt;',
6
+ '>': '&gt;',
7
+ '"': '&quot;',
8
+ "'": '&#39;',
9
+ }[c]));
10
+ }
11
+
12
+ /** Mount escapeHtml()-safe markup from trusted template literals (DESLOP-027). */
13
+ export function mountTrustedHtml(el, html) {
14
+ el.replaceChildren();
15
+ if (!html) return;
16
+ const tpl = document.createElement('template');
17
+ tpl.innerHTML = html;
18
+ el.append(tpl.content);
19
+ }
package/motion.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "ease": { "house": [0.22, 1, 0.36, 1] },
3
+ "duration": { "hover": 180, "reveal": 600, "copied": 1500, "build": 1400 },
4
+ "frame": { "tick": 40, "spinner": 80, "caret": 530, "type": 16, "clock": 100 },
5
+ "reduced": { "duration": 0, "frame": null }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,50 @@
1
1
  {
2
2
  "name": "@digithings-ai/design",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Shared digithings design tokens and the CSS and vanilla-JS modules live surfaces still import.",
5
+ "license": "MIT",
6
+ "homepage": "https://digithings.ai",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/digithings-ai/digithings.git",
10
+ "directory": "packages/design"
11
+ },
12
+ "exports": {
13
+ "./tokens.css": "./tokens.css",
14
+ "./palettes.css": "./palettes.css",
15
+ "./tokens": "./tokens.ts",
16
+ "./quant-native": "./quant-native/ticker.js",
17
+ "./quant-native/styles.css": "./quant-native/styles.css",
18
+ "./app-shell-terminal": "./app-shell-terminal/index.js",
19
+ "./app-shell-terminal/styles.css": "./app-shell-terminal/styles.css",
20
+ "./html-escape.js": "./html-escape.js",
21
+ "./site/site.css": "./site/site.css",
22
+ "./site/*": "./site/*",
23
+ "./releases.json": "./releases.json",
24
+ "./motion.json": "./motion.json",
25
+ "./fonts": "./fonts.ts"
26
+ },
27
+ "files": [
28
+ "tokens.css",
29
+ "palettes.css",
30
+ "tokens.ts",
31
+ "motion.json",
32
+ "html-escape.js",
33
+ "quant-native",
34
+ "app-shell-terminal",
35
+ "site",
36
+ "releases.json",
37
+ "fonts.ts"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "peerDependencies": {
43
+ "next": ">=16"
44
+ },
45
+ "peerDependenciesMeta": {
46
+ "next": {
47
+ "optional": true
48
+ }
49
+ }
50
+ }