@cohesivesystems/presentation-tailwind 0.1.0-alpha.1

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,773 @@
1
+ import type {
2
+ ActionDefinition,
3
+ ActionPlacementDefinition,
4
+ DesignIntent,
5
+ InputFormGroupDefinition,
6
+ NavigationShellSlotDefinition,
7
+ } from '@cohesivesystems/presentation-contracts'
8
+ import {
9
+ inputFormGroupKinds,
10
+ navigationShellSlotKinds,
11
+ } from '@cohesivesystems/presentation-contracts'
12
+ import type {
13
+ PresentationDesignIntentFieldName,
14
+ } from '@cohesivesystems/presentation-core'
15
+
16
+ export interface PresentationDesignSystemDesignIntentInterpretation {
17
+ readonly ignoredFields: readonly PresentationDesignIntentFieldName[]
18
+ readonly interpretedFields: readonly PresentationDesignIntentFieldName[]
19
+ }
20
+
21
+ export interface PresentationDesignSystemDiagnostics {
22
+ readonly navigationShellFrame: PresentationDesignSystemDesignIntentInterpretation
23
+ readonly navigationShellSlot: PresentationDesignSystemDesignIntentInterpretation
24
+ readonly routedSurfaceLayout: PresentationDesignSystemDesignIntentInterpretation
25
+ }
26
+
27
+ export interface PresentationRoutedSurfaceClassNameContext {
28
+ readonly design: DesignIntent | null | undefined
29
+ }
30
+
31
+ export interface PresentationToneClassNameContext {
32
+ readonly tone: string | null | undefined
33
+ }
34
+
35
+ export interface PresentationDocumentWorkspaceNodeClassNameContext {
36
+ readonly isActiveSearchMatch?: boolean
37
+ readonly isSearchMatch?: boolean
38
+ }
39
+
40
+ export interface PresentationDocumentWorkspaceToneClassNameContext {
41
+ readonly label?: string | null | undefined
42
+ readonly tone: string | null | undefined
43
+ }
44
+
45
+ export interface PresentationSurfaceChromeClassNameContext {
46
+ readonly design?: DesignIntent | null | undefined
47
+ }
48
+
49
+ export interface PresentationInputFormGroupClassNameContext {
50
+ readonly group: InputFormGroupDefinition
51
+ }
52
+
53
+ export interface PresentationNavigationShellClassNameContext {
54
+ readonly design: DesignIntent | null | undefined
55
+ }
56
+
57
+ export interface PresentationNavigationShellSlotClassNameContext {
58
+ readonly slot: NavigationShellSlotDefinition
59
+ }
60
+
61
+ export interface PresentationActionButtonContext {
62
+ readonly action: ActionDefinition | null
63
+ readonly placement: ActionPlacementDefinition
64
+ }
65
+
66
+ export type PresentationActionButtonSize =
67
+ 'default' | 'icon' | 'icon-lg' | 'icon-sm' | 'icon-xs' | 'lg' | 'sm' | 'xs'
68
+
69
+ export type PresentationActionButtonVariant =
70
+ 'default' | 'destructive' | 'ghost' | 'link' | 'outline' | 'secondary'
71
+
72
+ export interface PresentationDesignSystemClassNames {
73
+ readonly badge: {
74
+ readonly tone: (context: PresentationToneClassNameContext) => string
75
+ }
76
+ readonly documentWorkspace: {
77
+ readonly actionRow: (context: PresentationSurfaceChromeClassNameContext) => string
78
+ readonly actionIcon: (context: PresentationSurfaceChromeClassNameContext) => string
79
+ readonly codeCell: (context: PresentationSurfaceChromeClassNameContext) => string
80
+ readonly codeTable: (context: PresentationSurfaceChromeClassNameContext) => string
81
+ readonly constraintBlock: (context: PresentationSurfaceChromeClassNameContext) => string
82
+ readonly constraintList: (context: PresentationSurfaceChromeClassNameContext) => string
83
+ readonly contentPane: (context: PresentationSurfaceChromeClassNameContext) => string
84
+ readonly dataTableContainer: (context: PresentationSurfaceChromeClassNameContext) => string
85
+ readonly detailContent: (context: PresentationSurfaceChromeClassNameContext) => string
86
+ readonly detailEmpty: (context: PresentationSurfaceChromeClassNameContext) => string
87
+ readonly detailHeader: (context: PresentationSurfaceChromeClassNameContext) => string
88
+ readonly detailHeaderContent: (context: PresentationSurfaceChromeClassNameContext) => string
89
+ readonly detailPanel: (context: PresentationSurfaceChromeClassNameContext) => string
90
+ readonly detailSubtitle: (context: PresentationSurfaceChromeClassNameContext) => string
91
+ readonly detailTable: (context: PresentationSurfaceChromeClassNameContext) => string
92
+ readonly detailTableLabelCell: (context: PresentationSurfaceChromeClassNameContext) => string
93
+ readonly detailTableRow: (context: PresentationSurfaceChromeClassNameContext) => string
94
+ readonly detailTableValueCell: (context: PresentationSurfaceChromeClassNameContext) => string
95
+ readonly detailTableValueText: (context: PresentationSurfaceChromeClassNameContext) => string
96
+ readonly detailTitle: (context: PresentationSurfaceChromeClassNameContext) => string
97
+ readonly errorMessage: (context: PresentationSurfaceChromeClassNameContext) => string
98
+ readonly iconTone: (context: PresentationDocumentWorkspaceToneClassNameContext) => string
99
+ readonly inlineMeta: (context: PresentationSurfaceChromeClassNameContext) => string
100
+ readonly loadingMessage: (context: PresentationSurfaceChromeClassNameContext) => string
101
+ readonly nodeBadge: (context: PresentationDocumentWorkspaceToneClassNameContext) => string
102
+ readonly nodeLabel: (context: PresentationDocumentWorkspaceNodeClassNameContext) => string
103
+ readonly nodeLabelText: (context: PresentationSurfaceChromeClassNameContext) => string
104
+ readonly root: (context: PresentationSurfaceChromeClassNameContext) => string
105
+ readonly section: (context: PresentationSurfaceChromeClassNameContext) => string
106
+ readonly sectionBody: (context: PresentationSurfaceChromeClassNameContext) => string
107
+ readonly sectionHeading: (context: PresentationSurfaceChromeClassNameContext) => string
108
+ readonly sectionHeadingRow: (context: PresentationSurfaceChromeClassNameContext) => string
109
+ readonly splitter: (context: PresentationSurfaceChromeClassNameContext) => string
110
+ readonly splitterHandle: (context: PresentationSurfaceChromeClassNameContext) => string
111
+ readonly status: (context: PresentationSurfaceChromeClassNameContext) => string
112
+ readonly tableCell: (context: PresentationSurfaceChromeClassNameContext) => string
113
+ readonly tableHeader: (context: PresentationSurfaceChromeClassNameContext) => string
114
+ readonly tableHeaderCell: (context: PresentationSurfaceChromeClassNameContext) => string
115
+ readonly treePane: (context: PresentationSurfaceChromeClassNameContext) => string
116
+ }
117
+ readonly formSurface: {
118
+ readonly actionRow: (context: PresentationSurfaceChromeClassNameContext) => string
119
+ readonly content: (context: PresentationSurfaceChromeClassNameContext) => string
120
+ readonly field: (context: PresentationSurfaceChromeClassNameContext) => string
121
+ readonly group: (context: PresentationInputFormGroupClassNameContext) => string
122
+ readonly groups: (context: PresentationSurfaceChromeClassNameContext) => string
123
+ readonly rangeGroup: (context: PresentationSurfaceChromeClassNameContext) => string
124
+ readonly root: (context: PresentationSurfaceChromeClassNameContext) => string
125
+ }
126
+ readonly metricDashboard: {
127
+ readonly root: (context: PresentationSurfaceChromeClassNameContext) => string
128
+ }
129
+ readonly navigationShell: {
130
+ readonly brandBadge: (context: PresentationNavigationShellClassNameContext) => string
131
+ readonly brandSubtitle: (context: PresentationNavigationShellClassNameContext) => string
132
+ readonly brandTitle: (context: PresentationNavigationShellClassNameContext) => string
133
+ readonly header: (context: PresentationNavigationShellClassNameContext) => string
134
+ readonly headerContent: (context: PresentationNavigationShellClassNameContext) => string
135
+ readonly headerNavigation: (context: PresentationNavigationShellClassNameContext) => string
136
+ readonly navigationItem: (context: PresentationNavigationShellClassNameContext) => string
137
+ readonly root: (context: PresentationNavigationShellClassNameContext) => string
138
+ readonly slotRoot: (context: PresentationNavigationShellSlotClassNameContext) => string
139
+ }
140
+ readonly routedSurface: {
141
+ readonly content: (context: PresentationRoutedSurfaceClassNameContext) => string
142
+ readonly root: (context: PresentationRoutedSurfaceClassNameContext) => string
143
+ }
144
+ readonly recordDetail: {
145
+ readonly root: (context: PresentationSurfaceChromeClassNameContext) => string
146
+ }
147
+ readonly statusNotice: {
148
+ readonly tone: (context: PresentationToneClassNameContext) => string
149
+ }
150
+ readonly viewSurface: {
151
+ readonly content: (context: PresentationSurfaceChromeClassNameContext) => string
152
+ readonly root: (context: PresentationSurfaceChromeClassNameContext) => string
153
+ }
154
+ readonly toggle: {
155
+ readonly tone: (context: PresentationToneClassNameContext) => string
156
+ }
157
+ }
158
+
159
+ export interface PresentationDesignSystemComponents {
160
+ readonly actionButton: {
161
+ readonly size: (context: PresentationActionButtonContext) => PresentationActionButtonSize
162
+ readonly variant: (context: PresentationActionButtonContext) => PresentationActionButtonVariant
163
+ }
164
+ }
165
+
166
+ export interface PresentationDesignSystem {
167
+ readonly classNames: PresentationDesignSystemClassNames
168
+ readonly components: PresentationDesignSystemComponents
169
+ readonly diagnostics: PresentationDesignSystemDiagnostics
170
+ readonly id: string
171
+ readonly target: string
172
+ }
173
+
174
+ const workspaceRoleTokens = new Set([
175
+ 'detail',
176
+ 'details',
177
+ 'execution-details',
178
+ ])
179
+
180
+ const fullWidthRoleTokens = new Set([
181
+ 'catalog',
182
+ 'collection',
183
+ 'document-workspace',
184
+ 'editor',
185
+ 'list',
186
+ 'table',
187
+ 'workspace',
188
+ ])
189
+
190
+ const wideRoleTokens = new Set([
191
+ 'catalog',
192
+ 'collection',
193
+ 'list',
194
+ 'table',
195
+ ])
196
+
197
+ const workspaceSizeHints = new Set([
198
+ '2xl',
199
+ 'full',
200
+ 'max',
201
+ 'screen',
202
+ 'xl',
203
+ ])
204
+
205
+ const wideSizeHints = new Set([
206
+ 'wide',
207
+ ])
208
+
209
+ export const tailwindPresentationDesignSystem: PresentationDesignSystem = {
210
+ classNames: {
211
+ badge: {
212
+ tone: ({ tone }) => badgeClassByTone[coercePresentationTone(tone)],
213
+ },
214
+ documentWorkspace: {
215
+ actionRow: () => 'mt-3',
216
+ actionIcon: () => 'size-3.5',
217
+ codeCell: () => 'border-b border-slate-950/8 px-3 py-2 font-mono text-[0.72rem] font-semibold text-slate-900',
218
+ codeTable: () => 'w-full border-separate border-spacing-0 text-left text-xs',
219
+ constraintBlock: () => 'overflow-auto px-3 py-2 text-xs text-slate-700',
220
+ constraintList: () => 'grid gap-2',
221
+ contentPane: () => 'h-full overflow-auto px-2 py-2',
222
+ dataTableContainer: () =>
223
+ 'mt-2 max-h-86 overflow-auto rounded-md border border-slate-950/8 bg-white/72',
224
+ detailContent: () => 'min-h-0 flex-1 overflow-auto px-4 py-3',
225
+ detailEmpty: () =>
226
+ 'flex h-full items-center justify-center px-4 py-3 text-sm text-slate-500',
227
+ detailHeader: () => 'border-b border-slate-950/8 px-4 py-3',
228
+ detailHeaderContent: () => 'flex min-w-0 items-center gap-2',
229
+ detailPanel: () => 'flex h-full min-w-0 flex-col overflow-hidden bg-slate-50/55',
230
+ detailSubtitle: () =>
231
+ 'text-[0.68rem] font-semibold uppercase tracking-[0.18em] text-slate-500',
232
+ detailTable: () => 'w-full border-separate border-spacing-0 text-left text-sm',
233
+ detailTableLabelCell: () =>
234
+ 'w-36 border-b border-slate-950/8 py-2 pr-3 text-xs font-semibold uppercase tracking-[0.12em] text-slate-500',
235
+ detailTableRow: () => 'align-top',
236
+ detailTableValueCell: () => 'border-b border-slate-950/8 py-2 text-slate-800',
237
+ detailTableValueText: () => 'wrap-break-word',
238
+ detailTitle: () => 'truncate text-sm font-semibold text-slate-950',
239
+ errorMessage: () =>
240
+ 'mt-4 rounded-md border border-red-300/60 bg-red-50 px-3 py-2 text-sm text-red-800',
241
+ iconTone: ({ tone }) => documentWorkspaceIconClassByTone[coerceDocumentWorkspaceTone(tone)],
242
+ inlineMeta: () => 'mt-1 wrap-break-word font-mono text-[0.68rem] text-slate-500',
243
+ loadingMessage: () => 'mt-4 text-sm text-slate-500',
244
+ nodeBadge: ({ label, tone }) =>
245
+ resolveDocumentWorkspaceNodeBadgeClassName(coerceDocumentWorkspaceTone(tone), label),
246
+ nodeLabel: ({ isActiveSearchMatch, isSearchMatch }) =>
247
+ [
248
+ 'flex min-w-0 flex-wrap items-center gap-1.5 rounded-md px-1 py-0.5 text-sm text-slate-700',
249
+ isSearchMatch ? 'bg-amber-50 ring-1 ring-amber-200/70' : '',
250
+ isActiveSearchMatch ? 'bg-amber-100 ring-amber-300/80' : '',
251
+ ].filter(Boolean).join(' '),
252
+ nodeLabelText: () => 'min-w-0 truncate font-medium text-slate-900',
253
+ root: () =>
254
+ 'h-full min-h-170 overflow-hidden rounded-md border border-slate-950/10 bg-white/72',
255
+ section: () => 'mt-4',
256
+ sectionBody: () => 'mt-2',
257
+ sectionHeading: () => 'text-sm font-semibold text-slate-950',
258
+ sectionHeadingRow: () => 'flex flex-wrap items-center gap-2',
259
+ splitter: () =>
260
+ 'group flex w-2 items-stretch justify-center bg-slate-950/4 outline-none transition-colors hover:bg-slate-950/8 focus-visible:bg-slate-950/10',
261
+ splitterHandle: () => 'my-3 w-px rounded-full bg-slate-950/14 group-hover:bg-slate-950/25',
262
+ status: () =>
263
+ 'rounded-md border border-slate-950/8 bg-white/65 px-4 py-3 text-sm text-slate-500',
264
+ tableCell: () => 'border-b border-slate-950/8 px-3 py-2 text-slate-800',
265
+ tableHeader: () => 'sticky top-0 bg-white',
266
+ tableHeaderCell: () =>
267
+ 'border-b border-slate-950/8 px-3 py-2 font-semibold uppercase tracking-[0.12em] text-slate-500',
268
+ treePane: () => 'h-full overflow-auto px-2 py-2',
269
+ },
270
+ formSurface: {
271
+ actionRow: () => 'flex flex-wrap items-end justify-end gap-2',
272
+ content: () => 'grid gap-4',
273
+ field: () => 'grid min-w-0 gap-1.5',
274
+ group: ({ group }) => resolveInputFormGroupClassName(group),
275
+ groups: () => 'grid gap-4',
276
+ rangeGroup: () => 'grid gap-2 sm:grid-cols-2',
277
+ root: () => 'relative z-30 overflow-visible bg-white/76',
278
+ },
279
+ metricDashboard: {
280
+ root: () => 'bg-white/76',
281
+ },
282
+ navigationShell: {
283
+ brandBadge: () => 'border-teal-700/15 bg-teal-50 text-teal-700',
284
+ brandSubtitle: () => 'text-xs text-slate-500',
285
+ brandTitle: () => 'text-sm font-medium text-slate-950',
286
+ header: ({ design }) => {
287
+ const density = normalizeDesignToken(design?.Density)
288
+ const paddingClassName = density === 'comfortable' ? 'px-4 py-3' : 'px-4 py-2'
289
+
290
+ return [
291
+ 'sticky top-0 z-40 border-b border-slate-950/8 bg-white/86',
292
+ paddingClassName,
293
+ 'shadow-[0_12px_30px_rgba(15,23,42,0.06)] backdrop-blur-xl',
294
+ ].join(' ')
295
+ },
296
+ headerContent: ({ design }) => [
297
+ 'mx-auto flex',
298
+ resolveNavigationShellHeaderMaxWidthClassName(design),
299
+ 'flex-wrap items-center justify-between gap-3',
300
+ ].join(' '),
301
+ headerNavigation: () => 'flex flex-wrap items-center gap-1',
302
+ navigationItem: () => 'text-slate-700',
303
+ root: () => 'min-h-screen bg-background',
304
+ slotRoot: ({ slot }) => resolveNavigationShellSlotRootClassName(slot),
305
+ },
306
+ routedSurface: {
307
+ content: ({ design }) => resolveRoutedSurfaceContentClassName(design),
308
+ root: () =>
309
+ 'flex min-h-[calc(100vh-4rem)] flex-col bg-[radial-gradient(circle_at_top_left,rgba(255,255,255,0.92),transparent_28%),linear-gradient(180deg,#f7f5f0_0%,#f6f8fb_48%,#e8eef6_100%)] px-4 py-5 text-slate-700',
310
+ },
311
+ recordDetail: {
312
+ root: () => 'bg-white/76',
313
+ },
314
+ statusNotice: {
315
+ tone: ({ tone }) => statusNoticeClassByTone[coercePresentationTone(tone)],
316
+ },
317
+ toggle: {
318
+ tone: ({ tone }) =>
319
+ [
320
+ inactiveToggleClassName,
321
+ toggleClassByTone[coercePresentationTone(tone)],
322
+ ].join(' '),
323
+ },
324
+ viewSurface: {
325
+ content: () => 'flex min-h-0 flex-1 flex-col gap-4',
326
+ root: () => 'min-h-155 bg-white/76',
327
+ },
328
+ },
329
+ components: {
330
+ actionButton: {
331
+ size: ({ action }) => resolveActionButtonSize(action),
332
+ variant: ({ action, placement }) => resolveActionButtonVariant(action, placement),
333
+ },
334
+ },
335
+ diagnostics: {
336
+ navigationShellFrame: {
337
+ ignoredFields: [
338
+ 'Role',
339
+ 'Variant',
340
+ 'Tone',
341
+ 'Layout',
342
+ ],
343
+ interpretedFields: [
344
+ 'Density',
345
+ 'Size',
346
+ ],
347
+ },
348
+ navigationShellSlot: {
349
+ ignoredFields: [
350
+ 'Role',
351
+ 'Variant',
352
+ 'Tone',
353
+ 'Size',
354
+ ],
355
+ interpretedFields: [
356
+ 'Density',
357
+ 'Layout',
358
+ ],
359
+ },
360
+ routedSurfaceLayout: {
361
+ ignoredFields: [
362
+ 'Variant',
363
+ 'Tone',
364
+ 'Density',
365
+ ],
366
+ interpretedFields: [
367
+ 'Role',
368
+ 'Size',
369
+ 'Layout',
370
+ ],
371
+ },
372
+ },
373
+ id: 'tailwind-default',
374
+ target: 'tailwind',
375
+ }
376
+
377
+ const presentationToneValues = [
378
+ 'accent',
379
+ 'danger',
380
+ 'info',
381
+ 'muted',
382
+ 'neutral',
383
+ 'success',
384
+ 'warning',
385
+ ] as const
386
+
387
+ type PresentationTone = (typeof presentationToneValues)[number]
388
+ type PresentationDocumentWorkspaceTone =
389
+ | PresentationTone
390
+ | 'cardinality'
391
+ | 'data-type'
392
+ | 'enum'
393
+ | 'field'
394
+ | 'graph'
395
+ | 'loop'
396
+ | 'nullability'
397
+ | 'presence'
398
+ | 'repeat'
399
+ | 'requirement'
400
+ | 'scalar'
401
+ | 'segment'
402
+ | 'sequence'
403
+ | 'shape'
404
+ | 'structural-type'
405
+ | 'type'
406
+ | 'union'
407
+
408
+ const presentationTones = new Set<string>(presentationToneValues)
409
+ const documentWorkspaceTones = new Set<string>([
410
+ ...presentationToneValues,
411
+ 'cardinality',
412
+ 'data-type',
413
+ 'enum',
414
+ 'field',
415
+ 'graph',
416
+ 'loop',
417
+ 'nullability',
418
+ 'presence',
419
+ 'repeat',
420
+ 'requirement',
421
+ 'scalar',
422
+ 'segment',
423
+ 'sequence',
424
+ 'shape',
425
+ 'structural-type',
426
+ 'type',
427
+ 'union',
428
+ ])
429
+
430
+ const badgeClassByTone = {
431
+ accent: 'border-indigo-700/15 bg-indigo-50 text-indigo-700',
432
+ danger: 'border-red-700/15 bg-red-50 text-red-700',
433
+ info: 'border-sky-700/15 bg-sky-50 text-sky-700',
434
+ muted: 'border-slate-950/10 bg-slate-100 text-slate-700',
435
+ neutral: 'border-slate-950/10 bg-slate-100 text-slate-700',
436
+ success: 'border-teal-700/15 bg-teal-50 text-teal-700',
437
+ warning: 'border-amber-700/15 bg-amber-50 text-amber-800',
438
+ } satisfies Readonly<Record<PresentationTone, string>>
439
+
440
+ const statusNoticeClassByTone = {
441
+ accent: 'border-indigo-300/50 bg-indigo-50 text-indigo-800',
442
+ danger: 'border-red-300/60 bg-red-50 text-red-800',
443
+ info: 'border-sky-300/50 bg-sky-50 text-sky-800',
444
+ muted: 'border-slate-950/10 bg-slate-100 text-slate-700',
445
+ neutral: 'border-slate-950/10 bg-slate-100 text-slate-700',
446
+ success: 'border-teal-300/50 bg-teal-50 text-teal-800',
447
+ warning: 'border-amber-300/50 bg-amber-50 text-amber-900',
448
+ } satisfies Readonly<Record<PresentationTone, string>>
449
+
450
+ const toggleClassByTone = {
451
+ accent: 'data-[state=on]:border-indigo-700/15 data-[state=on]:bg-indigo-50 data-[state=on]:text-indigo-700',
452
+ danger: 'data-[state=on]:border-red-700/15 data-[state=on]:bg-red-50 data-[state=on]:text-red-700',
453
+ info: 'data-[state=on]:border-sky-700/15 data-[state=on]:bg-sky-50 data-[state=on]:text-sky-700',
454
+ muted: 'data-[state=on]:border-slate-950/10 data-[state=on]:bg-slate-100 data-[state=on]:text-slate-700',
455
+ neutral: 'data-[state=on]:border-slate-950/10 data-[state=on]:bg-slate-100 data-[state=on]:text-slate-700',
456
+ success: 'data-[state=on]:border-teal-700/15 data-[state=on]:bg-teal-50 data-[state=on]:text-teal-700',
457
+ warning: 'data-[state=on]:border-amber-700/15 data-[state=on]:bg-amber-50 data-[state=on]:text-amber-800',
458
+ } satisfies Readonly<Record<PresentationTone, string>>
459
+
460
+ const inactiveToggleClassName =
461
+ 'data-[state=off]:border-slate-950/10 data-[state=off]:bg-white data-[state=off]:text-slate-500'
462
+
463
+ const documentWorkspaceIconClassByTone = {
464
+ accent: 'text-indigo-700',
465
+ cardinality: 'text-violet-700',
466
+ danger: 'text-red-700',
467
+ 'data-type': 'text-teal-700',
468
+ enum: 'text-amber-700',
469
+ field: 'text-indigo-700',
470
+ graph: 'text-slate-700',
471
+ info: 'text-sky-700',
472
+ loop: 'text-violet-700',
473
+ muted: 'text-slate-600',
474
+ neutral: 'text-slate-700',
475
+ nullability: 'text-amber-700',
476
+ presence: 'text-sky-700',
477
+ repeat: 'text-violet-700',
478
+ requirement: 'text-amber-700',
479
+ scalar: 'text-teal-700',
480
+ segment: 'text-sky-700',
481
+ sequence: 'text-sky-700',
482
+ shape: 'text-sky-700',
483
+ structural: 'text-sky-700',
484
+ 'structural-type': 'text-sky-700',
485
+ success: 'text-teal-700',
486
+ type: 'text-teal-700',
487
+ union: 'text-violet-700',
488
+ warning: 'text-amber-700',
489
+ } satisfies Readonly<Record<PresentationDocumentWorkspaceTone | 'structural', string>>
490
+
491
+ function resolveRoutedSurfaceContentClassName(
492
+ design: DesignIntent | null | undefined,
493
+ ): string {
494
+ if (hasAnyDesignToken(design?.Role, fullWidthRoleTokens)) {
495
+ return 'flex min-h-0 w-full max-w-none flex-1 flex-col gap-5'
496
+ }
497
+
498
+ if (hasAnyDesignToken(design?.Role, workspaceRoleTokens)) {
499
+ return 'mx-auto flex min-h-0 w-full max-w-7xl flex-1 flex-col gap-5'
500
+ }
501
+
502
+ if (hasAnyDesignToken(design?.Role, wideRoleTokens)) {
503
+ return 'mx-auto flex min-h-0 w-full max-w-420 flex-1 flex-col gap-5'
504
+ }
505
+
506
+ if (hasAnyDesignToken(design?.Layout, workspaceRoleTokens)) {
507
+ return 'mx-auto flex min-h-0 w-full max-w-7xl flex-1 flex-col gap-5'
508
+ }
509
+
510
+ if (hasAnyDesignToken(design?.Layout, wideRoleTokens)) {
511
+ return 'mx-auto flex min-h-0 w-full max-w-420 flex-1 flex-col gap-5'
512
+ }
513
+
514
+ const size = normalizeDesignToken(design?.Size)
515
+ if (size && workspaceSizeHints.has(size)) {
516
+ return 'mx-auto flex min-h-0 w-full max-w-7xl flex-1 flex-col gap-5'
517
+ }
518
+
519
+ if (size && wideSizeHints.has(size)) {
520
+ return 'mx-auto flex min-h-0 w-full max-w-420 flex-1 flex-col gap-5'
521
+ }
522
+
523
+ return 'mx-auto flex min-h-0 w-full max-w-360 flex-1 flex-col gap-5'
524
+ }
525
+
526
+ function resolveActionButtonVariant(
527
+ action: ActionDefinition | null,
528
+ placement: ActionPlacementDefinition,
529
+ ): PresentationActionButtonVariant {
530
+ const designVariant = normalizeDesignToken(action?.Design?.Variant)
531
+ const tone = normalizeDesignToken(action?.Design?.Tone)
532
+ const intent = normalizeDesignToken(placement.Intent)
533
+ if (
534
+ tone === 'warning' ||
535
+ tone === 'danger' ||
536
+ intent === 'warning' ||
537
+ intent === 'danger' ||
538
+ intent === 'destructive'
539
+ ) {
540
+ return 'destructive'
541
+ }
542
+
543
+ if (intent === 'primary' || designVariant === 'primary') {
544
+ return 'default'
545
+ }
546
+
547
+ if (designVariant === 'ghost' && placement.Region.startsWith('row')) {
548
+ return 'ghost'
549
+ }
550
+
551
+ return 'outline'
552
+ }
553
+
554
+ function resolveActionButtonSize(
555
+ action: ActionDefinition | null,
556
+ ): PresentationActionButtonSize {
557
+ switch (normalizeDesignToken(action?.Design?.Size)) {
558
+ case 'xs':
559
+ return 'xs'
560
+ case 'lg':
561
+ return 'lg'
562
+ case 'sm':
563
+ case 'md':
564
+ default:
565
+ return 'sm'
566
+ }
567
+ }
568
+
569
+ function coercePresentationTone(value: string | null | undefined): PresentationTone {
570
+ const normalized = normalizeDesignToken(value)
571
+ return normalized && presentationTones.has(normalized)
572
+ ? normalized as PresentationTone
573
+ : 'info'
574
+ }
575
+
576
+ function coerceDocumentWorkspaceTone(
577
+ value: string | null | undefined,
578
+ ): PresentationDocumentWorkspaceTone {
579
+ const normalized = normalizeDesignToken(value)
580
+ return normalized && documentWorkspaceTones.has(normalized)
581
+ ? normalized as PresentationDocumentWorkspaceTone
582
+ : 'info'
583
+ }
584
+
585
+ function resolveDocumentWorkspaceNodeBadgeClassName(
586
+ tone: PresentationDocumentWorkspaceTone,
587
+ label: string | null | undefined,
588
+ ) {
589
+ if (tone === 'requirement') {
590
+ return label?.toUpperCase() === 'M'
591
+ ? 'h-4 rounded-md border-amber-700/15 bg-amber-50 px-1.5 text-[0.67rem] text-amber-700'
592
+ : 'h-4 rounded-md border-slate-950/10 bg-slate-100 px-1.5 text-[0.67rem] text-slate-600'
593
+ }
594
+
595
+ const toneClassName = badgeClassByTone[coerceDocumentWorkspaceBadgeTone(tone)]
596
+ const maxWidthClassName = tone === 'type' ? 'max-w-80 truncate' : ''
597
+ return ['h-4 rounded-md px-1.5 text-[0.67rem]', maxWidthClassName, toneClassName]
598
+ .filter(Boolean)
599
+ .join(' ')
600
+ }
601
+
602
+ function coerceDocumentWorkspaceBadgeTone(
603
+ tone: PresentationDocumentWorkspaceTone,
604
+ ): PresentationTone {
605
+ switch (tone) {
606
+ case 'cardinality':
607
+ case 'repeat':
608
+ case 'union':
609
+ return 'accent'
610
+ case 'data-type':
611
+ case 'scalar':
612
+ case 'success':
613
+ case 'type':
614
+ return 'success'
615
+ case 'enum':
616
+ case 'nullability':
617
+ case 'requirement':
618
+ case 'warning':
619
+ return 'warning'
620
+ case 'field':
621
+ case 'shape':
622
+ case 'info':
623
+ case 'presence':
624
+ case 'sequence':
625
+ return 'info'
626
+ case 'danger':
627
+ return 'danger'
628
+ case 'muted':
629
+ case 'neutral':
630
+ case 'graph':
631
+ case 'loop':
632
+ case 'segment':
633
+ case 'structural-type':
634
+ return 'neutral'
635
+ case 'accent':
636
+ return 'accent'
637
+ }
638
+ }
639
+
640
+ function resolveNavigationShellHeaderMaxWidthClassName(
641
+ design: DesignIntent | null | undefined,
642
+ ) {
643
+ switch (normalizeDesignToken(design?.Size)) {
644
+ case '2xl':
645
+ case 'full':
646
+ case 'max':
647
+ case 'screen':
648
+ case 'xl':
649
+ return 'max-w-7xl'
650
+ case 'lg':
651
+ case 'wide':
652
+ default:
653
+ return 'max-w-420'
654
+ }
655
+ }
656
+
657
+ function resolveNavigationShellSlotRootClassName(
658
+ slot: NavigationShellSlotDefinition,
659
+ ): string {
660
+ const density = normalizeDesignToken(slot.Design?.Density)
661
+ const gapClassName = density === 'comfortable' ? 'gap-2' : 'gap-1'
662
+
663
+ if (isNavigationShellSlotKind(slot, navigationShellSlotKinds.brand, 'Brand')) {
664
+ return 'flex items-center gap-2'
665
+ }
666
+
667
+ if (
668
+ isNavigationShellSlotKind(
669
+ slot,
670
+ navigationShellSlotKinds.primaryNavigation,
671
+ 'PrimaryNavigation',
672
+ )
673
+ ) {
674
+ return `flex flex-wrap items-center ${gapClassName}`
675
+ }
676
+
677
+ if (
678
+ isNavigationShellSlotKind(
679
+ slot,
680
+ navigationShellSlotKinds.utilityActions,
681
+ 'UtilityActions',
682
+ )
683
+ ) {
684
+ return 'flex items-center gap-2'
685
+ }
686
+
687
+ if (
688
+ isNavigationShellSlotKind(
689
+ slot,
690
+ navigationShellSlotKinds.systemNotices,
691
+ 'SystemNotices',
692
+ ) ||
693
+ isNavigationShellSlotKind(
694
+ slot,
695
+ navigationShellSlotKinds.routedContent,
696
+ 'RoutedContent',
697
+ )
698
+ ) {
699
+ return 'contents'
700
+ }
701
+
702
+ switch (normalizeDesignToken(slot.Design?.Layout ?? slot.Placement)) {
703
+ case 'topcenter':
704
+ case 'top-center':
705
+ return `flex flex-wrap items-center ${gapClassName}`
706
+ case 'topright':
707
+ case 'top-right':
708
+ return 'flex items-center gap-2'
709
+ case 'belownavigation':
710
+ case 'below-navigation':
711
+ case 'main':
712
+ return 'contents'
713
+ default:
714
+ return `flex flex-wrap items-center ${gapClassName}`
715
+ }
716
+ }
717
+
718
+ function resolveInputFormGroupClassName(group: InputFormGroupDefinition) {
719
+ const kind = String(group.Kind).toLocaleLowerCase()
720
+ if (group.Id === 'identity' || kind === String(inputFormGroupKinds.identity).toLocaleLowerCase()) {
721
+ return 'grid gap-3 lg:grid-cols-[1fr_1fr_minmax(18rem,1.25fr)]'
722
+ }
723
+
724
+ if (group.Id === 'lifecycle' || kind === String(inputFormGroupKinds.lifecycle).toLocaleLowerCase()) {
725
+ return 'grid gap-3'
726
+ }
727
+
728
+ if (group.Display.Orientation === 'horizontal') {
729
+ return 'flex flex-wrap items-end gap-3'
730
+ }
731
+
732
+ return 'grid gap-3 lg:grid-cols-2'
733
+ }
734
+
735
+ function isNavigationShellSlotKind(
736
+ slot: NavigationShellSlotDefinition,
737
+ numericKind: unknown,
738
+ label: string,
739
+ ) {
740
+ const normalizedKind = String(slot.Kind).replace(/[-_\s]/g, '').toLowerCase()
741
+ const normalizedLabel = label.replace(/[-_\s]/g, '').toLowerCase()
742
+
743
+ return slot.Kind === numericKind || normalizedKind === normalizedLabel
744
+ }
745
+
746
+ function hasAnyDesignToken(
747
+ value: string | null | undefined,
748
+ tokens: ReadonlySet<string>,
749
+ ): boolean {
750
+ return getDesignTokenCandidates(value).some((token) => tokens.has(token))
751
+ }
752
+
753
+ function getDesignTokenCandidates(value: string | null | undefined) {
754
+ const normalized = normalizeDesignToken(value)
755
+ if (!normalized) {
756
+ return []
757
+ }
758
+
759
+ const parts = normalized
760
+ .split(/[^a-z0-9]+/u)
761
+ .filter(Boolean)
762
+ return [
763
+ normalized,
764
+ ...parts,
765
+ ...parts.flatMap((part, index) =>
766
+ parts.slice(index + 1).map((next) => `${part}-${next}`),
767
+ ),
768
+ ]
769
+ }
770
+
771
+ function normalizeDesignToken(value: string | null | undefined) {
772
+ return value?.trim().toLowerCase() || null
773
+ }