@hotelfriendag/design-tokens 0.3.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.
- package/LICENSE +21 -0
- package/README.md +558 -0
- package/README.uk.md +377 -0
- package/RFC-0001-cross-project-design-system.md +273 -0
- package/RFC-0002-semantic-tier-naming.md +296 -0
- package/UI_DESIGN.md +608 -0
- package/ai-rules/CLAUDE.md +80 -0
- package/ai-rules/cursorrules.template +39 -0
- package/ai-rules/github-copilot-instructions.md +45 -0
- package/ai-rules/system-prompt-compact.md +43 -0
- package/components.html +3018 -0
- package/generate-tokens.cjs +665 -0
- package/package.json +98 -0
- package/portal-audit.html +2306 -0
- package/pre-built/_tokens.scss +138 -0
- package/pre-built/components.css +515 -0
- package/pre-built/shadcn-tokens.css +67 -0
- package/pre-built/status.css +51 -0
- package/pre-built/stylelint-design-system.cjs +69 -0
- package/pre-built/tailwind.additive.css +158 -0
- package/pre-built/tailwind.css +158 -0
- package/pre-built/tailwind.preset.js +207 -0
- package/pre-built/tokens.css +185 -0
- package/pre-built/tokens.d.ts +240 -0
- package/pre-built/tokens.js +243 -0
- package/pre-built/tokens.ts +240 -0
- package/scripts/integration-smoke.sh +91 -0
- package/scripts/pre-commit.sh +55 -0
- package/scripts/validate-tokens.cjs +113 -0
- package/states-canonical.json +240 -0
- package/states.json +2950 -0
- package/status-map.json +47 -0
- package/tokens.figma.json +230 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// HotelFriend design tokens — SCSS variables
|
|
2
|
+
// Generated from docs/tokens.figma.json — do NOT edit by hand.
|
|
3
|
+
|
|
4
|
+
$colorBlue50: #E9F6FC; // Light tint — active-state bg
|
|
5
|
+
$colorBlue100: #EFF6FF; // Faint tint — reset-filters bg
|
|
6
|
+
$colorBlue500: #24AFE8; // Canonical primary anchor (portal legacy SCSS still uses #26ADE4 — pending consolidation)
|
|
7
|
+
$colorBlue600: #149AD1; // Primary hover
|
|
8
|
+
$colorGray25: #FBFBFC; // Page background top
|
|
9
|
+
$colorGray50: #F6F7FB; // Section backgrounds
|
|
10
|
+
$colorGray100: #F1F3F6; // Inputs bg, hover-bg, header bars
|
|
11
|
+
$colorGray200: #E4E8EF; // Border-light, dividers
|
|
12
|
+
$colorGray300: #D1D6DD; // Default border
|
|
13
|
+
$colorGray400: #AEBCCF; // Placeholder + disabled text — portal $dark__Grey
|
|
14
|
+
$colorGray500: #99A1B7; // Placeholder legacy / soft helper
|
|
15
|
+
$colorGray550: #7E8EA6; // Helper text on light bg
|
|
16
|
+
$colorGray600: #50627E; // Cancel-button text, ink-muted
|
|
17
|
+
$colorGray700: #485B78; // Steel-blue (tab/nav inactive) — Phase 2 component-tier
|
|
18
|
+
$colorGray800: #4B5675; // Secondary text
|
|
19
|
+
$colorGray900: #2B2B2B; // Body text, headings
|
|
20
|
+
$colorGray950: #252F4A; // Deepest ink (pagination active text) — Phase 2 component-tier
|
|
21
|
+
$colorGreen500: #59B59D; // Success / mint
|
|
22
|
+
$colorAmber400: #FFBD5A; // Warning / buttermilk
|
|
23
|
+
$colorAmber600: #FFC900; // Strong yellow — frontdesk OOO
|
|
24
|
+
$colorRed400: #EA6565; // Error / rouge
|
|
25
|
+
$colorRed500: #FB5A56; // Cancel-red, deleted — between rouge and crimson
|
|
26
|
+
$colorRed700: #D9534F; // Deeper red — btn-danger
|
|
27
|
+
$colorOrange500: #F87921; // Coral / peach
|
|
28
|
+
$colorOrange700: #A55505; // Due-out brown
|
|
29
|
+
$colorViolet500: #5761D8; // Offer, inspected
|
|
30
|
+
$colorSlate500: #7F8FA4; // Canceled, deleted, OOS — cloud-gray
|
|
31
|
+
$colorOlive700: #5B7A02; // Check-in (rare dark olive)
|
|
32
|
+
$colorWhite: #FFFFFF;
|
|
33
|
+
$colorBlack: #000000;
|
|
34
|
+
$colorAccent: {primitive.color.blue.500}; // Brand primary — links, primary buttons, focus
|
|
35
|
+
$colorAccentHover: {primitive.color.blue.600}; // Primary button hover
|
|
36
|
+
$colorAccentSubtle: {primitive.color.blue.50}; // Accent tint — selected nav, active row
|
|
37
|
+
$colorAccentSubtler: {primitive.color.blue.100}; // Even softer accent — reset-filters bg
|
|
38
|
+
$colorOn-accent: {primitive.color.white}; // Text/icons ON accent bg (Radix-style foreground pair)
|
|
39
|
+
$colorBgPage: {primitive.color.gray.25}; // Page background (outermost)
|
|
40
|
+
$colorBgSection: {primitive.color.gray.50}; // Section bg, table header
|
|
41
|
+
$colorBgMuted: {primitive.color.gray.100}; // Input bg / hover bg / muted surface
|
|
42
|
+
$colorBgSurface: {primitive.color.white}; // Card / modal / popover surface
|
|
43
|
+
$colorFg: {primitive.color.gray.900}; // Default text / heading color
|
|
44
|
+
$colorFgMuted: {primitive.color.gray.800}; // Secondary text, descriptions
|
|
45
|
+
$colorFgSubtle: {primitive.color.gray.550}; // Helper / hint text
|
|
46
|
+
$colorFgFaint: {primitive.color.gray.400}; // Placeholder + disabled input text
|
|
47
|
+
$colorBorder: {primitive.color.gray.300}; // Default 1px border (buttons, inputs, chips)
|
|
48
|
+
$colorBorderSubtle: {primitive.color.gray.200}; // Faint dividers, light borders
|
|
49
|
+
$colorBorderStrong: {primitive.color.gray.400}; // Hover/focus border
|
|
50
|
+
$colorTabFg-inactive: {primitive.color.gray.700}; // Tab / nav-item text when NOT selected (.hf-tab, .hf-pill-tab default)
|
|
51
|
+
$colorTabCount-fg: {primitive.color.gray.700}; // Count-badge text inside an inactive tab
|
|
52
|
+
$colorPaginationFg-inactive: {primitive.color.gray.700}; // Pagination item text when not active
|
|
53
|
+
$colorPaginationFg-active: {primitive.color.gray.950}; // Pagination item text on the subtle-gray ACTIVE bg
|
|
54
|
+
$colorInputBorder: #DBDFE9; // Input + checkbox/radio border — between gray-200 (#E4E8EF) and gray-300 (#D1D6DD); portal-exact, doesn't fit a primitive step
|
|
55
|
+
$colorMenuBg-hover: #F5F5F5; // Dropdown item hover bg — slightly warmer than bg-muted; portal $table__hover
|
|
56
|
+
$colorStatusSuccess: {primitive.color.green.500}; // Success, due-in, clean
|
|
57
|
+
$colorStatusSuccess-bg: rgba(89,181,157,0.15); // Success @15% — pill bg
|
|
58
|
+
$colorStatusWarning: {primitive.color.amber.400}; // Warning, waiting, new
|
|
59
|
+
$colorStatusWarning-bg: rgba(255,189,90,0.15);
|
|
60
|
+
$colorStatusWarning-strong: {primitive.color.amber.600}; // Frontdesk out-of-order — more saturated yellow
|
|
61
|
+
$colorStatusWarning-strong-bg: rgba(255,201,0,0.15);
|
|
62
|
+
$colorStatusError: {primitive.color.red.400}; // Error, no-show, dirty, action-required
|
|
63
|
+
$colorStatusError-bg: rgba(234,101,101,0.15);
|
|
64
|
+
$colorStatusError-strong: {primitive.color.red.700}; // btn-danger
|
|
65
|
+
$colorStatusError-alt: {primitive.color.red.500}; // btn-cancel-red, deleted variant
|
|
66
|
+
$colorStatusInfo: {semantic.color.accent.default}; // Info = accent
|
|
67
|
+
$colorStatusInfo-bg: rgba(36,175,232,0.15);
|
|
68
|
+
$colorStatusCancel: {primitive.color.slate.500}; // Cancellations, deleted, out-of-service
|
|
69
|
+
$colorStatusCancel-bg: rgba(127,143,164,0.15);
|
|
70
|
+
$colorStatusCoral: {primitive.color.orange.500}; // Check-out, in-progress
|
|
71
|
+
$colorStatusCoral-bg: rgba(248,121,33,0.15);
|
|
72
|
+
$colorStatusViolet: {primitive.color.violet.500}; // Offer, inspected
|
|
73
|
+
$colorStatusViolet-bg: rgba(87,97,216,0.15);
|
|
74
|
+
$colorStatusOlive: {primitive.color.olive.700}; // Check-in (rare deep olive)
|
|
75
|
+
$colorStatusOlive-bg: rgba(91,122,2,0.15);
|
|
76
|
+
$colorStatusOrange-deep: {primitive.color.orange.700}; // Due-out brown
|
|
77
|
+
$colorStatusOrange-deep-bg: rgba(165,85,5,0.15);
|
|
78
|
+
$fontFamilyPrimary: Roboto;
|
|
79
|
+
$fontWeightRegular: 400;
|
|
80
|
+
$fontWeightMedium: 500;
|
|
81
|
+
$fontWeightSemibold: 600;
|
|
82
|
+
$fontSizeXs: 11px; // Tag labels, uppercase captions
|
|
83
|
+
$fontSizeSm: 13px; // Grid headers, chips, sm buttons
|
|
84
|
+
$fontSizeBase: 14px; // Body, labels (most common — 218 occurrences)
|
|
85
|
+
$fontSizeMd: 15px; // Primary buttons
|
|
86
|
+
$fontSizeLg: 16px; // Modal titles, large tabs
|
|
87
|
+
$fontSizeXl: 18px; // Info-block titles, modal title
|
|
88
|
+
$fontSize2xl: 20px; // Section heading
|
|
89
|
+
$fontSizeH2: 24px;
|
|
90
|
+
$fontSizePage: 26px; // Page title
|
|
91
|
+
$fontSizeHero: 30px; // Dashboard stat numbers
|
|
92
|
+
$lineHeightHeadings: 1.2;
|
|
93
|
+
$lineHeightBody: 1.5;
|
|
94
|
+
$lineHeightTight: 1;
|
|
95
|
+
$spacing0: 0px;
|
|
96
|
+
$spacing1: 4px;
|
|
97
|
+
$spacing2: 8px;
|
|
98
|
+
$spacing3: 12px;
|
|
99
|
+
$spacing4: 16px;
|
|
100
|
+
$spacing5: 20px; // Default section gap
|
|
101
|
+
$spacing6: 24px;
|
|
102
|
+
$spacing7: 30px;
|
|
103
|
+
$spacing8: 32px;
|
|
104
|
+
$radiusSm: 6px; // Buttons, inputs, status pills, modals (portal-exact)
|
|
105
|
+
$radiusMd: 8px; // Pagination items
|
|
106
|
+
$radiusLg: 9px; // Dropdowns, toasts, pill-tabs
|
|
107
|
+
$radiusXl: 12px; // Large elevated cards
|
|
108
|
+
$radiusPill: 99px; // Rounded pill (rare — most status pills are 6px square)
|
|
109
|
+
$opacityDisabled: 0.6; // Disabled state
|
|
110
|
+
$opacityTint: 0.15; // Status badge bg alpha
|
|
111
|
+
$sizeSidebarExpanded: 215px;
|
|
112
|
+
$sizeSidebarCollapsed: 80px;
|
|
113
|
+
$sizeHeaderHeight: 60px;
|
|
114
|
+
$sizeContentMax: 1440px;
|
|
115
|
+
$sizeBtnDefault: 40px; // Primary button height
|
|
116
|
+
$sizeBtnSm: 32px;
|
|
117
|
+
$sizeBtnSearch: 38px; // Inline filter/search submit height
|
|
118
|
+
$sizeInputDefault: 39px; // Form-control input height
|
|
119
|
+
$sizeTapTarget: 44px; // Mobile minimum tap target
|
|
120
|
+
$zIndexSidebar: 1000;
|
|
121
|
+
$zIndexDropdown: 1001;
|
|
122
|
+
$zIndexDatepicker: 3000;
|
|
123
|
+
$zIndexStickyTopbar: 4000;
|
|
124
|
+
$zIndexModalBackdrop: 10050;
|
|
125
|
+
$zIndexModalDialog: 10051;
|
|
126
|
+
$zIndexPreloader: 99999;
|
|
127
|
+
$zIndexTopmostOverlay: 1000001; // Above all
|
|
128
|
+
$motionFast: 0.1s;
|
|
129
|
+
$motion: 0.2s ease-in-out; // Canonical transition
|
|
130
|
+
$motionSlow: 0.3s; // Most common in legacy CSS — being normalized down to 0.2s
|
|
131
|
+
$motionBouncy: 0.4s cubic-bezier(0.88, -0.99, 0, 1.81);
|
|
132
|
+
$shadow: 0px 1px 8px 0px rgba(0,0,0,0.1);
|
|
133
|
+
$shadowSubtle: 0px 2px 4px 0px rgba(0,0,0,0.05);
|
|
134
|
+
$shadowWrapper: 0px 3px 4px 0px rgba(0,0,0,0.03);
|
|
135
|
+
$shadowCard: 0px 12px 24px 0px rgba(26,26,30,0.06);
|
|
136
|
+
$shadowModal: 0px 6px 18px 0px rgba(0,0,0,0.1);
|
|
137
|
+
$shadowOutline: 0px 1px 2px 0px rgba(72,91,120,0.18);
|
|
138
|
+
$shadowHover: 0px 2px 3px 0px rgba(0,0,0,0.06);
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* HotelFriend Design System — Component classes (.hf-*)
|
|
3
|
+
*
|
|
4
|
+
* Drop-in companion to `tokens.css` / `tailwind.css`. Defines the `.hf-*`
|
|
5
|
+
* component primitives used throughout `components.html`. Framework-agnostic
|
|
6
|
+
* — works with Tailwind v4, plain CSS, SCSS, Vue, React.
|
|
7
|
+
*
|
|
8
|
+
* Phase 1B (RFC-0002) status:
|
|
9
|
+
* ✅ All chrome bound to SEMANTIC tokens (var(--color-hf-fg), -bg-*, -border, -accent, -status-*).
|
|
10
|
+
* ✅ Primitives never referenced directly (except Phase-2 escape hatches — see TODOs below).
|
|
11
|
+
* ✅ One namespace — no forked --status-* declarations; all status colors come from
|
|
12
|
+
* `--color-hf-status-{role}` (semantic) which alias `--color-hf-{family}-{step}` (primitive).
|
|
13
|
+
* ✅ Class names equal token names — domain→semantic mapping NOW LIVES in `status-map.json`
|
|
14
|
+
* and is emitted to `pre-built/status.css` by `generate-tokens.cjs --target=status-css`.
|
|
15
|
+
* This file no longer contains the mapping — consumers should `@import` both files.
|
|
16
|
+
* ⚠ `pre-built/components.css` is still hand-mirrored from components.html (Phase 2D will generate).
|
|
17
|
+
*
|
|
18
|
+
* Phase 2 TODOs (tracked here):
|
|
19
|
+
* 1. ✅ Phase 2A done — status-map.json + emitStatusCss(). See `pre-built/status.css`.
|
|
20
|
+
* 2. ✅ Phase 2B done — 4 component-tier tokens introduced:
|
|
21
|
+
* --color-hf-tab-fg-inactive / -count-fg
|
|
22
|
+
* --color-hf-pagination-fg-inactive / -fg-active
|
|
23
|
+
* --color-hf-input-border (#DBDFE9 — was bare hex)
|
|
24
|
+
* --color-hf-menu-bg-hover (#F5F5F5 — was bare hex)
|
|
25
|
+
* 3. Phase 2C — generate this whole file from tokens.figma.json + status-map.json (TODO).
|
|
26
|
+
* 4. Phase 2D — reconcile components.html @theme block with Phase 1B token names (TODO).
|
|
27
|
+
*
|
|
28
|
+
* BREAKING CHANGES vs Phase 1A:
|
|
29
|
+
* - `--color-hf-primary*` → `--color-hf-accent[ -hover / -subtle / -subtler ]`
|
|
30
|
+
* - `--color-hf-text-*` → `--color-hf-fg[ -muted / -subtle / -faint ]`
|
|
31
|
+
* - `--color-hf-neutral-*` → `--color-hf-bg-*` / `--color-hf-border[ -subtle ]`
|
|
32
|
+
* - `--color-hf-badge-*` → removed; replaced by domain→semantic CSS map below
|
|
33
|
+
*
|
|
34
|
+
* Usage:
|
|
35
|
+
* <link rel="stylesheet" href="tokens.css"> ← variables first
|
|
36
|
+
* <link rel="stylesheet" href="components.css"> ← components after
|
|
37
|
+
* <link rel="stylesheet" href="status.css"> ← .status-{domain}-{state} rules (Phase 2A — generated from status-map.json)
|
|
38
|
+
*
|
|
39
|
+
* Or with Tailwind v4:
|
|
40
|
+
* @import "tailwindcss";
|
|
41
|
+
* @import "./tailwind.css";
|
|
42
|
+
* @import "./components.css";
|
|
43
|
+
* @import "./status.css";
|
|
44
|
+
*
|
|
45
|
+
* STATUS PILLS — the domain→semantic mapping lives in `status-map.json` and is
|
|
46
|
+
* emitted to `pre-built/status.css` by `generate-tokens.cjs --target=status-css`.
|
|
47
|
+
* To add a new status (e.g. `booking.partial-refund`), add it to status-map.json
|
|
48
|
+
* and re-run the generator — no CSS edit needed here.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/* ════════════════════════════════════════════════════════════════════════════
|
|
52
|
+
* PILL — .hf-pill (uses currentColor inherited from .status-* above)
|
|
53
|
+
* ════════════════════════════════════════════════════════════════════════════ */
|
|
54
|
+
.hf-pill {
|
|
55
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
56
|
+
padding: 6px 20px;
|
|
57
|
+
border-radius: var(--radius-hf-sm, 6px);
|
|
58
|
+
font-size: var(--font-size-hf-base, 14px);
|
|
59
|
+
font-weight: var(--font-weight-hf-medium, 500);
|
|
60
|
+
line-height: var(--line-height-hf-tight, 1);
|
|
61
|
+
white-space: nowrap;
|
|
62
|
+
border: 1px solid currentColor;
|
|
63
|
+
text-align: center;
|
|
64
|
+
user-select: none;
|
|
65
|
+
transition: opacity 200ms, background-color 200ms;
|
|
66
|
+
}
|
|
67
|
+
.hf-pill:hover { opacity: .9; }
|
|
68
|
+
.hf-pill.is-active {
|
|
69
|
+
background: transparent !important;
|
|
70
|
+
box-shadow: inset 0 0 0 1px currentColor;
|
|
71
|
+
}
|
|
72
|
+
.hf-pill--striped {
|
|
73
|
+
background-image: repeating-linear-gradient(
|
|
74
|
+
-45deg, currentColor 0, currentColor 1px, transparent 1px, transparent 6px
|
|
75
|
+
);
|
|
76
|
+
background-color: var(--color-hf-white, #fff) !important;
|
|
77
|
+
}
|
|
78
|
+
.hf-pill--dd {
|
|
79
|
+
padding-right: 32px;
|
|
80
|
+
cursor: pointer;
|
|
81
|
+
position: relative;
|
|
82
|
+
}
|
|
83
|
+
.hf-pill--dd::after {
|
|
84
|
+
content: '';
|
|
85
|
+
position: absolute; right: 12px; top: 50%;
|
|
86
|
+
width: 8px; height: 5px;
|
|
87
|
+
background-color: currentColor;
|
|
88
|
+
clip-path: polygon(0 0, 100% 0, 50% 100%);
|
|
89
|
+
transform: translateY(-50%);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ════════════════════════════════════════════════════════════════════════════
|
|
93
|
+
* TABS (.hf-tab) — large underline (page-nav) + small (sub-filter)
|
|
94
|
+
*
|
|
95
|
+
* ⚠ Phase 2 TODO: gray-700 used here for inactive tab color is portal-specific.
|
|
96
|
+
* Promote to component-tier token `--hf-tab-fg-inactive`.
|
|
97
|
+
* ════════════════════════════════════════════════════════════════════════════ */
|
|
98
|
+
.hf-tab {
|
|
99
|
+
padding: 22px 15px;
|
|
100
|
+
font-size: var(--font-size-hf-lg, 16px);
|
|
101
|
+
font-weight: var(--font-weight-hf-medium, 500);
|
|
102
|
+
color: var(--color-hf-tab-fg-inactive, #50627E);
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
transition: color 200ms;
|
|
105
|
+
position: relative;
|
|
106
|
+
white-space: nowrap;
|
|
107
|
+
border: 0; background: 0;
|
|
108
|
+
line-height: var(--line-height-hf-tight, 1);
|
|
109
|
+
display: inline-flex; align-items: center; gap: 8px;
|
|
110
|
+
}
|
|
111
|
+
.hf-tab:hover { color: var(--color-hf-fg, #2B2B2B); }
|
|
112
|
+
.hf-tab.is-active { color: var(--color-hf-accent, #24AFE8); }
|
|
113
|
+
.hf-tab.is-active::after {
|
|
114
|
+
content: ''; position: absolute; left: 0; right: 0; bottom: -1px;
|
|
115
|
+
height: 3px; background: var(--color-hf-accent, #24AFE8);
|
|
116
|
+
}
|
|
117
|
+
.hf-tab:disabled { color: var(--color-hf-fg-faint, #AEBCCF); cursor: not-allowed; }
|
|
118
|
+
.hf-tab:disabled:hover { color: var(--color-hf-fg-faint, #AEBCCF); }
|
|
119
|
+
|
|
120
|
+
.hf-tab--sm {
|
|
121
|
+
padding: 10px 15px;
|
|
122
|
+
font-size: var(--font-size-hf-md, 15px);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.hf-tab__count {
|
|
126
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
127
|
+
min-width: 22px; height: 18px; padding: 0 6px;
|
|
128
|
+
background: rgba(72,91,120,.1);
|
|
129
|
+
color: var(--color-hf-tab-count-fg, #50627E);
|
|
130
|
+
border-radius: 9999px;
|
|
131
|
+
font-size: var(--font-size-hf-xs, 11px);
|
|
132
|
+
font-weight: var(--font-weight-hf-semibold, 600);
|
|
133
|
+
line-height: var(--line-height-hf-tight, 1);
|
|
134
|
+
}
|
|
135
|
+
.hf-tab.is-active .hf-tab__count {
|
|
136
|
+
background: var(--color-hf-accent-subtler, #EFF6FF);
|
|
137
|
+
color: var(--color-hf-accent, #24AFE8);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.hf-pill-tabs {
|
|
141
|
+
display: inline-flex; gap: 4px; padding: 4px;
|
|
142
|
+
background: var(--color-hf-bg-section, #F6F7FB);
|
|
143
|
+
border-radius: var(--radius-hf-lg, 9px);
|
|
144
|
+
}
|
|
145
|
+
.hf-pill-tab {
|
|
146
|
+
padding: 8px 14px;
|
|
147
|
+
font-size: var(--font-size-hf-base, 14px);
|
|
148
|
+
font-weight: var(--font-weight-hf-medium, 500);
|
|
149
|
+
color: var(--color-hf-tab-fg-inactive, #50627E);
|
|
150
|
+
background: transparent;
|
|
151
|
+
border: 0;
|
|
152
|
+
border-radius: var(--radius-hf-sm, 6px);
|
|
153
|
+
cursor: pointer;
|
|
154
|
+
transition: background 200ms, color 200ms;
|
|
155
|
+
white-space: nowrap;
|
|
156
|
+
}
|
|
157
|
+
.hf-pill-tab:hover { color: var(--color-hf-fg, #2B2B2B); }
|
|
158
|
+
.hf-pill-tab.is-active {
|
|
159
|
+
background: var(--color-hf-bg-surface, #fff);
|
|
160
|
+
color: var(--color-hf-accent, #24AFE8);
|
|
161
|
+
box-shadow: 0 1px 2px rgba(0,0,0,.05);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* ════════════════════════════════════════════════════════════════════════════
|
|
165
|
+
* PAGINATION (.hf-pagination) — subtle gray active (NOT accent!)
|
|
166
|
+
*
|
|
167
|
+
* ⚠ Phase 2 TODO: gray-950 (#252F4A) used for active text is portal-specific.
|
|
168
|
+
* Promote to component-tier `--hf-pagination-fg-active`.
|
|
169
|
+
* ════════════════════════════════════════════════════════════════════════════ */
|
|
170
|
+
.hf-pagination {
|
|
171
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
172
|
+
padding: 0; list-style: none;
|
|
173
|
+
}
|
|
174
|
+
.hf-pagination__item {
|
|
175
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
176
|
+
min-width: 34px; height: 34px;
|
|
177
|
+
padding: 0 12px;
|
|
178
|
+
font-size: var(--font-size-hf-base, 14px);
|
|
179
|
+
font-weight: var(--font-weight-hf-regular, 400);
|
|
180
|
+
color: var(--color-hf-pagination-fg-inactive, #485B78);
|
|
181
|
+
background: transparent;
|
|
182
|
+
border: 1px solid transparent;
|
|
183
|
+
border-radius: var(--radius-hf-md, 8px);
|
|
184
|
+
cursor: pointer;
|
|
185
|
+
transition: background-color 200ms, color 200ms, border-color 200ms;
|
|
186
|
+
text-decoration: none;
|
|
187
|
+
}
|
|
188
|
+
.hf-pagination__item:hover {
|
|
189
|
+
background: var(--color-hf-bg-muted, #F1F3F6);
|
|
190
|
+
color: var(--color-hf-fg, #2B2B2B);
|
|
191
|
+
}
|
|
192
|
+
.hf-pagination__item.is-active {
|
|
193
|
+
background: var(--color-hf-border-subtle, #E4E8EF);
|
|
194
|
+
border-color: var(--color-hf-border-subtle, #E4E8EF);
|
|
195
|
+
color: var(--color-hf-pagination-fg-active, #252F4A);
|
|
196
|
+
cursor: default;
|
|
197
|
+
}
|
|
198
|
+
.hf-pagination__item.is-active:hover { background: var(--color-hf-border-subtle, #E4E8EF); }
|
|
199
|
+
.hf-pagination__item:disabled,
|
|
200
|
+
.hf-pagination__item.is-disabled {
|
|
201
|
+
color: var(--color-hf-fg-faint, #AEBCCF);
|
|
202
|
+
cursor: not-allowed;
|
|
203
|
+
pointer-events: none;
|
|
204
|
+
}
|
|
205
|
+
.hf-pagination__ellipsis {
|
|
206
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
207
|
+
min-width: 34px; height: 34px;
|
|
208
|
+
color: var(--color-hf-gray-700, #485B78);
|
|
209
|
+
cursor: default;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* ════════════════════════════════════════════════════════════════════════════
|
|
213
|
+
* ALERT (.hf-alert)
|
|
214
|
+
* ════════════════════════════════════════════════════════════════════════════ */
|
|
215
|
+
.hf-alert {
|
|
216
|
+
position: relative;
|
|
217
|
+
display: flex; align-items: flex-start; gap: 16px;
|
|
218
|
+
padding: 16px 48px 14px 20px;
|
|
219
|
+
background: var(--color-hf-bg-surface, #fff);
|
|
220
|
+
border: 1px solid rgba(72,91,120,.15);
|
|
221
|
+
border-radius: var(--radius-hf-sm, 6px);
|
|
222
|
+
box-shadow: 0 2px 4px 0 rgba(72,91,120,.07);
|
|
223
|
+
overflow: hidden;
|
|
224
|
+
}
|
|
225
|
+
.hf-alert::before {
|
|
226
|
+
content: '';
|
|
227
|
+
position: absolute;
|
|
228
|
+
left: 0; right: 0; top: 0;
|
|
229
|
+
height: 3px;
|
|
230
|
+
background: currentColor;
|
|
231
|
+
}
|
|
232
|
+
.hf-alert__icon {
|
|
233
|
+
flex-shrink: 0;
|
|
234
|
+
width: 26px; height: 26px;
|
|
235
|
+
border-radius: 5px;
|
|
236
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
237
|
+
background: currentColor;
|
|
238
|
+
}
|
|
239
|
+
.hf-alert__icon > svg {
|
|
240
|
+
width: 16px; height: 16px;
|
|
241
|
+
color: var(--color-hf-on-accent, #fff);
|
|
242
|
+
stroke: var(--color-hf-on-accent, #fff);
|
|
243
|
+
fill: none;
|
|
244
|
+
}
|
|
245
|
+
.hf-alert__body { flex: 1; min-width: 0; color: var(--color-hf-fg, #2B2B2B); }
|
|
246
|
+
.hf-alert__title {
|
|
247
|
+
font-size: var(--font-size-hf-xl, 18px);
|
|
248
|
+
font-weight: var(--font-weight-hf-medium, 500);
|
|
249
|
+
line-height: 1.3;
|
|
250
|
+
margin: 0 0 4px;
|
|
251
|
+
color: var(--color-hf-fg, #2B2B2B);
|
|
252
|
+
}
|
|
253
|
+
.hf-alert__text {
|
|
254
|
+
font-size: var(--font-size-hf-md, 15px);
|
|
255
|
+
line-height: 1.4;
|
|
256
|
+
color: var(--color-hf-gray-700, #50627E); /* TODO Phase 2 — review fg axis */
|
|
257
|
+
margin: 0;
|
|
258
|
+
}
|
|
259
|
+
.hf-alert__close {
|
|
260
|
+
position: absolute; top: 12px; right: 12px;
|
|
261
|
+
width: 24px; height: 24px;
|
|
262
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
263
|
+
background: transparent; border: 0; border-radius: 4px;
|
|
264
|
+
color: var(--color-hf-gray-500, #99A1B7);
|
|
265
|
+
cursor: pointer;
|
|
266
|
+
transition: color 200ms, background 200ms;
|
|
267
|
+
}
|
|
268
|
+
.hf-alert__close:hover {
|
|
269
|
+
color: var(--color-hf-fg, #2B2B2B);
|
|
270
|
+
background: var(--color-hf-bg-muted, #F1F3F6);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* Variants — set accent color via currentColor */
|
|
274
|
+
.hf-alert--success { color: var(--color-hf-status-success); }
|
|
275
|
+
.hf-alert--info { color: var(--color-hf-accent); }
|
|
276
|
+
.hf-alert--warn { color: var(--color-hf-status-warning); }
|
|
277
|
+
.hf-alert--error { color: var(--color-hf-status-error); }
|
|
278
|
+
|
|
279
|
+
/* Tinted modifier */
|
|
280
|
+
.hf-alert--tinted.hf-alert--success { background: rgba(89,181,157,.06); }
|
|
281
|
+
.hf-alert--tinted.hf-alert--info { background: rgba(36,175,232,.06); }
|
|
282
|
+
.hf-alert--tinted.hf-alert--warn { background: rgba(255,189,90,.10); }
|
|
283
|
+
.hf-alert--tinted.hf-alert--error { background: rgba(234,101,101,.06); }
|
|
284
|
+
|
|
285
|
+
/* Page banner */
|
|
286
|
+
.hf-alert--banner {
|
|
287
|
+
border-radius: 0;
|
|
288
|
+
border-left: 0; border-right: 0;
|
|
289
|
+
box-shadow: none;
|
|
290
|
+
background: currentColor;
|
|
291
|
+
}
|
|
292
|
+
.hf-alert--banner::before { display: none; }
|
|
293
|
+
.hf-alert--banner .hf-alert__body { color: var(--color-hf-on-accent, #fff); }
|
|
294
|
+
.hf-alert--banner .hf-alert__title,
|
|
295
|
+
.hf-alert--banner .hf-alert__text { color: var(--color-hf-on-accent, #fff); }
|
|
296
|
+
.hf-alert--banner .hf-alert__icon { background: rgba(255,255,255,.18); }
|
|
297
|
+
.hf-alert--banner .hf-alert__close { color: rgba(255,255,255,.7); }
|
|
298
|
+
.hf-alert--banner .hf-alert__close:hover { color: var(--color-hf-on-accent, #fff); background: rgba(255,255,255,.12); }
|
|
299
|
+
|
|
300
|
+
/* Compact */
|
|
301
|
+
.hf-alert--compact { padding: 10px 36px 10px 14px; gap: 10px; }
|
|
302
|
+
.hf-alert--compact .hf-alert__icon { width: 20px; height: 20px; border-radius: 4px; }
|
|
303
|
+
.hf-alert--compact .hf-alert__icon > svg { width: 12px; height: 12px; }
|
|
304
|
+
.hf-alert--compact .hf-alert__title {
|
|
305
|
+
font-size: var(--font-size-hf-base, 14px);
|
|
306
|
+
font-weight: var(--font-weight-hf-semibold, 600);
|
|
307
|
+
margin-bottom: 0;
|
|
308
|
+
}
|
|
309
|
+
.hf-alert--compact .hf-alert__text { font-size: var(--font-size-hf-sm, 13px); }
|
|
310
|
+
.hf-alert--compact .hf-alert__close { top: 8px; right: 8px; }
|
|
311
|
+
|
|
312
|
+
/* ════════════════════════════════════════════════════════════════════════════
|
|
313
|
+
* TOAST (.hf-toast)
|
|
314
|
+
* ════════════════════════════════════════════════════════════════════════════ */
|
|
315
|
+
.hf-toast {
|
|
316
|
+
display: inline-flex; align-items: center; gap: 10px;
|
|
317
|
+
padding: 10px 14px;
|
|
318
|
+
background: var(--color-hf-bg-surface, #fff);
|
|
319
|
+
border: 1px solid rgba(72,91,120,.15);
|
|
320
|
+
border-radius: var(--radius-hf-lg, 9px);
|
|
321
|
+
box-shadow: var(--shadow-hf-modal, 0 6px 18px rgba(0,0,0,.10));
|
|
322
|
+
min-width: 280px; max-width: 420px;
|
|
323
|
+
color: var(--color-hf-fg, #2B2B2B);
|
|
324
|
+
font-size: var(--font-size-hf-base, 14px);
|
|
325
|
+
}
|
|
326
|
+
.hf-toast__icon { flex-shrink: 0; width: 20px; height: 20px; }
|
|
327
|
+
.hf-toast__text { flex: 1; }
|
|
328
|
+
.hf-toast__close {
|
|
329
|
+
flex-shrink: 0;
|
|
330
|
+
width: 20px; height: 20px;
|
|
331
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
332
|
+
background: transparent; border: 0; border-radius: 4px;
|
|
333
|
+
color: var(--color-hf-gray-500, #99A1B7);
|
|
334
|
+
cursor: pointer; transition: color 200ms;
|
|
335
|
+
}
|
|
336
|
+
.hf-toast__close:hover { color: var(--color-hf-fg, #2B2B2B); }
|
|
337
|
+
|
|
338
|
+
/* ════════════════════════════════════════════════════════════════════════════
|
|
339
|
+
* MODAL (.hf-modal)
|
|
340
|
+
* ════════════════════════════════════════════════════════════════════════════ */
|
|
341
|
+
.hf-modal {
|
|
342
|
+
background: var(--color-hf-bg-surface, #fff);
|
|
343
|
+
border: 1px solid rgba(72,91,120,.15);
|
|
344
|
+
border-radius: var(--radius-hf-sm, 6px);
|
|
345
|
+
box-shadow: 0 2px 4px 0 rgba(72,91,120,.18);
|
|
346
|
+
overflow: hidden;
|
|
347
|
+
width: 100%;
|
|
348
|
+
}
|
|
349
|
+
.hf-modal__header {
|
|
350
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
351
|
+
padding: 20px;
|
|
352
|
+
border-bottom: 1px solid var(--color-hf-border-subtle, #E4E8EF);
|
|
353
|
+
}
|
|
354
|
+
.hf-modal__title {
|
|
355
|
+
font-size: var(--font-size-hf-xl, 18px);
|
|
356
|
+
font-weight: var(--font-weight-hf-semibold, 600);
|
|
357
|
+
color: var(--color-hf-fg, #2B2B2B);
|
|
358
|
+
margin: 0;
|
|
359
|
+
}
|
|
360
|
+
.hf-modal__close {
|
|
361
|
+
width: 32px; height: 32px;
|
|
362
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
363
|
+
background: transparent;
|
|
364
|
+
border: 0;
|
|
365
|
+
border-radius: var(--radius-hf-sm, 6px);
|
|
366
|
+
color: var(--color-hf-gray-700, #50627E);
|
|
367
|
+
cursor: pointer;
|
|
368
|
+
transition: background 200ms, color 200ms;
|
|
369
|
+
}
|
|
370
|
+
.hf-modal__close:hover {
|
|
371
|
+
background: var(--color-hf-bg-muted, #F1F3F6);
|
|
372
|
+
color: var(--color-hf-fg, #2B2B2B);
|
|
373
|
+
}
|
|
374
|
+
.hf-modal__body { padding: 20px; }
|
|
375
|
+
.hf-modal__footer {
|
|
376
|
+
display: flex; justify-content: flex-end; gap: 12px;
|
|
377
|
+
padding: 20px;
|
|
378
|
+
border-top: 0;
|
|
379
|
+
background: transparent;
|
|
380
|
+
}
|
|
381
|
+
.hf-modal--with-footer-border .hf-modal__footer {
|
|
382
|
+
border-top: 1px solid var(--color-hf-border-subtle, #E4E8EF);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/* ════════════════════════════════════════════════════════════════════════════
|
|
386
|
+
* CHECKBOX & RADIO (.hf-check, .hf-radio)
|
|
387
|
+
* ════════════════════════════════════════════════════════════════════════════ */
|
|
388
|
+
.hf-check,
|
|
389
|
+
.hf-radio {
|
|
390
|
+
appearance: none; -webkit-appearance: none;
|
|
391
|
+
position: relative; flex-shrink: 0;
|
|
392
|
+
width: 18px; height: 18px;
|
|
393
|
+
margin: 0;
|
|
394
|
+
background: transparent;
|
|
395
|
+
border: 1px solid var(--color-hf-input-border, #DBDFE9);
|
|
396
|
+
cursor: pointer;
|
|
397
|
+
transition: border-color 200ms, background-color 200ms;
|
|
398
|
+
}
|
|
399
|
+
.hf-check { border-radius: var(--radius-hf-sm, 6px); }
|
|
400
|
+
.hf-radio { border-radius: 9999px; }
|
|
401
|
+
.hf-check:hover:not(:disabled),
|
|
402
|
+
.hf-radio:hover:not(:disabled) { border-color: var(--color-hf-accent, #26ADE4); }
|
|
403
|
+
.hf-check:checked,
|
|
404
|
+
.hf-radio:checked {
|
|
405
|
+
background: var(--color-hf-accent, #26ADE4);
|
|
406
|
+
border-color: var(--color-hf-accent, #26ADE4);
|
|
407
|
+
}
|
|
408
|
+
.hf-check:checked::after {
|
|
409
|
+
content: '';
|
|
410
|
+
position: absolute; top: 2px; left: 5px;
|
|
411
|
+
width: 5px; height: 10px;
|
|
412
|
+
border: solid var(--color-hf-on-accent, #fff);
|
|
413
|
+
border-width: 0 2px 2px 0;
|
|
414
|
+
transform: rotate(45deg);
|
|
415
|
+
}
|
|
416
|
+
.hf-radio:checked::after {
|
|
417
|
+
content: '';
|
|
418
|
+
position: absolute; top: 50%; left: 50%;
|
|
419
|
+
width: 6px; height: 6px;
|
|
420
|
+
background: var(--color-hf-on-accent, #fff); border-radius: 9999px;
|
|
421
|
+
transform: translate(-50%, -50%);
|
|
422
|
+
}
|
|
423
|
+
.hf-check:disabled,
|
|
424
|
+
.hf-radio:disabled { cursor: not-allowed; opacity: .5; }
|
|
425
|
+
.hf-check:checked:disabled,
|
|
426
|
+
.hf-radio:checked:disabled {
|
|
427
|
+
background: rgba(38,173,228,.5);
|
|
428
|
+
border-color: rgba(38,173,228,.5);
|
|
429
|
+
opacity: 1;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/* ════════════════════════════════════════════════════════════════════════════
|
|
433
|
+
* DROPDOWN (.hf-dropdown-menu)
|
|
434
|
+
* ════════════════════════════════════════════════════════════════════════════ */
|
|
435
|
+
.hf-dropdown-menu {
|
|
436
|
+
background: var(--color-hf-bg-surface, #fff);
|
|
437
|
+
border-radius: var(--radius-hf-lg, 9px);
|
|
438
|
+
box-shadow: 0 1px 10px 0 rgba(0,0,0,.1);
|
|
439
|
+
min-width: 180px;
|
|
440
|
+
padding: 6px 0;
|
|
441
|
+
border: 1px solid rgba(228,232,239,.6);
|
|
442
|
+
}
|
|
443
|
+
.hf-dropdown-header {
|
|
444
|
+
padding: 10px 16px 8px;
|
|
445
|
+
font-size: var(--font-size-hf-xs, 11px);
|
|
446
|
+
font-weight: var(--font-weight-hf-semibold, 600);
|
|
447
|
+
color: var(--color-hf-gray-500, #99A1B7);
|
|
448
|
+
text-transform: uppercase;
|
|
449
|
+
letter-spacing: .04em;
|
|
450
|
+
}
|
|
451
|
+
.hf-dropdown-item {
|
|
452
|
+
display: flex; align-items: center; gap: 10px;
|
|
453
|
+
padding: 8px 16px;
|
|
454
|
+
font-size: var(--font-size-hf-base, 14px);
|
|
455
|
+
color: var(--color-hf-fg, #2B2B2B);
|
|
456
|
+
cursor: pointer; transition: background 150ms;
|
|
457
|
+
line-height: 1.4;
|
|
458
|
+
}
|
|
459
|
+
.hf-dropdown-item:hover { background: var(--color-hf-menu-bg-hover, #F5F5F5); }
|
|
460
|
+
.hf-dropdown-item.is-active {
|
|
461
|
+
background: var(--color-hf-accent-subtler, #EFF6FF);
|
|
462
|
+
color: var(--color-hf-accent, #24AFE8);
|
|
463
|
+
}
|
|
464
|
+
.hf-dropdown-item:focus-visible {
|
|
465
|
+
background: var(--color-hf-menu-bg-hover, #F5F5F5);
|
|
466
|
+
outline: 2px solid var(--color-hf-accent, #24AFE8);
|
|
467
|
+
outline-offset: -2px;
|
|
468
|
+
}
|
|
469
|
+
.hf-dropdown-item.danger { color: var(--color-hf-status-error, #EA6565); }
|
|
470
|
+
.hf-dropdown-item.danger:hover { background: rgba(234,101,101,.08); }
|
|
471
|
+
.hf-dropdown-item[aria-disabled="true"],
|
|
472
|
+
.hf-dropdown-item.is-disabled {
|
|
473
|
+
color: var(--color-hf-fg-faint, #AEBCCF);
|
|
474
|
+
cursor: not-allowed;
|
|
475
|
+
pointer-events: none;
|
|
476
|
+
}
|
|
477
|
+
.hf-dropdown-item__shortcut {
|
|
478
|
+
margin-left: auto;
|
|
479
|
+
color: var(--color-hf-gray-500, #99A1B7);
|
|
480
|
+
font-size: 12px;
|
|
481
|
+
}
|
|
482
|
+
.hf-dropdown-item__icon {
|
|
483
|
+
color: var(--color-hf-gray-500, #99A1B7);
|
|
484
|
+
width: 16px; height: 16px;
|
|
485
|
+
flex-shrink: 0;
|
|
486
|
+
}
|
|
487
|
+
.hf-dropdown-item:hover .hf-dropdown-item__icon { color: var(--color-hf-gray-700, #50627E); }
|
|
488
|
+
.hf-dropdown-item.danger .hf-dropdown-item__icon { color: currentColor; }
|
|
489
|
+
.hf-dropdown-divider {
|
|
490
|
+
height: 1px;
|
|
491
|
+
background: var(--color-hf-border-subtle, #E4E8EF);
|
|
492
|
+
margin: 4px 0;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/* ════════════════════════════════════════════════════════════════════════════
|
|
496
|
+
* SKELETON + SPINNER
|
|
497
|
+
* ════════════════════════════════════════════════════════════════════════════ */
|
|
498
|
+
@keyframes hf-shimmer {
|
|
499
|
+
0% { background-position: -200% 0; }
|
|
500
|
+
100% { background-position: 200% 0; }
|
|
501
|
+
}
|
|
502
|
+
.skeleton {
|
|
503
|
+
background: linear-gradient(
|
|
504
|
+
90deg,
|
|
505
|
+
var(--color-hf-bg-muted, #F1F3F6) 25%,
|
|
506
|
+
var(--color-hf-border-subtle, #E4E8EF) 50%,
|
|
507
|
+
var(--color-hf-bg-muted, #F1F3F6) 75%
|
|
508
|
+
);
|
|
509
|
+
background-size: 200% 100%;
|
|
510
|
+
animation: hf-shimmer 1.4s infinite;
|
|
511
|
+
border-radius: 4px;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
@keyframes hf-spin { to { transform: rotate(360deg); } }
|
|
515
|
+
.hf-spin { animation: hf-spin .6s linear infinite; }
|