@brightweblabs/theme 0.2.1 → 0.4.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/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # `@brightweblabs/theme` token contract
2
+
3
+ BrightWeb themes follow one cascade: L0 brand primitives feed L1 shadcn semantics, L2 describes brand-agnostic states, and L3 exposes component and experience controls. Package components consume L1-L3 tokens only; theme files such as `themes/mq.css` override values without changing component code.
4
+
5
+ ## Typography and font loading
6
+
7
+ Geist is the offline-safe platform default. Applications load `GeistSans` and `GeistMono` from the `geist/font` package and attach their variables to the app root (the platform layout uses `<body>`):
8
+
9
+ ```tsx
10
+ import { GeistMono } from "geist/font/mono";
11
+ import { GeistSans } from "geist/font/sans";
12
+
13
+ <body className={`${GeistSans.variable} ${GeistMono.variable}`}>
14
+ ```
15
+
16
+ The family contract starts at `--font-body`, `--font-heading`, and `--font-code`; Tailwind-facing `--font-sans`, `--font-display`, and `--font-mono` aliases resolve through those tokens. Raw sizes live only in the `--text-ui-*`, `--type-*`, weight, leading, and tracking scales in `src/tokens.css`. Components consume semantic utilities such as `text-ui-body`, `portal-title`, and `heading-2`, so changing a family or scale does not require component edits.
17
+
18
+ Client themes can override the family without forking recipes. The MQ path is:
19
+
20
+ ```css
21
+ @import "@brightweblabs/theme/css";
22
+ @import "@brightweblabs/theme/themes/mq";
23
+ @import "@brightweblabs/theme/themes/mq-aliases";
24
+
25
+ :root {
26
+ --font-client-body: "Mulish", sans-serif;
27
+ --font-body: var(--font-client-body);
28
+ }
29
+ ```
30
+
31
+ `themes/mq.css` supplies MQ color and surface values, while `themes/mq-aliases.css` maps legacy MQ typography names onto the same tokenized scale. Import the MQ stylesheet after the base theme. Keep font loading at the app root; theme packages only select variables and never fetch fonts.
32
+
33
+ ## Runtime theme switching
34
+
35
+ Theme tokens respond to both `html.dark` and `html[data-theme="dark"]`. Platform apps should use the shell-owned controller so those selectors never drift:
36
+
37
+ ```tsx
38
+ import { ThemeProvider, ThemeScript } from "@brightweblabs/app-shell";
39
+
40
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
41
+ return (
42
+ <html lang="en" suppressHydrationWarning>
43
+ <head>
44
+ <ThemeScript defaultTheme="light" />
45
+ </head>
46
+ <body>
47
+ <ThemeProvider defaultTheme="light">{children}</ThemeProvider>
48
+ </body>
49
+ </html>
50
+ );
51
+ }
52
+ ```
53
+
54
+ `ThemeScript` applies the stored `bw-theme` mode before hydration to prevent a light-theme flash. `ThemeProvider` keeps the root class, `data-theme`, and `color-scheme` synchronized, persists `"light" | "dark" | "system"`, and follows `prefers-color-scheme` while system mode is selected. Account menus rendered below the provider use this controller automatically.
55
+
56
+ ## L3 visual tokens
57
+
58
+ | Tokens | Purpose |
59
+ | --- | --- |
60
+ | `--font-body`, `--font-heading`, `--font-code`; `--font-sans`, `--font-display`, `--font-mono` | Root font families and their Tailwind-facing aliases. |
61
+ | `--type-weight-*`, `--type-leading-*`, `--type-tracking-*` | Shared typography weight, rhythm, and tracking primitives. |
62
+ | `--text-ui-*`, `--type-paragraph*`, `--type-heading-2`, `--type-label` | Tokenized platform and compatibility type scales. |
63
+ | `--foreground-muted-accessible`, `--foreground-inverse-muted`, `--foreground-inverse-subtle` | Contrast-safe secondary and inverse text roles. |
64
+ | `--text-ui-chip`, `--text-ui-action`, `--text-ui-calendar` | Compact control and calendar type sizes. |
65
+ | `--text-ui-shell-title`, `--text-ui-report-title`, `--text-ui-report-title-lg`, `--text-ui-report-metric` | Shell and CRM report display type sizes. |
66
+ | `--surface-overlay`, `--surface-overlay-strong`, `--surface-tooltip`, `--surface-badge-tint` | Overlay, tooltip, and tint surfaces. |
67
+ | `--surface-button-soft`, `--surface-button-soft-hover`, `--border-button-soft-hover` | Soft button surface states. |
68
+ | `--surface-selection`, `--border-selection`, `--surface-pagination-active`, `--border-pagination-active` | Selected control and active pagination states. |
69
+ | `--surface-danger-subtle` | Subtle destructive-state background. |
70
+ | `--surface-account-team`, `--surface-account-client`, `--surface-account`, `--surface-account-hover`, `--account-presence` | Account rail identity, presence, and interaction surfaces. |
71
+ | `--account-presence-size`, `--radius-swatch`, `--radius-pill`, `--radius-scrollbar` | Presence, swatch, pill, avatar, and scrollbar geometry. |
72
+ | `--row-hover-sweep`, `--surface-status-success`, `--surface-status-warning`, `--surface-status-danger` | Shared row and status surfaces. |
73
+ | `--dashboard-*` | Aggregate dashboard glows, shadows, task groups, milestone states, and local shell contrast recipes. |
74
+ | `--tint-soft-border`, `--tint-soft-bg`, `--tint-soft-hover`, `--tint-hero-border`, `--tint-hero-bg`, `--tint-hero-fg`, `--tint-hero-hover` | Dynamic tint recipes resolved where a component supplies `--tint`. |
75
+ | `--scrollbar-thumb`, `--scrollbar-thumb-hover`, `--scrollbar-size` | Shared scrollbar colour and geometry. |
76
+ | `--toast-success-bg`, `--toast-success-border`, `--toast-warning-bg`, `--toast-warning-text`, `--toast-warning-border`, `--toast-error-bg`, `--toast-error-border`, `--toast-info-bg`, `--toast-info-text`, `--toast-info-border` | Sonner semantic toast palette. |
77
+ | `--shadow-accent-control`, `--shadow-toolbar-control`, `--shadow-toolbar-popover`, `--shadow-tooltip`, `--shadow-dialog`, `--shadow-phone-dropdown` | Component elevation recipes. |
78
+ | `--report-hero-glow`, `--report-hero-rule` | CRM report decorative surfaces. |
79
+ | `--shell-frame-offset`, `--shell-sidebar-width`, `--shell-sidebar-collapsed-width`, `--shell-sidebar-toggle-offset`, `--shell-sidebar-toggle-size`, `--shell-sidebar-toggle-inset`, `--shell-brand-height` | Shell frame and sidebar geometry. |
80
+ | `--shell-nav-item-height`, `--shell-nav-item-collapsed-size`, `--shell-nav-icon-well-size`, `--shell-nav-icon-radius`, `--shell-nav-icon-size`, `--shell-nav-divider-width`, `--shell-nav-child-height`, `--shell-nav-child-enter-offset` | Shell navigation sizing and entry geometry. |
81
+ | `--shell-nav-active-indicator-inset`, `--shell-nav-active-indicator-collapsed-inset`, `--shell-nav-active-indicator-width`, `--shell-nav-active-indicator-offset`, `--shell-nav-context-dot-size`, `--shell-nav-context-dot-offset`, `--shell-nav-context-dot-border` | Shell navigation active markers. |
82
+ | `--shell-header-divider-height`, `--shell-account-gap`, `--shell-account-padding-y` | Header and account-control anatomy. |
83
+ | `--shell-surface-border`, `--shell-surface-hover`, `--shell-surface-active`, `--shell-hairline`, `--shell-icon-bg`, `--shell-group-open`, `--shell-shadow`, `--shell-background`, `--shell-sidebar-background` | Shell surfaces and elevation. |
84
+ | `--shell-control-muted`, `--shell-nav-foreground`, `--shell-nav-active-border`, `--shell-nav-active-icon-bg`, `--shell-nav-active-icon-fg`, `--shell-nav-context-icon-bg`, `--shell-nav-context-icon-fg` | Shell control and navigation colours. |
85
+ | `--shell-nav-child-fg`, `--shell-nav-child-icon-fg`, `--shell-nav-child-active-bg`, `--shell-nav-child-active-border`, `--shell-navbar-fg`, `--shell-navbar-muted`, `--shell-navbar-hairline` | Child-navigation and header colours. |
86
+ | `--toolbar-popover-width`, `--toolbar-chip-height`, `--toolbar-search-min-width`, `--toolbar-icon-size`, `--radius-toolbar-popover` | Shared CRM toolbar geometry. |
87
+ | `--crm-sidebar-gap`, `--crm-report-copy-max-width`, `--crm-report-metric-min-width`, `--report-stat-min-width` | CRM dashboard and report layout. |
88
+ | `--timeline-list-inset`, `--timeline-line-offset`, `--timeline-marker-offset` | Timeline anatomy. |
89
+ | `--table-header-height`, `--table-cell-padding-x`, `--table-cell-padding-y`, `--crm-table-viewport-offset`, `--crm-table-min-height`, `--crm-table-empty-min-height` | Table row anatomy and CRM viewport sizing. |
90
+ | `--dialog-width`, `--sheet-width`, `--crm-sheet-width`, `--menu-min-width`, `--chart-tooltip-min-width` | Overlay and floating-surface geometry. |
91
+ | `--skeleton-line-height`, `--skeleton-line-height-lg`, `--skeleton-line-height-compact`, `--skeleton-line-height-xs` | Skeleton text-line anatomy. |
92
+ | `--section-icon-size`, `--surface-enter-offset`, `--space-eyebrow-y` | Shared surface icon, entrance, and eyebrow geometry. |
93
+
94
+ All tokens above have neutral defaults in `src/tokens.css`. Values that are derived from semantic tokens automatically follow `themes/mq.css`; a direct MQ declaration is needed only when MQ intentionally differs from the neutral default. `themes/mq-aliases.css` remains the compatibility layer for legacy MQ primitive and typography names.
95
+
96
+ The base stylesheet also provides the global `prefers-reduced-motion: reduce` contract. It disables smooth scrolling and collapses animation and transition timing; skeleton and package styles add static fallbacks where their animated state would otherwise remain off-screen or transparent.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@brightweblabs/theme",
3
3
  "private": false,
