feat: migrate foundation — tokens, SCSS primitives and core utilities - #2
Lantum-Brendan wants to merge 9 commits into
Conversation
Pulled the shared foundation over from webui — design tokens, surface/form/utility styles, plus the color, currency, dropdown and theme helpers — so ui-kit has its base layer. Foundation (Layer 0) now 10/10 curated: - tokens.css + _vars.scss (0.1-0.3, pre-existing) - _surfaces.scss, _form-styles.scss, _utilities.scss (0.4) - utils/colors.ts, utils/currency.ts (0.5) - composables/useDropdown.ts, useTheme.ts (0.5) Verified one-at-a-time: tsc clean, sass compile, happy-dom functional checks and parity vs webui.
There was a problem hiding this comment.
Pull request overview
This PR migrates a “foundation” layer into ui-kit, adding shared SCSS primitives plus reusable, zero-domain utilities/composables to serve as the base styling + helper layer for downstream UI usage.
Changes:
- Added currency parsing/formatting helpers and deterministic color utilities under
utils/. - Added
useThemeanduseDropdowncomposables undercomposables/for headless UI behavior. - Added foundational SCSS primitives for surfaces, forms, and utilities; updated package publishing include list.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| utils/currency.ts | Adds locale-aware parsing/formatting helpers for currency-like values. |
| utils/colors.ts | Adds deterministic color generation + palette helpers for theming/chart use. |
| composables/useTheme.ts | Adds headless theme state + persistence + DOM class toggling. |
| composables/useDropdown.ts | Adds click-outside / Escape-to-close dropdown state helper. |
| assets/scss/_utilities.scss | Adds shared utility classes (chips, icon-button, visibility helpers, etc.). |
| assets/scss/_surfaces.scss | Adds surface/tone-card primitives and dark-mode surface overrides. |
| assets/scss/_form-styles.scss | Adds shared structural form styling primitives. |
| package.json | Ensures utils/ and composables/ are included in published package files. |
Suppressed comments (2)
utils/currency.ts:199
- Same as above: formatting errors already fall back to a rounded string, but logging to
console.errorwill be noisy for consumers of this package.
} catch (error) {
console.error('Error formatting amount:', amount, error);
// Fallback to simple formatting
const rounded = Math.round(value * 100) / 100;
return showCurrency && displayCurrency ? `${rounded} ${displayCurrency}` : String(rounded);
composables/useTheme.ts:59
initThemereads fromlocalStoragewithout checking availability and without a try/catch. In some environments (SSR edge cases, private mode, storage disabled)localStorage.getItemcan throw and break initialization.
function initTheme() {
if (typeof window === 'undefined') return;
const saved = localStorage.getItem(THEME_STORAGE_KEY) as ThemeMode | null;
const initialTheme = saved || 'light';
theme.value = initialTheme;
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| } catch (error) { | ||
| console.error('Error parsing amount:', amountStr, error); | ||
| return { value: 0, currency: '' }; | ||
| } |
| // Extract currency (last word/token in the string, typically 3 uppercase letters) | ||
| const currencyMatch = amountStr.match(/\b([A-Z]{3})\b\s*$/); | ||
| const currency = currencyMatch ? currencyMatch[1] : ''; | ||
|
|
| export function getCurrencySymbol(currencyCode: string): string { | ||
| if (!currencyCode) return ''; | ||
| return CURRENCY_SYMBOL_MAP[currencyCode.toUpperCase()] || currencyCode; | ||
| } |
| /** | ||
| * Headless theme state machine — Tier 0 primitive (#10) | ||
| * Migrated from webui/composables/useTheme.ts | ||
| * Manages light/dark/system, persists to localStorage, toggles `document.documentElement.dark` (drives tokens.css). | ||
| */ |
| const theme = ref<ThemeMode>('light'); | ||
| const isDark = ref(false); | ||
|
|
||
| const THEME_STORAGE_KEY = 'trakli-theme'; |
| hash = (hash << 5) - hash + char; | ||
| hash = hash & hash; // Convert to 32-bit integer | ||
| } |
| export function generateColorPalette( | ||
| items: string[], | ||
| options: { | ||
| saturation?: number; | ||
| lightness?: number; | ||
| preferBasePalette?: boolean; | ||
| } = {} | ||
| ): Record<string, string> { | ||
| const { preferBasePalette = true } = options; | ||
| const colorMap: Record<string, string> = {}; | ||
|
|
||
| items.forEach((item, index) => { | ||
| colorMap[item] = getColorForItem(item, preferBasePalette ? index : undefined, options); | ||
| }); | ||
|
|
||
| return colorMap; | ||
| } |
| if (typeof localStorage !== 'undefined') { | ||
| localStorage.setItem(THEME_STORAGE_KEY, newTheme); | ||
| } |
…oggleButton with stories Decouple ThemeToggleButton i18n -> props, keep useTheme. Verified per file: diff vs webui, sass compile, vitest happy-dom mount, storybook build 17 stories.
… and ViewToggle with stories Decouple EmptyState/ViewToggle i18n -> props, replace solar icon with lucide Package. Improve LoadingSkeleton visibility: bg-gray -> border-light gradient. Verified per file: diff vs webui, sass compile, vitest happy-dom mount, storybook build.
…der and AuthFooterLink
… using primitives
…tor, CollapsibleSection, KpiCard, ConfirmModal, NotificationsContainer, SparkLine, ReportsTabs - AuthSocialLogin: relative kit imports (TButton + GoogleIcon) - AuthCarousel: fixed SCSS path, self-contained carousel widget - ThemeSelector: raw <button>/<div> → TDropdown + TDropdownItem - LanguageSelector: raw <button>/<div> → TDropdown + TDropdownItem - CollapsibleSection: header-button → TButton(text), edit-button → TButton(outline) - KpiCard: <div class=kpi-card> → TCard - ConfirmModal: close/cancel/confirm buttons → TButton, content div → TCard - NotificationsContainer: close <button> → TButton(text), imports ConfirmModal - SparkLine: migrated pure SVG chart, updated SCSS path - ReportsTabs: migrated pill-tab nav with animated indicator, updated SCSS path
nfebe
left a comment
There was a problem hiding this comment.
The decoupling is right: i18n, auth and router lifted out to props, raw buttons swapped for the primitives. Five things before this merges.
DescriptorRenderer lost three of its four branches. const variant = computed(() => 'default') is a constant, so the v-else fallback is dead code, and sidebar.nav, onboarding.steps and the plugin escape hatch are gone. The escape hatch is what lets a plugin layer register a real component. Restore them, or leave the file in webui.
EmptyState builds its copy by concatenation: You don't have any ${pageName.toLowerCase()} at the moment. webui ships six locales, so this cannot be translated. Take title and description as props.
Logo points at /logo.svg and retries /_nuxt/logo.svg on error. The package ships neither. Ship the SVG and import it, so consumers get it by installing.
SearchInput hand-rolls its own <input> because the layer has no input primitive. Worth adding TInput here rather than in the next migration.
Fourteen comments narrate the migration: Migrated from webui/composables/useTheme.ts, Tier 0 primitive (#10), mirrors webui/utils/apiErrors.ts. That context rots once the plan closes. Two files also open with @ts-nocheck.
One question rather than a change: TAvatar hardcodes Settings, Admin and Logout with an is_admin branch. Fine if every Trakli surface has that menu, wrong if a plugin UI needs a different one.
…primitives - DescriptorRenderer: restore plugin escape hatch, sidebar.nav, and onboarding branches - EmptyState: replace string concatenation with title/description props and slots - Logo: bundle package logo.svg directly via static import - TInput: introduce base input primitive and refactor SearchInput to compose it - TAvatar: add extensible menu items prop and slots for custom surfaces - Clean up transitional migration comments and verify strict typing
Pulled the shared foundation over from webui — design tokens, surface/form/utility styles, plus the color, currency, dropdown and theme helpers — so ui-kit has its base layer.
Foundation now curated: