Skip to content

Commit d8fbf58

Browse files
feat(design-tokens): DLT-3356 extend prota/deuter/trita accessible themes to semantic and components (#1237)
Co-authored-by: Joshua Hynes <jhynes@dialpad.com>
1 parent 09ff456 commit d8fbf58

17 files changed

Lines changed: 292 additions & 333 deletions

File tree

apps/dialtone-documentation/docs/.vuepress/theme/composables/useThemeManager.js

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -112,26 +112,27 @@ export function useThemeManager(options = {}) {
112112
* @param {string} brandName - The brand theme name
113113
*/
114114
const applyBrandTheme = (brandName) => {
115-
// DP is the base theme - for docs site, we don't need to apply brand overrides for DP
116-
// since the base DP theme CSS is already loaded in the HTML
117-
// For non-DP brands, we apply the brand override using the shared setBrand function
118-
if (brandName !== 'dp') {
119-
const theme = themes && themes[brandName];
120-
121-
if (!theme) {
122-
console.warn(`[useThemeManager] Theme "${brandName}" not found in loaded themes`);
123-
return;
124-
}
115+
// DP's base CSS is already loaded in the HTML — switching back to it requires
116+
// stripping any previously injected brand override tag. TODO: replace with
117+
// a clearBrand() export from @dialpad/dialtone-tokens/themes/config when added.
118+
if (brandName === 'dp') {
119+
document.getElementById('dialtone-css-brand-colors')?.remove();
120+
return;
121+
}
125122

126-
if (!theme.brand?.css) {
127-
console.warn(`[useThemeManager] Theme "${brandName}" missing brand.css property`);
128-
return;
129-
}
123+
const theme = themes && themes[brandName];
130124

131-
// Use shared setBrand function from config.js
132-
setBrand(theme, document.documentElement);
125+
if (!theme) {
126+
console.warn(`[useThemeManager] Theme "${brandName}" not found in loaded themes`);
127+
return;
133128
}
134-
// Note: The shared setBrand function handles style tag creation, updates, and cleanup
129+
130+
if (!theme.brand?.css) {
131+
console.warn(`[useThemeManager] Theme "${brandName}" missing brand.css property`);
132+
return;
133+
}
134+
135+
setBrand(theme, document.documentElement);
135136
};
136137

137138
/**

apps/dialtone-documentation/docs/scratch-color.md

Lines changed: 50 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ layout: Blank
1616
<dt-stack gap="100" direction="row">
1717
<span>
1818
<dt-text strength="bold">Mode:</dt-text>
19-
<dt-text tone="tertiary">{{ capitalize(currentMode) }}</dt-text>
19+
<dt-text tone="tertiary">{{ currentMode.charAt(0).toUpperCase() + currentMode.slice(1) }}</dt-text>
2020
</span>
2121
<span>
2222
<dt-text strength="bold">Contrast:</dt-text>
23-
<dt-text tone="tertiary">{{ capitalize(currentContrast) }}</dt-text>
23+
<dt-text tone="tertiary">{{ currentContrast.charAt(0).toUpperCase() + currentContrast.slice(1) }}</dt-text>
2424
</span>
2525
</dt-stack>
2626
<template #startIcon="{ iconSize }">
@@ -219,130 +219,73 @@ layout: Blank
219219
</dt-dropdown>
220220
</dt-stack>
221221
</dt-box>
222-
<dt-box surface="primary" padding="400">
222+
<dt-box class="foo" surface="primary" padding="400">
223223
<dt-stack gap="400">
224224
<dt-stack gap="200">
225-
<dt-text as="h3" kind="label" size="200" strength="bold" tone="primary">Surfaces</dt-text>
226225
<dt-stack direction="row" align="start">
227226
<dt-stack
228-
v-for="surfaceItem in primarySurfaces"
229-
:key="surfaceItem.name"
227+
v-for="{ name, dark } in [
228+
{ name: 'primary' },
229+
{ name: 'secondary' },
230+
{ name: 'moderate' },
231+
{ name: 'bold' },
232+
{ name: 'strong', dark: true },
233+
{ name: 'contrast', dark: true },
234+
]"
235+
:key="name"
230236
>
231-
<dt-box padding="300" :surface="surfaceItem.name">
232-
<dt-text v-if="surfaceItem.dark" v-dt-mode:invert as="p" kind="code" size="100" strength="bold" tone="primary">{{ surfaceItem.name }}</dt-text>
233-
<dt-text v-else as="p" kind="code" size="100" strength="bold" tone="primary">{{ surfaceItem.name }}</dt-text>
237+
<dt-box padding="300" :surface="name">
238+
<dt-text v-if="dark" v-dt-mode:invert as="p" kind="code" size="100" strength="bold" tone="primary">{{ name }}</dt-text>
239+
<dt-text v-else as="p" kind="code" size="100" strength="bold" tone="primary">{{ name }}</dt-text>
234240
</dt-box>
235-
<dt-box padding="300" :surface="`${surfaceItem.name}-opaque`">
236-
<dt-text v-if="surfaceItem.dark" v-dt-mode:invert as="p" kind="code" size="100" strength="bold" tone="primary">{{ surfaceItem.name }}-opaque</dt-text>
237-
<dt-text v-else as="p" kind="code" size="100" strength="bold" tone="primary">{{ surfaceItem.name }}-opaque</dt-text>
241+
<dt-box padding="300" :surface="`${name}-opaque`">
242+
<dt-text v-if="dark" v-dt-mode:invert as="p" kind="code" size="100" strength="bold" tone="primary">{{ name }}-opaque</dt-text>
243+
<dt-text v-else as="p" kind="code" size="100" strength="bold" tone="primary">{{ name }}-opaque</dt-text>
238244
</dt-box>
239245
</dt-stack>
240246
</dt-stack>
241247
<dt-stack direction="row" align="start">
242-
<dt-stack v-for="surfaceName in semanticSubtleSurfaces" :key="surfaceName">
243-
<dt-box padding="300" :surface="surfaceName"><dt-text as="p" kind="code" size="100" strength="bold" tone="primary">{{ surfaceName }}</dt-text></dt-box>
244-
<dt-box padding="300" :surface="`${surfaceName}-opaque`"><dt-text as="p" kind="code" size="100" strength="bold" tone="primary">{{ surfaceName }}-opaque</dt-text></dt-box>
248+
<dt-stack v-for="name in ['info', 'info-subtle', 'brand', 'brand-subtle', 'positive', 'positive-subtle', 'warning', 'warning-subtle', 'critical', 'critical-subtle']" :key="name">
249+
<dt-box padding="300" :surface="name"><dt-text as="p" kind="code" size="100" strength="bold" tone="primary">{{ name }}</dt-text></dt-box>
250+
<dt-box padding="300" :surface="`${name}-opaque`"><dt-text as="p" kind="code" size="100" strength="bold" tone="primary">{{ name }}-opaque</dt-text></dt-box>
245251
</dt-stack>
246252
</dt-stack>
247253
<dt-stack direction="row" align="start">
248-
<dt-box v-for="surfaceName in semanticStrongSurfaces" :key="surfaceName" padding="300" :surface="surfaceName">
249-
<dt-text as="p" kind="code" size="100" strength="bold" tone="primary" v-dt-mode:invert>{{ surfaceName }}</dt-text>
254+
<dt-box v-for="name in ['info-strong', 'brand-strong', 'positive-strong', 'warning-strong', 'critical-strong']" :key="name" padding="300" :surface="name">
255+
<dt-text as="p" kind="code" size="100" strength="bold" tone="primary" v-dt-mode:invert>{{ name }}</dt-text>
250256
</dt-box>
251257
</dt-stack>
252-
<dt-stack direction="row" align="start">
253-
<dt-box padding="300" surface="backdrop"><dt-text as="p" kind="code" size="100" strength="bold" tone="primary">backdrop</dt-text></dt-box>
254-
</dt-stack>
255258
</dt-stack>
256-
<dt-box surface="primary" padding="300" border-radius="200" v-dt-mode:invert>
257-
<dt-stack gap="200">
258-
<dt-text as="h3" kind="label" size="200" strength="bold" tone="primary">Surfaces — inverted</dt-text>
259-
<dt-stack direction="row" align="start">
260-
<dt-stack
261-
v-for="surfaceItem in primarySurfaces"
262-
:key="surfaceItem.name"
263-
>
264-
<dt-box padding="300" :surface="surfaceItem.name">
265-
<dt-text as="p" kind="code" size="100" strength="bold" tone="primary">{{ surfaceItem.name }}-inv</dt-text>
266-
</dt-box>
267-
<dt-box padding="300" :surface="`${surfaceItem.name}-opaque`">
268-
<dt-text as="p" kind="code" size="100" strength="bold" tone="primary">{{ surfaceItem.name }}-opa-inv</dt-text>
269-
</dt-box>
270-
</dt-stack>
271-
</dt-stack>
272-
<dt-stack direction="row" align="start">
273-
<dt-stack v-for="surfaceName in semanticSubtleSurfaces" :key="surfaceName">
274-
<dt-box padding="300" :surface="surfaceName"><dt-text as="p" kind="code" size="100" strength="bold" tone="primary">{{ surfaceName }}-inv</dt-text></dt-box>
275-
<dt-box padding="300" :surface="`${surfaceName}-opaque`"><dt-text as="p" kind="code" size="100" strength="bold" tone="primary">{{ surfaceName }}-opa-inv</dt-text></dt-box>
276-
</dt-stack>
277-
</dt-stack>
278-
<dt-stack direction="row" align="start">
279-
<dt-box v-for="surfaceName in semanticStrongSurfaces" :key="surfaceName" padding="300" :surface="surfaceName">
280-
<dt-text as="p" kind="code" size="100" strength="bold" tone="primary">{{ surfaceName }}-inv</dt-text>
281-
</dt-box>
282-
</dt-stack>
283-
</dt-stack>
284-
</dt-box>
285259
<dt-stack gap="200">
286-
<dt-text as="h3" kind="label" size="200" strength="bold" tone="primary">Borders</dt-text>
287260
<dt-stack
288-
v-for="borderRow in borderRows"
289-
:key="borderRow.join()"
261+
v-for="row in [
262+
['subtle', 'default', 'moderate', 'bold'],
263+
['critical', 'critical-subtle', 'critical-strong', 'info', 'info-subtle', 'info-strong', 'positive', 'positive-subtle', 'positive-strong', 'warning', 'warning-subtle', 'warning-strong', 'brand', 'brand-subtle', 'brand-strong'],
264+
]"
265+
:key="row.join()"
290266
direction="row"
291267
align="stretch"
292268
gap="200"
293269
>
294-
<dt-box v-for="borderName in borderRow" :key="borderName" padding="200" border-width="200" border-radius="500" :border-color="borderName">
295-
<dt-text as="p" kind="code" size="100" strength="bold" tone="primary">{{ borderName }}</dt-text>
270+
<dt-box v-for="name in row" :key="name" padding="200" border-width="200" border-radius="500" :border-color="name">
271+
<dt-text as="p" kind="code" size="100" strength="bold" tone="primary">{{ name }}</dt-text>
296272
</dt-box>
297273
</dt-stack>
298274
</dt-stack>
299-
<dt-box surface="primary" padding="300" border-radius="200" v-dt-mode:invert>
300-
<dt-stack gap="200">
301-
<dt-text as="h3" kind="label" size="200" strength="bold" tone="primary">Borders — inverted</dt-text>
302-
<dt-stack
303-
v-for="borderRow in borderRows"
304-
:key="borderRow.join()"
305-
direction="row"
306-
align="stretch"
307-
gap="200"
308-
>
309-
<dt-box v-for="borderName in borderRow" :key="borderName" padding="200" border-width="200" border-radius="500" :border-color="borderName">
310-
<dt-text as="p" kind="code" size="100" strength="bold" tone="primary">{{ borderName }}-inv</dt-text>
311-
</dt-box>
312-
</dt-stack>
313-
</dt-stack>
314-
</dt-box>
315-
<dt-stack gap="200">
316-
<dt-text as="h3" kind="label" size="200" strength="bold" tone="primary">Foreground (text tones)</dt-text>
317-
<dt-stack direction="row" gap="100" align="baseline">
318-
<dt-text v-for="toneName in foregroundTones" :key="toneName" :tone="toneName">{{ toneName }}</dt-text>
319-
</dt-stack>
275+
<dt-stack direction="row" gap="100" align="baseline">
276+
<dt-text v-for="t in ['primary', 'secondary', 'tertiary', 'muted', 'disabled', 'placeholder', 'positive', 'positive-strong', 'warning', 'critical', 'critical-strong', 'info', 'info-strong']" :key="t" :tone="t">{{ t }}</dt-text>
320277
</dt-stack>
321-
<dt-box surface="primary" padding="300" border-radius="200" v-dt-mode:invert>
322-
<dt-stack gap="200">
323-
<dt-text as="h3" kind="label" size="200" strength="bold" tone="primary">Foreground — inverted</dt-text>
324-
<dt-stack direction="row" gap="100" align="baseline">
325-
<dt-text v-for="toneName in foregroundTones" :key="toneName" :tone="toneName">{{ toneName }}</dt-text>
326-
</dt-stack>
327-
</dt-stack>
328-
</dt-box>
329-
<dt-stack gap="200">
330-
<dt-text as="h3" kind="label" size="200" strength="bold" tone="primary">Links</dt-text>
331-
<dt-stack direction="row" gap="100" align="baseline">
332-
<dt-link v-for="linkTone in linkTones" :key="linkTone" href="#link" :tone="linkTone === 'base' ? undefined : linkTone">{{ capitalize(linkTone) }} link</dt-link>
333-
</dt-stack>
278+
<dt-stack direction="row" gap="100" align="baseline">
279+
<dt-link v-for="t in ['base', 'critical', 'muted', 'positive', 'warning', 'info', 'mention']" :key="t" href="#link" :tone="t === 'base' ? undefined : t">{{ capitalize(t) }} link</dt-link>
334280
</dt-stack>
335-
<dt-box surface="primary" padding="300" border-radius="200" v-dt-mode:invert>
336-
<dt-stack gap="200">
337-
<dt-text as="h3" kind="label" size="200" strength="bold" tone="primary">Links — inverted</dt-text>
338-
<dt-stack direction="row" gap="100" align="baseline">
339-
<dt-link v-for="linkTone in linkTones" :key="linkTone" href="#link" :tone="linkTone === 'base' ? undefined : linkTone">{{ capitalize(linkTone) }} link</dt-link>
340-
</dt-stack>
341-
</dt-stack>
342-
</dt-box>
343281
<dt-stack gap="200">
344282
<dt-stack
345-
v-for="row in buttonVariants"
283+
v-for="row in [
284+
{ kind: undefined, importances: [undefined, 'outlined', 'clear'] },
285+
{ kind: 'critical', importances: [undefined, 'outlined', 'clear'] },
286+
{ kind: 'positive', importances: [undefined, 'outlined', 'clear'] },
287+
{ kind: 'muted', importances: ['clear', 'outlined'] },
288+
]"
346289
:key="row.kind || 'default'"
347290
gap="100"
348291
direction="row"
@@ -356,17 +299,17 @@ layout: Blank
356299
<dt-presence presence="away" />
357300
</dt-stack>
358301
<dt-stack direction="row" gap="100">
359-
<template v-for="kind in badgeKinds" :key="kind || 'text'">
360-
<dt-badge v-for="t in badgeTypes" :key="`${kind || 'text'}-${t || 'default'}`" :text="kind === 'count' ? '1' : 'Label'" :kind="kind" :type="t" />
302+
<template v-for="kind in [undefined, 'count']" :key="kind || 'text'">
303+
<dt-badge v-for="t in [undefined, 'info', 'positive', 'warning', 'critical']" :key="`${kind || 'text'}-${t || 'default'}`" :text="kind === 'count' ? '1' : 'Label'" :kind="kind" :type="t" />
361304
</template>
362305
</dt-stack>
363306
<dt-stack direction="row" gap="200">
364307
<dt-stack v-for="important in [false, true]" :key="important" gap="100">
365-
<example-notice v-for="k in noticeKinds" :key="k" :important="important" :kind="k" :title="`${capitalize(k)} title (optional)`" />
308+
<example-notice v-for="k in ['base', 'critical', 'info', 'positive', 'warning']" :key="k" :important="important" :kind="k" :title="`${capitalize(k)} title (optional)`" />
366309
</dt-stack>
367310
</dt-stack>
368311
<dt-stack direction="row" gap="200">
369-
<dt-stack v-for="k in progressKinds" :key="k" gap="100">
312+
<dt-stack v-for="k in ['default', 'brand', 'critical', 'positive', 'warning', 'info', 'ai']" :key="k" gap="100">
370313
<dt-progress-circle :kind="k" :progress="66" :aria-label="`kind ${k}`" />
371314
</dt-stack>
372315
</dt-stack>
@@ -391,6 +334,12 @@ layout: Blank
391334
</dt-stack>
392335
</dt-box>
393336

337+
<style>
338+
.foo {
339+
/* background-image: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H20V20H0V0Z' fill='var(--dt-color-surface-primary)'/%3E%3Cpath d='M0 0L20 20M20 0L0 20' stroke='%23666' stroke-width='1'/%3E%3C/svg%3E"); */
340+
}
341+
</style>
342+
394343
<script setup>
395344
import { useThemeManager } from '@composables/useThemeManager';
396345
import ExampleNotice from '@exampleComponents/ExampleNotice.vue';
@@ -401,50 +350,6 @@ const messages = {
401350
positive: { "message": "Positive validation message", "type": "positive" },
402351
};
403352

404-
// Each section renders twice: once normally and once inside a v-dt-mode:invert
405-
// wrapper, which swaps all tokens to their *-inverted counterparts automatically.
406-
407-
const primarySurfaces = [
408-
{ name: 'primary' },
409-
{ name: 'secondary' },
410-
{ name: 'moderate' },
411-
{ name: 'bold' },
412-
{ name: 'strong', dark: true },
413-
{ name: 'contrast', dark: true },
414-
];
415-
const semanticSubtleSurfaces = [
416-
'info-subtle', 'info',
417-
'brand-subtle', 'brand',
418-
'positive-subtle', 'positive',
419-
'warning-subtle', 'warning',
420-
'critical-subtle', 'critical',
421-
];
422-
const semanticStrongSurfaces = [
423-
'info-strong', 'brand-strong', 'positive-strong', 'warning-strong', 'critical-strong',
424-
];
425-
426-
const borderRows = [
427-
['subtle', 'default', 'moderate', 'bold'],
428-
['critical-subtle', 'critical', 'critical-strong', 'info-subtle', 'info', 'info-strong', 'positive-subtle', 'positive', 'positive-strong', 'warning-subtle', 'warning', 'warning-strong', 'brand-subtle', 'brand', 'brand-strong'],
429-
['focus', 'accent'],
430-
];
431-
432-
const foregroundTones = [
433-
'primary', 'secondary', 'tertiary', 'muted', 'disabled', 'placeholder',
434-
'positive', 'warning', 'critical', 'info',
435-
];
436-
const linkTones = ['base', 'critical', 'muted', 'positive', 'warning', 'info', 'mention'];
437-
const buttonVariants = [
438-
{ kind: undefined, importances: [undefined, 'outlined', 'clear'] },
439-
{ kind: 'critical', importances: [undefined, 'outlined', 'clear'] },
440-
{ kind: 'positive', importances: [undefined, 'outlined', 'clear'] },
441-
{ kind: 'muted', importances: ['clear', 'outlined'] },
442-
];
443-
const badgeKinds = [undefined, 'count'];
444-
const badgeTypes = [undefined, 'info', 'positive', 'warning', 'critical'];
445-
const noticeKinds = ['base', 'critical', 'info', 'positive', 'warning'];
446-
const progressKinds = ['default', 'brand', 'critical', 'positive', 'warning', 'info', 'ai'];
447-
448353
const {
449354
currentMode,
450355
currentContrast,

packages/dialtone-css/lib/build/less/dialtone.less

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,10 @@
133133
}
134134
}
135135
}
136+
137+
@layer dialtone.utilities {
138+
/*
139+
[data-dt-brand="prota-deuter"] { --dt-color-surface-info-opaque: oklch(from var(--dt-color-blue-300) l c h / 0.5) !important; }
140+
[data-dt-brand="prota-deuter"][data-dt-mode="dark"] { --dt-color-surface-info-opaque: oklch(from var(--dt-color-blue-300) l c h / 0.6) !important; }
141+
*/
142+
}

