fix(frontend): resolve 4 P2 architecture issues (Wave 3)
FRONT-I-01: Extract Auth components to Razor Class Library packages/blazor-ui/
- Created GoodGo.BlazorUi RCL (net10.0, MudBlazor 8.15) at packages/blazor-ui/
- Moved AuthButton, AuthCard, AuthInput, OtpInput, BrandPanel, SocialLogin, LanguageSwitcher
- Referenced RCL from WebClientTpos.Client via ProjectReference
- Added GoodGo.BlazorUi.Components.Auth/Common namespaces to _Imports.razor
FRONT-I-02: Add ARIA/accessibility attributes (WCAG 2.1 AA)
- AuthButton: aria-label, aria-busy, aria-disabled, aria-hidden on decorative icons
- OtpInput: role=group, aria-label per digit, autocomplete=one-time-code
- PosLayout: aria-expanded + aria-controls on sidebar/order toggles, aria-label on all icon buttons
FRONT-I-03: Implement Style Dictionary design token pipeline
- Created packages/design-tokens/ with token JSON (color, spacing, typography, border)
- Style Dictionary config outputs: CSS custom properties → wwwroot/css/tokens.generated.css
- Second output: C# constants → packages/blazor-ui/DesignTokens/DesignTokens.g.cs
- Added tokens:build script to root package.json
- Added tokens.generated.css link to index.html (before app.css for cascade correctness)
FRONT-I-04: Replace eval() in OtpInput with safe JS interop
- Created wwwroot/js/otp-input.js with window.focusOtpInput(index) helper
- Replaced JS.InvokeVoidAsync("eval", ...) with JS.InvokeVoidAsync("focusOtpInput", index)
- Eliminates CSP-violating eval(), improves security and debuggability
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -23,8 +23,12 @@
|
||||
<header class="pos-status-bar">
|
||||
<div class="pos-status-bar__left">
|
||||
@* EN: Hamburger menu — visible on tablet/mobile only / VI: Menu hamburger — chi hien thi tren tablet/mobile *@
|
||||
<button class="pos-mobile-toggle" @onclick="ToggleSidebar" title="Menu">
|
||||
<i data-lucide="@(_sidebarOpen ? "x" : "menu")" style="width:20px;height:20px;"></i>
|
||||
<button class="pos-mobile-toggle"
|
||||
@onclick="ToggleSidebar"
|
||||
aria-label="@(_sidebarOpen ? "Đóng menu" : "Mở menu")"
|
||||
aria-expanded="@(_sidebarOpen ? "true" : "false")"
|
||||
aria-controls="pos-sidebar">
|
||||
<i data-lucide="@(_sidebarOpen ? "x" : "menu")" style="width:20px;height:20px;" aria-hidden="true"></i>
|
||||
</button>
|
||||
<span class="pos-status-bar__logo">GoodGo POS</span>
|
||||
<span class="pos-status-bar__store">@StoreName</span>
|
||||
@@ -37,15 +41,19 @@
|
||||
<span class="pos-status-bar__time">@_currentTime</span>
|
||||
@* EN: Order panel toggle — visible on tablet/mobile when order panel is hidden
|
||||
VI: Nút mở panel đơn hàng — hiện trên tablet/mobile khi panel đơn hàng ẩn *@
|
||||
<button class="pos-order-toggle" @onclick="ToggleOrderPanel" title="Đơn hàng">
|
||||
<i data-lucide="shopping-cart" style="width:18px;height:18px;"></i>
|
||||
<button class="pos-order-toggle"
|
||||
@onclick="ToggleOrderPanel"
|
||||
aria-label="@(_orderCount > 0 ? $"Đơn hàng ({_orderCount})" : "Đơn hàng")"
|
||||
aria-expanded="@(_orderPanelOpen ? "true" : "false")"
|
||||
aria-controls="pos-order-drawer-content">
|
||||
<i data-lucide="shopping-cart" style="width:18px;height:18px;" aria-hidden="true"></i>
|
||||
@if (_orderCount > 0)
|
||||
{
|
||||
<span class="pos-order-toggle__badge">@_orderCount</span>
|
||||
<span class="pos-order-toggle__badge" aria-hidden="true">@_orderCount</span>
|
||||
}
|
||||
</button>
|
||||
<button class="admin-icon-btn pos-admin-btn" @onclick="GoToPortal" title="Quản lý">
|
||||
<i data-lucide="settings"></i>
|
||||
<button class="admin-icon-btn pos-admin-btn" @onclick="GoToPortal" aria-label="Quản lý">
|
||||
<i data-lucide="settings" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
@@ -60,11 +68,13 @@
|
||||
|
||||
@* EN: Sidebar navigation — collapsible on tablet/mobile
|
||||
VI: Sidebar điều hướng — thu gọn trên tablet/mobile *@
|
||||
<nav class="pos-sidebar @(_sidebarOpen ? "pos-sidebar--open" : "")">
|
||||
<nav id="pos-sidebar"
|
||||
class="pos-sidebar @(_sidebarOpen ? "pos-sidebar--open" : "")"
|
||||
aria-label="POS navigation">
|
||||
<div class="pos-sidebar__header">
|
||||
<span style="font-size:15px;font-weight:700;color:var(--pos-orange-primary);">Menu</span>
|
||||
<button class="pos-sidebar__close" @onclick="CloseSidebar">
|
||||
<i data-lucide="x" style="width:18px;height:18px;"></i>
|
||||
<button class="pos-sidebar__close" @onclick="CloseSidebar" aria-label="Đóng menu">
|
||||
<i data-lucide="x" style="width:18px;height:18px;" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pos-sidebar__nav">
|
||||
@@ -113,8 +123,8 @@
|
||||
<div class="pos-order-drawer @(_orderPanelOpen ? "pos-order-drawer--open" : "")">
|
||||
<div class="pos-order-drawer__header">
|
||||
<span style="font-size:15px;font-weight:700;">Đơn hàng</span>
|
||||
<button class="pos-order-drawer__close" @onclick="CloseOrderPanel">
|
||||
<i data-lucide="x" style="width:18px;height:18px;"></i>
|
||||
<button class="pos-order-drawer__close" @onclick="CloseOrderPanel" aria-label="Đóng panel đơn hàng">
|
||||
<i data-lucide="x" style="width:18px;height:18px;" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pos-order-drawer__content" id="pos-order-drawer-content">
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WebClientTpos.Shared\WebClientTpos.Shared.csproj" />
|
||||
<!--
|
||||
EN: Shared Blazor UI component library (Auth components, LanguageSwitcher, etc.)
|
||||
VI: Thư viện component Blazor dùng chung (Auth components, LanguageSwitcher, v.v.)
|
||||
-->
|
||||
<ProjectReference Include="..\..\..\..\packages\blazor-ui\GoodGo.BlazorUi.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -13,4 +13,6 @@
|
||||
@using WebClientTpos.Shared.DTOs
|
||||
@using WebClientTpos.Client.Components
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using GoodGo.BlazorUi.Components.Auth
|
||||
@using GoodGo.BlazorUi.Components.Common
|
||||
|
||||
|
||||
@@ -55,6 +55,14 @@
|
||||
<!-- VI: CSS MudBlazor -->
|
||||
<link href="/_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
||||
|
||||
<!--
|
||||
EN: Design tokens (auto-generated by Style Dictionary — run `pnpm --filter @goodgo/design-tokens build`).
|
||||
Must be loaded FIRST so downstream CSS can reference --gds-* custom properties.
|
||||
VI: Design tokens (tự động tạo bởi Style Dictionary — chạy `pnpm --filter @goodgo/design-tokens build`).
|
||||
Phải load ĐẦU TIÊN để CSS downstream có thể tham chiếu --gds-* custom properties.
|
||||
-->
|
||||
<link rel="stylesheet" href="/css/tokens.generated.css" />
|
||||
|
||||
<!-- EN: Custom CSS -->
|
||||
<!-- VI: CSS tùy chỉnh -->
|
||||
<link rel="stylesheet" href="/css/app.css" />
|
||||
@@ -119,6 +127,10 @@
|
||||
<!-- EN: POS helpers (receipt printing, etc.) -->
|
||||
<!-- VI: Hàm hỗ trợ POS (in hóa đơn, v.v.) -->
|
||||
<script src="/js/pos-helpers.js"></script>
|
||||
|
||||
<!-- EN: OTP input focus helpers (safe JS interop, no eval) -->
|
||||
<!-- VI: Hàm hỗ trợ focus OTP input (JS interop an toàn, không dùng eval) -->
|
||||
<script src="/js/otp-input.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* EN: OTP input focus helpers — safe replacement for eval()-based focus management.
|
||||
* VI: Hàm hỗ trợ focus OTP input — thay thế an toàn cho cách dùng eval() để quản lý focus.
|
||||
*
|
||||
* Used by OtpInput.razor via IJSRuntime.InvokeVoidAsync.
|
||||
*/
|
||||
|
||||
/**
|
||||
* EN: Focus the OTP input at the given index.
|
||||
* VI: Focus vào ô OTP tại vị trí chỉ định.
|
||||
* @param {number} index - Zero-based index of the OTP input to focus
|
||||
*/
|
||||
window.focusOtpInput = function (index) {
|
||||
var el = document.getElementById('otp-' + index);
|
||||
if (el) {
|
||||
el.focus();
|
||||
}
|
||||
};
|
||||
@@ -25,6 +25,7 @@
|
||||
"scripts": {
|
||||
"dev": "pnpm --parallel -r dev",
|
||||
"build": "pnpm -r build",
|
||||
"tokens:build": "pnpm --filter @goodgo/design-tokens build",
|
||||
"test": "pnpm -r --filter='!@goodgo/service-template' test",
|
||||
"lint": "pnpm -r lint",
|
||||
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
|
||||
|
||||
@@ -5,15 +5,18 @@
|
||||
|
||||
<button type="@ButtonType"
|
||||
class="auth-btn auth-btn--@Variant @CssClass"
|
||||
disabled="@Disabled"
|
||||
disabled="@(Disabled || Loading)"
|
||||
aria-label="@AriaLabel"
|
||||
aria-busy="@(Loading ? "true" : "false")"
|
||||
aria-disabled="@((Disabled || Loading) ? "true" : "false")"
|
||||
@onclick="OnClick">
|
||||
@if (Loading)
|
||||
{
|
||||
<span class="spinner-small"></span>
|
||||
<span class="spinner-small" aria-hidden="true"></span>
|
||||
}
|
||||
@if (IconName != null)
|
||||
{
|
||||
<i data-lucide="@IconName"></i>
|
||||
<i data-lucide="@IconName" aria-hidden="true"></i>
|
||||
}
|
||||
@ChildContent
|
||||
</button>
|
||||
@@ -61,6 +64,12 @@
|
||||
/// </summary>
|
||||
[Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// EN: Accessible label for screen readers (required when button has no visible text).
|
||||
/// VI: Nhãn accessible cho screen reader (bắt buộc khi button không có text hiển thị).
|
||||
/// </summary>
|
||||
[Parameter] public string? AriaLabel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// EN: Button content.
|
||||
/// VI: Nội dung button.
|
||||
@@ -5,7 +5,9 @@
|
||||
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<div class="auth-otp-group">
|
||||
<div class="auth-otp-group"
|
||||
role="group"
|
||||
aria-label="@GroupAriaLabel">
|
||||
@for (int i = 0; i < DigitCount; i++)
|
||||
{
|
||||
var index = i;
|
||||
@@ -13,6 +15,8 @@
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
maxlength="1"
|
||||
autocomplete="one-time-code"
|
||||
aria-label="@($"Digit {index + 1} of {DigitCount}")"
|
||||
class="auth-otp-input @(UseBlueTheme ? "auth-otp-input--blue" : "") @(!string.IsNullOrEmpty(_digits[index]) ? "auth-otp-input--filled" : "")"
|
||||
value="@_digits[index]"
|
||||
@oninput="(e) => HandleInput(e, index)"
|
||||
@@ -35,6 +39,12 @@
|
||||
/// </summary>
|
||||
[Parameter] public bool UseBlueTheme { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// EN: Accessible group label for screen readers.
|
||||
/// VI: Nhãn nhóm accessible cho screen reader.
|
||||
/// </summary>
|
||||
[Parameter] public string GroupAriaLabel { get; set; } = "Enter OTP code";
|
||||
|
||||
/// <summary>
|
||||
/// EN: Callback when all digits are entered.
|
||||
/// VI: Callback khi tất cả chữ số được nhập.
|
||||
@@ -64,7 +74,7 @@
|
||||
{
|
||||
// EN: Auto-advance to next input
|
||||
// VI: Tự động chuyển sang ô tiếp theo
|
||||
await JS.InvokeVoidAsync("eval", $"document.getElementById('otp-{index + 1}')?.focus()");
|
||||
await JS.InvokeVoidAsync("focusOtpInput", index + 1);
|
||||
}
|
||||
|
||||
// EN: Check if all digits are filled
|
||||
@@ -83,7 +93,7 @@
|
||||
// EN: Move to previous input on backspace
|
||||
// VI: Chuyển về ô trước khi nhấn backspace
|
||||
_digits[index - 1] = "";
|
||||
await JS.InvokeVoidAsync("eval", $"document.getElementById('otp-{index - 1}')?.focus()");
|
||||
await JS.InvokeVoidAsync("focusOtpInput", index - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
packages/blazor-ui/GoodGo.BlazorUi.csproj
Normal file
26
packages/blazor-ui/GoodGo.BlazorUi.csproj
Normal file
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
EN: Razor Class Library — shared Blazor UI components for GoodGo platform.
|
||||
Consumed by web-client-tpos-net and web-client-base-net.
|
||||
VI: Razor Class Library — thư viện component Blazor dùng chung cho nền tảng GoodGo.
|
||||
Được dùng bởi web-client-tpos-net và web-client-base-net.
|
||||
-->
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AddRazorSupportForMvc>false</AddRazorSupportForMvc>
|
||||
<AssemblyName>GoodGo.BlazorUi</AssemblyName>
|
||||
<RootNamespace>GoodGo.BlazorUi</RootNamespace>
|
||||
<Version>1.0.0</Version>
|
||||
<Description>Shared Blazor UI component library for GoodGo platform</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="10.0.2" />
|
||||
<PackageReference Include="MudBlazor" Version="8.15.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
10
packages/blazor-ui/_Imports.razor
Normal file
10
packages/blazor-ui/_Imports.razor
Normal file
@@ -0,0 +1,10 @@
|
||||
@using System.Net.Http
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.JSInterop
|
||||
@using MudBlazor
|
||||
@using GoodGo.BlazorUi
|
||||
@using GoodGo.BlazorUi.Components.Auth
|
||||
@using GoodGo.BlazorUi.Components.Common
|
||||
14
packages/design-tokens/package.json
Normal file
14
packages/design-tokens/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "@goodgo/design-tokens",
|
||||
"version": "1.0.0",
|
||||
"description": "GoodGo design token pipeline — single source of truth for CSS vars and C# constants",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "style-dictionary build --config style-dictionary.config.mjs",
|
||||
"watch": "node --watch-path=./tokens style-dictionary.config.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"style-dictionary": "^4.3.0"
|
||||
}
|
||||
}
|
||||
109
packages/design-tokens/style-dictionary.config.mjs
Normal file
109
packages/design-tokens/style-dictionary.config.mjs
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* EN: Style Dictionary configuration for GoodGo design token pipeline.
|
||||
* Transforms token JSON → CSS custom properties + C# constants.
|
||||
* Single source of truth: packages/design-tokens/tokens/*.json
|
||||
*
|
||||
* VI: Cấu hình Style Dictionary cho pipeline design token GoodGo.
|
||||
* Chuyển đổi JSON token → CSS custom properties + C# constants.
|
||||
* Nguồn duy nhất: packages/design-tokens/tokens/*.json
|
||||
*
|
||||
* Usage: node --input-type=module style-dictionary.config.mjs
|
||||
* Or: npx style-dictionary build --config style-dictionary.config.mjs
|
||||
*/
|
||||
|
||||
import StyleDictionary from 'style-dictionary';
|
||||
|
||||
// ─── Custom transform: camelCase → kebab-case for CSS custom properties ───────
|
||||
StyleDictionary.registerTransform({
|
||||
name: 'name/goodgo/css',
|
||||
type: 'name',
|
||||
transform: (token) => {
|
||||
// Flatten path, convert camelCase to kebab-case, join with '-'
|
||||
return token.path
|
||||
.map((part) => part.replace(/([A-Z])/g, '-$1').toLowerCase())
|
||||
.join('-');
|
||||
},
|
||||
});
|
||||
|
||||
// ─── Custom formatter: C# static class with const string fields ───────────────
|
||||
StyleDictionary.registerFormat({
|
||||
name: 'csharp/goodgo-tokens',
|
||||
format: ({ dictionary }) => {
|
||||
const indent = ' ';
|
||||
const lines = dictionary.allTokens.map((token) => {
|
||||
const name = token.path
|
||||
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
|
||||
.join('');
|
||||
const comment = token.comment ? ` // ${token.comment}` : '';
|
||||
return `${indent}public const string ${name} = "${token.value}";${comment}`;
|
||||
});
|
||||
|
||||
return [
|
||||
'// <auto-generated>',
|
||||
'// EN: This file is auto-generated by the Style Dictionary pipeline.',
|
||||
'// Do NOT edit manually. Update tokens/*.json and re-run the build.',
|
||||
'// VI: File này được tạo tự động bởi Style Dictionary pipeline.',
|
||||
'// KHÔNG chỉnh sửa thủ công. Cập nhật tokens/*.json và build lại.',
|
||||
'// </auto-generated>',
|
||||
'',
|
||||
'namespace GoodGo.BlazorUi.DesignTokens;',
|
||||
'',
|
||||
'/// <summary>',
|
||||
'/// EN: Auto-generated design token constants. Maps 1:1 with CSS custom properties.',
|
||||
'/// VI: Hằng số design token tự động. Ánh xạ 1:1 với CSS custom properties.',
|
||||
'/// </summary>',
|
||||
'public static class DesignTokens',
|
||||
'{',
|
||||
...lines,
|
||||
'}',
|
||||
'',
|
||||
].join('\n');
|
||||
},
|
||||
});
|
||||
|
||||
// ─── Main config ──────────────────────────────────────────────────────────────
|
||||
export default {
|
||||
source: ['tokens/**/*.json'],
|
||||
|
||||
platforms: {
|
||||
/**
|
||||
* EN: CSS custom properties — consumed by app.css and component stylesheets.
|
||||
* Output: apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/css/tokens.generated.css
|
||||
* VI: CSS custom properties — dùng trong app.css và stylesheet component.
|
||||
*/
|
||||
css: {
|
||||
transformGroup: 'css',
|
||||
transforms: ['name/goodgo/css', 'attribute/cti'],
|
||||
prefix: 'gds',
|
||||
buildPath: '../../apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/css/',
|
||||
files: [
|
||||
{
|
||||
destination: 'tokens.generated.css',
|
||||
format: 'css/variables',
|
||||
options: {
|
||||
outputReferences: false,
|
||||
selector: ':root',
|
||||
},
|
||||
filter: (token) => token.attributes?.category !== 'primitive',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* EN: C# constants — consumed by Blazor components for type-safe token access.
|
||||
* Output: packages/blazor-ui/DesignTokens/DesignTokens.g.cs
|
||||
* VI: C# constants — dùng trong Blazor component để truy cập token an toàn.
|
||||
*/
|
||||
csharp: {
|
||||
transforms: ['attribute/cti'],
|
||||
buildPath: '../blazor-ui/DesignTokens/',
|
||||
files: [
|
||||
{
|
||||
destination: 'DesignTokens.g.cs',
|
||||
format: 'csharp/goodgo-tokens',
|
||||
filter: (token) => token.attributes?.category !== 'primitive',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
10
packages/design-tokens/tokens/border.json
Normal file
10
packages/design-tokens/tokens/border.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"border": {
|
||||
"radius": {
|
||||
"base": { "value": "6px", "comment": "Default border radius" },
|
||||
"lg": { "value": "10px", "comment": "Large border radius" },
|
||||
"xl": { "value": "14px", "comment": "XL border radius" },
|
||||
"2xl": { "value": "20px", "comment": "2XL border radius" }
|
||||
}
|
||||
}
|
||||
}
|
||||
72
packages/design-tokens/tokens/color.json
Normal file
72
packages/design-tokens/tokens/color.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"color": {
|
||||
"primitive": {
|
||||
"neutral": {
|
||||
"0": { "value": "#ffffff", "comment": "Pure white" },
|
||||
"50": { "value": "#fafafa", "comment": "Near white" },
|
||||
"100": { "value": "#ADADB0", "comment": "Light gray" },
|
||||
"200": { "value": "#8B8B90", "comment": "Medium gray" },
|
||||
"300": { "value": "#6B6B70", "comment": "Muted gray" },
|
||||
"400": { "value": "#3A3A3E", "comment": "Dark gray" },
|
||||
"500": { "value": "#2A2A2E", "comment": "Darker gray" },
|
||||
"600": { "value": "#1F1F23", "comment": "Surface border" },
|
||||
"700": { "value": "#1A1A1D", "comment": "Elevated surface" },
|
||||
"800": { "value": "#111113", "comment": "Base surface" },
|
||||
"900": { "value": "#0A0A0B", "comment": "Page background" },
|
||||
"950": { "value": "#050506", "comment": "Near black" }
|
||||
},
|
||||
"accent": {
|
||||
"400": { "value": "#FF8A4C", "comment": "Orange light" },
|
||||
"500": { "value": "#FF5C00", "comment": "Orange primary" },
|
||||
"600": { "value": "#E05200", "comment": "Orange dark" }
|
||||
},
|
||||
"success": {
|
||||
"500": { "value": "#22C55E", "comment": "Success green" }
|
||||
},
|
||||
"white": { "value": "#ffffff" },
|
||||
"black": { "value": "#000000" },
|
||||
"overlay": { "value": "rgba(0, 0, 0, 0.6)" }
|
||||
},
|
||||
"semantic": {
|
||||
"bg": {
|
||||
"page": { "value": "{color.primitive.neutral.900.value}", "comment": "Page background" },
|
||||
"surface": { "value": "{color.primitive.neutral.800.value}", "comment": "Card / panel surface" },
|
||||
"elevated": { "value": "{color.primitive.neutral.700.value}", "comment": "Elevated elements" },
|
||||
"interactive": { "value": "{color.primitive.neutral.500.value}", "comment": "Interactive backgrounds" },
|
||||
"surfaceHover": { "value": "{color.primitive.neutral.600.value}", "comment": "Hover state" },
|
||||
"overlay": { "value": "rgba(10, 10, 11, 0.9)", "comment": "Modal overlay" }
|
||||
},
|
||||
"text": {
|
||||
"primary": { "value": "{color.primitive.white.value}", "comment": "Primary text" },
|
||||
"secondary": { "value": "{color.primitive.neutral.100.value}", "comment": "Secondary text" },
|
||||
"tertiary": { "value": "{color.primitive.neutral.200.value}", "comment": "Tertiary / helper text" },
|
||||
"disabled": { "value": "{color.primitive.neutral.300.value}", "comment": "Disabled text" },
|
||||
"muted": { "value": "rgba(255, 255, 255, 0.8)", "comment": "Muted white text" },
|
||||
"inverse": { "value": "{color.primitive.neutral.900.value}", "comment": "Inverse (dark on light)" }
|
||||
},
|
||||
"accent": {
|
||||
"primary": { "value": "{color.primitive.accent.500.value}", "comment": "Brand orange" },
|
||||
"light": { "value": "{color.primitive.accent.400.value}", "comment": "Light orange" },
|
||||
"glow": { "value": "rgba(255, 92, 0, 0.15)", "comment": "Subtle glow" },
|
||||
"glowStrong": { "value": "rgba(255, 92, 0, 0.3)", "comment": "Strong glow" }
|
||||
},
|
||||
"border": {
|
||||
"subtle": { "value": "{color.primitive.neutral.600.value}", "comment": "Subtle divider" },
|
||||
"default": { "value": "{color.primitive.neutral.500.value}", "comment": "Default border" },
|
||||
"strong": { "value": "{color.primitive.neutral.400.value}", "comment": "Strong border" }
|
||||
},
|
||||
"action": {
|
||||
"primaryBg": { "value": "{color.primitive.accent.500.value}", "comment": "CTA button bg" },
|
||||
"primaryBgHover": { "value": "{color.primitive.accent.600.value}", "comment": "CTA button hover" },
|
||||
"primaryText": { "value": "{color.primitive.white.value}", "comment": "CTA button text" },
|
||||
"secondaryBg": { "value": "transparent", "comment": "Ghost button bg" },
|
||||
"secondaryBgHover": { "value": "rgba(255, 255, 255, 0.05)", "comment": "Ghost button hover" },
|
||||
"secondaryText": { "value": "{color.primitive.white.value}", "comment": "Ghost button text" },
|
||||
"secondaryBorder": { "value": "{color.primitive.neutral.500.value}","comment": "Ghost button border" }
|
||||
},
|
||||
"status": {
|
||||
"success": { "value": "{color.primitive.success.500.value}", "comment": "Success state" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
packages/design-tokens/tokens/spacing.json
Normal file
16
packages/design-tokens/tokens/spacing.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"spacing": {
|
||||
"1": { "value": "0.25rem", "comment": "4px" },
|
||||
"2": { "value": "0.5rem", "comment": "8px" },
|
||||
"3": { "value": "0.75rem", "comment": "12px" },
|
||||
"4": { "value": "1rem", "comment": "16px" },
|
||||
"5": { "value": "1.25rem", "comment": "20px" },
|
||||
"6": { "value": "1.5rem", "comment": "24px" },
|
||||
"8": { "value": "2rem", "comment": "32px" },
|
||||
"10": { "value": "2.5rem", "comment": "40px" },
|
||||
"12": { "value": "3rem", "comment": "48px" },
|
||||
"16": { "value": "4rem", "comment": "64px" },
|
||||
"20": { "value": "5rem", "comment": "80px" },
|
||||
"24": { "value": "6rem", "comment": "96px" }
|
||||
}
|
||||
}
|
||||
8
packages/design-tokens/tokens/typography.json
Normal file
8
packages/design-tokens/tokens/typography.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"font": {
|
||||
"family": {
|
||||
"heading": { "value": "'Roboto', system-ui, sans-serif", "comment": "Heading / display font" },
|
||||
"body": { "value": "'Roboto', system-ui, sans-serif", "comment": "Body text font" }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user