4
- "version": "0.2.1",
4
+ "version": "0.4.0",
5
5
  "main": "./src/tint.ts",
6
6
  "types": "./src/tint.ts",
7
7
  "files": [
package/src/base.css CHANGED
@@ -5,6 +5,7 @@
5
5
  ::before,
6
6
  ::after {
7
7
  box-sizing: border-box;
8
+ border-color: var(--border);
8
9
  }
9
10
 
10
11
  html,
@@ -13,7 +14,7 @@
13
14
  padding: 0;
14
15
  background: var(--page-background);
15
16
  color: var(--foreground);
16
- font-family: var(--font-sans), sans-serif;
17
+ font-family: var(--font-sans);
17
18
  -webkit-font-smoothing: antialiased;
18
19
  -moz-osx-font-smoothing: grayscale;
19
20
  text-rendering: optimizeLegibility;
@@ -30,4 +31,28 @@
30
31
  input {
31
32
  font: inherit;
32
33
  }
34
+
35
+ /* Hand-rolled controls keep a visible keyboard state. Component utilities
36
+ can replace this fallback with their own focus-visible treatment. */
37
+ :where(button, a[href], [role="button"]):focus-visible {
38
+ outline: 2px solid currentColor;
39
+ outline-offset: 2px;
40
+ }
41
+
42
+ @media (prefers-reduced-motion: reduce) {
43
+ html {
44
+ scroll-behavior: auto !important;
45
+ }
46
+
47
+ *,
48
+ ::before,
49
+ ::after {
50
+ animation-delay: 0ms !important;
51
+ animation-duration: 0ms !important;
52
+ animation-iteration-count: 1 !important;
53
+ scroll-behavior: auto !important;
54
+ transition-delay: 0ms !important;
55
+ transition-duration: 0ms !important;
56
+ }
57
+ }
33
58
  }