packages/dialtone-tokens/tokens/$metadata.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,12 @@
3737
"components/avatar/default",
3838
"components/badge/default",
3939
"components/badge/dark",
40+
"components/badge/default-prota-deuter",
4041
"components/button/default",
4142
"components/checkbox/default",
4243
"components/icon/default",
4344
"components/presence/default",
4445
"components/presence/dark",
45-
"components/presence/default-prota-deuter",
46-
"components/presence/dark-prota-deuter",
4746
"components/radio/default",
4847
"theme/102/default",
4948
"theme/102/dark",

packages/dialtone-tokens/tokens/$themes.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6194,11 +6194,11 @@
61946194
"theme/prota-deuter/default": "enabled",
61956195
"components/avatar/default": "enabled",
61966196
"components/badge/default": "enabled",
6197+
"components/badge/default-prota-deuter": "enabled",
61976198
"components/button/default": "enabled",
61986199
"components/checkbox/default": "enabled",
61996200
"components/icon/default": "enabled",
62006201
"components/presence/default": "enabled",
6201-
"components/presence/default-prota-deuter": "enabled",
62026202
"components/radio/default": "enabled"
62036203
},
62046204
"$figmaVariableReferences": {},
@@ -6218,13 +6218,12 @@
62186218
"theme/prota-deuter/dark": "enabled",
62196219
"components/avatar/default": "enabled",
62206220
"components/badge/default": "enabled",
6221+
"components/badge/default-prota-deuter": "enabled",
62216222
"components/button/default": "enabled",
62226223
"components/checkbox/default": "enabled",
62236224
"components/icon/default": "enabled",
62246225
"components/presence/default": "enabled",
62256226
"components/presence/dark": "enabled",
6226-
"components/presence/default-prota-deuter": "enabled",
6227-
"components/presence/dark-prota-deuter": "enabled",
62286227
"components/radio/default": "enabled"
62296228
},
62306229
"$figmaVariableReferences": {},

0 commit comments

Comments
 (0)