package/src/surfaces.css CHANGED
@@ -1,93 +1,95 @@
1
1
  /* ── Shared structural surfaces ───────────────────────────────────────── */
2
- .surface-card {
3
- border-radius: var(--radius-card);
4
- border: 1px solid var(--border-hairline);
5
- background: var(--surface-card);
6
- padding: var(--surface-card-padding);
7
- animation: surface-fade-in 640ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
8
- }
2
+ /* Layered so Tailwind utilities (p-0, bg-*, …) can override per-instance.
3
+ Unlayered, these beat @layer utilities and e.g. p-0 on the CRM table card
4
+ silently loses to the default card padding. */
5
+ @layer components {
6
+ .surface-card {
7
+ border-radius: var(--radius-card);
8
+ border: 1px solid var(--border-hairline);
9
+ background: var(--surface-card);
10
+ padding: var(--surface-card-padding);
11
+ animation: surface-fade-in 640ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
12
+ }
9
13
 
10
- .surface-card.is-light {
11
- background: var(--surface-card-light);
12
- }
14
+ .surface-card.is-light {
15
+ background: var(--surface-card-light);
16
+ }
13
17
 
14
- /* A near-black panel has strong figure/ground in light mode. In dark mode it
15
- elevates above the page through a lighter accent mix, border, and shadow. */
16
- .brand-panel {
17
- --row-sweep-tint: var(--brand-panel-foreground);
18
- background: var(--brand-panel-surface);
19
- }
18
+ /* A near-black panel has strong figure/ground in light mode. In dark mode it
19
+ elevates above the page through a lighter accent mix, border, and shadow. */
20
+ .brand-panel {
21
+ --row-sweep-tint: var(--brand-panel-foreground);
22
+ background: var(--brand-panel-surface);
23
+ }
20
24
 
21
- .dark .brand-panel,
22
- .theme-dark .brand-panel {
23
- border: 1px solid var(--brand-panel-border);
24
- box-shadow: var(--brand-shadow-lift);
25
- }
25
+ .dark .brand-panel,
26
+ .theme-dark .brand-panel {
27
+ border: 1px solid var(--brand-panel-border);
28
+ box-shadow: var(--brand-shadow-lift);
29
+ }
26
30
 
27
- .row-hover-sweep {
28
- pointer-events: none;
29
- position: absolute;
30
- inset: 0;
31
- border-radius: var(--radius-card);
32
- opacity: 0;
33
- transition: opacity 300ms ease;
34
- background: linear-gradient(
35
- 90deg,
36
- transparent,
37
- color-mix(in srgb, var(--row-sweep-tint, var(--foreground)) 9%, transparent) 50%,
38
- transparent
39
- );
40
- }
31
+ .row-hover-sweep {
32
+ pointer-events: none;
33
+ position: absolute;
34
+ inset: 0;
35
+ border-radius: var(--radius-card);
36
+ opacity: 0;
37
+ transition: opacity 300ms ease;
38
+ background: var(--row-hover-sweep);
39
+ }
41
40
 
42
- .group:hover > .row-hover-sweep,
43
- .group:focus-visible > .row-hover-sweep {
44
- opacity: 1;
41
+ .group:hover > .row-hover-sweep,
42
+ .group:focus-visible > .row-hover-sweep {
43
+ opacity: 1;
44
+ }
45
45
  }
46
46
 
47
47
  /* ── Canonical tinted-pill recipes ────────────────────────────────────── */
48
48
  @layer components {
49
49
  .tint-soft {
50
- border-color: color-mix(in srgb, var(--tint) 34%, transparent);
51
- background-color: color-mix(in srgb, var(--tint) 10%, var(--card));
50
+ border-color: var(--tint-soft-border);
51
+ background-color: var(--tint-soft-bg);
52
52
  color: var(--tint-strong);
53
53
  }
54
54
 
55
55
  .tint-soft:hover {
56
- background-color: color-mix(in srgb, var(--tint) 14%, var(--card));
56
+ background-color: var(--tint-soft-hover);
57
57
  }
58
58
 
59
59
  .tint-hero {
60
- border-color: color-mix(in srgb, var(--tint) 46%, transparent);
61
- background-color: color-mix(in srgb, var(--tint) 16%, transparent);
62
- color: color-mix(in srgb, var(--tint) 18%, white);
60
+ border-color: var(--tint-hero-border);
61
+ background-color: var(--tint-hero-bg);
62
+ color: var(--tint-hero-fg);
63
63
  }
64
64
 
65
65
  .tint-hero:hover {
66
- background-color: color-mix(in srgb, var(--tint) 24%, transparent);
66
+ background-color: var(--tint-hero-hover);
67
67
  }
68
68
  }
69
69
 
70
- .section-icon {
70
+ @layer components {
71
+ .section-icon {
71
72
  display: flex;
72
- width: 1.875rem;
73
- height: 1.875rem;
73
+ width: var(--section-icon-size);
74
+ height: var(--section-icon-size);
74
75
  flex-shrink: 0;
75
76
  align-items: center;
76
77
  justify-content: center;
77
- border-radius: 999px;
78
+ border-radius: var(--radius-pill);
78
79
  background: var(--muted);
79
80
  color: var(--muted-foreground);
80
81
  }
81
82
 
82
- .stat-cell {
83
- padding: var(--stat-cell-padding);
83
+ .stat-cell {
84
+ padding: var(--stat-cell-padding);
85
+ }
84
86
  }
85
87
 
86
- /* Skeleton shimmer respects reduced-motion with a quiet breathe fallback. */
88
+ /* Skeleton shimmer becomes a static surface under reduced motion. */
87
89
  @keyframes surface-fade-in {
88
90
  from {
89
91
  opacity: 0;
90
- transform: translateY(10px);
92
+ transform: translateY(var(--surface-enter-offset));
91
93
  }
92
94
  to {
93
95
  opacity: 1;
@@ -101,17 +103,8 @@
101
103
  }
102
104
  }
103
105
 
104
- @keyframes skeleton-breathe {
105
- 0%,
106
- 100% {
107
- opacity: 1;
108
- }
109
- 50% {
110
- opacity: 0.6;
111
- }
112
- }
113
-
114
- .skeleton-ghost {
106
+ @layer components {
107
+ .skeleton-ghost {
115
108
  position: relative;
116
109
  overflow: hidden;
117
110
  background: var(--skeleton);
@@ -132,44 +125,47 @@
132
125
  }
133
126
 
134
127
  .skeleton-ghost {
135
- animation: skeleton-breathe 2.4s ease-in-out infinite;
128
+ animation: none;
129
+ opacity: 1;
130
+ transform: none;
136
131
  }
137
132
  }
138
133
 
139
134
  .eyebrow {
140
135
  display: inline-flex;
141
136
  width: fit-content;
142
- padding: 6px 10px;
143
- border-radius: 999px;
137
+ padding: var(--space-eyebrow-y) var(--radius-control);
138
+ border-radius: var(--radius-pill);
144
139
  background: var(--brand-surface-tint);
145
140
  color: var(--brand-accent);
146
141
  font-size: var(--text-ui-label);
147
142
  font-weight: var(--font-weight-bold);
148
- letter-spacing: 0.04em;
143
+ letter-spacing: var(--type-tracking-040);
149
144
  text-transform: uppercase;
150
145
  }
151
146
 
152
147
  .status {
153
148
  display: inline-flex;
154
149
  align-items: center;
155
- gap: 8px;
156
- padding: 8px 12px;
157
- border-radius: 999px;
150
+ gap: var(--space-xs);
151
+ padding: var(--space-xs) var(--space-sm);
152
+ border-radius: var(--radius-pill);
158
153
  font-size: var(--text-ui-meta);
159
154
  font-weight: var(--font-weight-bold);
160
155
  }
161
156
 
162
157
  .status.ok {
163
- background: color-mix(in srgb, var(--semantic-success) 14%, transparent);
158
+ background: var(--surface-status-success);
164
159
  color: var(--semantic-success-strong);
165
160
  }
166
161
 
167
162
  .status.warn {
168
- background: color-mix(in srgb, var(--semantic-warning) 14%, transparent);
163
+ background: var(--surface-status-warning);
169
164
  color: var(--semantic-warning-strong);
170
165
  }
171
166
 
172
- .status.error {
173
- background: color-mix(in srgb, var(--semantic-danger) 12%, transparent);
174
- color: var(--semantic-danger-strong);
167
+ .status.error {
168
+ background: var(--surface-status-danger);
169
+ color: var(--semantic-danger-strong);
170
+ }
175
171
  }
package/src/theme.css CHANGED
@@ -12,6 +12,7 @@
12
12
  --color-secondary-foreground: var(--secondary-foreground);
13
13
  --color-muted: var(--muted);
14
14
  --color-muted-foreground: var(--muted-foreground);
15
+ --color-foreground-muted-accessible: var(--foreground-muted-accessible);
15
16
  --color-accent: var(--accent);
16
17
  --color-accent-foreground: var(--accent-foreground);
17
18
  --color-destructive: var(--destructive);
@@ -83,6 +84,45 @@
83
84
  --radius-2xl: var(--radius-panel);
84
85
  --radius-3xl: var(--radius-panel);
85
86
 
87
+ /* Tailwind typography utilities resolve through the same token contract. */
88
+ --font-sans: var(--font-body);
89
+ --font-display: var(--font-heading);
90
+ --font-mono: var(--font-code);
91
+ --font-weight-regular: var(--type-weight-regular);
92
+ --font-weight-normal: var(--type-weight-regular);
93
+ --font-weight-medium: var(--type-weight-medium);
94
+ --font-weight-semibold: var(--type-weight-medium);
95
+ --font-weight-bold: var(--type-weight-bold);
96
+ --font-weight-heavy: var(--type-weight-heavy);
97
+ --font-weight-extrabold: var(--type-weight-heavy);
98
+ --font-weight-black: var(--type-weight-black);
99
+ --text-xs: var(--text-ui-meta);
100
+ --text-xs--line-height: var(--type-leading-133);
101
+ --text-sm: var(--text-ui-body);
102
+ --text-sm--line-height: var(--type-leading-143);
103
+ --text-base: var(--type-paragraph);
104
+ --text-base--line-height: var(--type-leading-normal);
105
+ --text-lg: var(--text-ui-subhead);
106
+ --text-lg--line-height: var(--type-leading-156);
107
+ --text-xl: var(--text-ui-panel-title);
108
+ --text-xl--line-height: var(--type-leading-140);
109
+ --text-2xl: var(--text-ui-heading);
110
+ --text-2xl--line-height: var(--type-leading-133);
111
+ --text-3xl: var(--text-ui-title-sm);
112
+ --text-3xl--line-height: var(--type-leading-120);
113
+ --leading-none: var(--type-leading-none);
114
+ --leading-tight: var(--type-leading-tight);
115
+ --leading-snug: var(--type-leading-snug);
116
+ --leading-normal: var(--type-leading-normal);
117
+ --leading-relaxed: var(--type-leading-relaxed);
118
+ --leading-loose: var(--type-leading-loose);
119
+ --tracking-tighter: var(--type-tracking-n050);
120
+ --tracking-tight: var(--type-tracking-tight);
121
+ --tracking-normal: var(--type-tracking-normal);
122
+ --tracking-wide: var(--type-tracking-wide);
123
+ --tracking-wider: var(--type-tracking-wider);
124
+ --tracking-widest: var(--type-tracking-widest);
125
+
86
126
  /* Named spacing keys sit alongside Tailwind's numeric scale. */
87
127
  --spacing-2xs: var(--space-2xs);
88
128
  --spacing-xs: var(--space-xs);