@marcoschwartz/lite-ui 0.24.11 → 0.24.13

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 CHANGED
@@ -19,23 +19,21 @@ A lightweight, modern UI component library built with React, TypeScript, and Tai
19
19
  npm install @marcoschwartz/lite-ui
20
20
  ```
21
21
 
22
- ### Setup
22
+ ### Setup for Next.js (Required)
23
23
 
24
- 1. **Import the CSS** in your app's entry point:
24
+ > ⚠️ **Important:** You must include `ColorSchemeScript` in your layout to prevent dark mode flashing (FOUC).
25
25
 
26
26
  ```tsx
27
- // app/layout.tsx (Next.js) or main.tsx (Vite)
27
+ // app/layout.tsx
28
+ import { ThemeProvider, ColorSchemeScript } from '@marcoschwartz/lite-ui';
28
29
  import '@marcoschwartz/lite-ui/styles.css';
29
- ```
30
-
31
- 2. **Wrap your app with ThemeProvider** (recommended):
32
-
33
- ```tsx
34
- import { ThemeProvider } from '@marcoschwartz/lite-ui';
35
30
 
36
31
  export default function RootLayout({ children }) {
37
32
  return (
38
- <html lang="en">
33
+ <html lang="en" suppressHydrationWarning>
34
+ <head>
35
+ <ColorSchemeScript defaultColorMode="system" />
36
+ </head>
39
37
  <body>
40
38
  <ThemeProvider defaultTheme="default" defaultColorMode="system">
41
39
  {children}
@@ -46,7 +44,36 @@ export default function RootLayout({ children }) {
46
44
  }
47
45
  ```
48
46
 
49
- 3. **Start using components:**
47
+ **Key points:**
48
+ - `ColorSchemeScript` must be in `<head>` - it prevents flash by setting colors before React hydrates
49
+ - `suppressHydrationWarning` on `<html>` is required to avoid React warnings
50
+ - `defaultColorMode` should match between `ColorSchemeScript` and `ThemeProvider`
51
+
52
+ ### Setup for Vite/Other React Apps
53
+
54
+ ```tsx
55
+ // main.tsx
56
+ import '@marcoschwartz/lite-ui/styles.css';
57
+ import { ThemeProvider } from '@marcoschwartz/lite-ui';
58
+
59
+ ReactDOM.createRoot(document.getElementById('root')!).render(
60
+ <ThemeProvider defaultTheme="default" defaultColorMode="system">
61
+ <App />
62
+ </ThemeProvider>
63
+ );
64
+ ```
65
+
66
+ For Vite apps, add the theme script to your `index.html`:
67
+
68
+ ```html
69
+ <head>
70
+ <script>
71
+ (function(){try{var d=document.documentElement,c=localStorage.getItem('lite-ui-color-mode')||'system',r=c;if(c==='system'||!c)r=matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light';d.setAttribute('data-color-mode',r);d.style.colorScheme=r;if(r==='dark'){d.classList.add('dark');d.style.backgroundColor='hsl(0,0%,3.9%)';d.style.color='hsl(0,0%,98%)'}else{d.classList.remove('dark');d.style.backgroundColor='hsl(0,0%,100%)';d.style.color='hsl(0,0%,3.9%)'}}catch(e){}})();
72
+ </script>
73
+ </head>
74
+ ```
75
+
76
+ ### Start using components:
50
77
 
51
78
  ```tsx
52
79
  import { Button, Card, TextInput } from '@marcoschwartz/lite-ui';
@@ -99,7 +126,29 @@ export default function Page() {
99
126
 
100
127
  ## 🌙 Dark Mode
101
128
 
102
- Lite UI includes built-in dark mode support with three color modes:
129
+ Lite UI includes built-in dark mode support with three color modes.
130
+
131
+ ### Preventing Flash (FOUC)
132
+
133
+ To prevent the flash of wrong colors on page load, you **must** use `ColorSchemeScript`:
134
+
135
+ ```tsx
136
+ // app/layout.tsx
137
+ import { ColorSchemeScript, ThemeProvider } from '@marcoschwartz/lite-ui';
138
+
139
+ <html lang="en" suppressHydrationWarning>
140
+ <head>
141
+ <ColorSchemeScript defaultColorMode="system" />
142
+ </head>
143
+ <body>
144
+ <ThemeProvider defaultColorMode="system">
145
+ {children}
146
+ </ThemeProvider>
147
+ </body>
148
+ </html>
149
+ ```
150
+
151
+ ### Toggling Dark Mode
103
152
 
104
153
  ```tsx
105
154
  import { useTheme } from '@marcoschwartz/lite-ui';
@@ -117,6 +166,29 @@ function ThemeToggle() {
117
166
  }
118
167
  ```
119
168
 
169
+ ## ⬆️ Upgrading from v0.24.7 or earlier
170
+
171
+ If you're upgrading from an earlier version, update your `layout.tsx`:
172
+
173
+ ```diff
174
+ - import { ThemeProvider, themeScript } from '@marcoschwartz/lite-ui';
175
+ + import { ThemeProvider, ColorSchemeScript } from '@marcoschwartz/lite-ui';
176
+
177
+ <html lang="en" suppressHydrationWarning>
178
+ <head>
179
+ - <script
180
+ - dangerouslySetInnerHTML={{ __html: themeScript }}
181
+ - suppressHydrationWarning
182
+ - />
183
+ + <ColorSchemeScript defaultColorMode="system" />
184
+ </head>
185
+ ```
186
+
187
+ The new `ColorSchemeScript` component:
188
+ - Sets CSS custom properties inline (prevents flash even if CSS loads slowly)
189
+ - Cleaner API than the old `themeScript` string
190
+ - Supports `nonce` prop for Content Security Policy
191
+
120
192
  ## 🎭 Theming
121
193
 
122
194
  Switch between built-in themes or create your own:
package/dist/index.d.mts CHANGED
@@ -1347,8 +1347,10 @@ declare function getColorSchemeScriptString(defaultColorMode?: 'light' | 'dark'
1347
1347
  * Theme initialization script that runs before React hydration
1348
1348
  * This prevents flickering by setting the theme before the page renders
1349
1349
  * Must be inlined in the HTML <head> as a blocking script
1350
+ *
1351
+ * @deprecated Use ColorSchemeScript component instead for better Next.js integration
1350
1352
  */
1351
- declare const themeScript = "\n(function() {\n try {\n // Get stored preferences\n var storedTheme = localStorage.getItem('lite-ui-theme') || 'default';\n var storedColorMode = localStorage.getItem('lite-ui-color-mode');\n\n // Determine color mode (system, light, or dark)\n var colorMode = storedColorMode;\n if (!colorMode || colorMode === 'system') {\n colorMode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n }\n\n // Set attributes before render\n var html = document.documentElement;\n html.setAttribute('data-theme', storedTheme);\n html.setAttribute('data-color-mode', colorMode);\n\n // Set color-scheme for browser UI elements (scrollbars, form controls)\n html.style.colorScheme = colorMode;\n\n // Add dark class for Tailwind\n if (colorMode === 'dark') {\n html.classList.add('dark');\n // Set critical inline styles as fallback before CSS loads\n html.style.backgroundColor = 'hsl(0 0% 3.9%)';\n html.style.color = 'hsl(0 0% 98%)';\n } else {\n html.classList.remove('dark');\n html.style.backgroundColor = 'hsl(0 0% 100%)';\n html.style.color = 'hsl(0 0% 3.9%)';\n }\n\n // Mark as initialized to prevent FOUC\n html.setAttribute('data-theme-initialized', 'true');\n } catch (e) {\n // Ensure page shows even on error\n document.documentElement.setAttribute('data-theme-initialized', 'true');\n }\n})();\n";
1353
+ declare const themeScript = "(function(){try{var d=document.documentElement,s=d.style,t=localStorage.getItem('lite-ui-theme')||'default',c=localStorage.getItem('lite-ui-color-mode'),r=c;if(!c||c==='system')r=matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light';d.setAttribute('data-theme',t);d.setAttribute('data-color-mode',r);s.colorScheme=r;if(r==='dark'){d.classList.add('dark');s.setProperty('--background','0 0% 3.9%');s.setProperty('--foreground','0 0% 98%');s.setProperty('--card','0 0% 5.9%');s.setProperty('--card-foreground','0 0% 98%');s.setProperty('--popover','0 0% 5.9%');s.setProperty('--popover-foreground','0 0% 98%');s.setProperty('--muted','0 0% 14.9%');s.setProperty('--muted-foreground','0 0% 63.9%');s.setProperty('--border','0 0% 25%');s.setProperty('--input','0 0% 25%');s.setProperty('--primary','217 91% 60%');s.setProperty('--secondary','0 0% 14.9%');s.setProperty('--accent','0 0% 14.9%');s.backgroundColor='hsl(0,0%,3.9%)';s.color='hsl(0,0%,98%)'}else{d.classList.remove('dark');s.backgroundColor='hsl(0,0%,100%)';s.color='hsl(0,0%,3.9%)'}d.setAttribute('data-theme-initialized','true')}catch(e){document.documentElement.setAttribute('data-theme-initialized','true')}})()";
1352
1354
  declare function getThemeScript(): string;
1353
1355
 
1354
1356
  interface IconProps {
package/dist/index.d.ts CHANGED
@@ -1347,8 +1347,10 @@ declare function getColorSchemeScriptString(defaultColorMode?: 'light' | 'dark'
1347
1347
  * Theme initialization script that runs before React hydration
1348
1348
  * This prevents flickering by setting the theme before the page renders
1349
1349
  * Must be inlined in the HTML <head> as a blocking script
1350
+ *
1351
+ * @deprecated Use ColorSchemeScript component instead for better Next.js integration
1350
1352
  */
1351
- declare const themeScript = "\n(function() {\n try {\n // Get stored preferences\n var storedTheme = localStorage.getItem('lite-ui-theme') || 'default';\n var storedColorMode = localStorage.getItem('lite-ui-color-mode');\n\n // Determine color mode (system, light, or dark)\n var colorMode = storedColorMode;\n if (!colorMode || colorMode === 'system') {\n colorMode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n }\n\n // Set attributes before render\n var html = document.documentElement;\n html.setAttribute('data-theme', storedTheme);\n html.setAttribute('data-color-mode', colorMode);\n\n // Set color-scheme for browser UI elements (scrollbars, form controls)\n html.style.colorScheme = colorMode;\n\n // Add dark class for Tailwind\n if (colorMode === 'dark') {\n html.classList.add('dark');\n // Set critical inline styles as fallback before CSS loads\n html.style.backgroundColor = 'hsl(0 0% 3.9%)';\n html.style.color = 'hsl(0 0% 98%)';\n } else {\n html.classList.remove('dark');\n html.style.backgroundColor = 'hsl(0 0% 100%)';\n html.style.color = 'hsl(0 0% 3.9%)';\n }\n\n // Mark as initialized to prevent FOUC\n html.setAttribute('data-theme-initialized', 'true');\n } catch (e) {\n // Ensure page shows even on error\n document.documentElement.setAttribute('data-theme-initialized', 'true');\n }\n})();\n";
1353
+ declare const themeScript = "(function(){try{var d=document.documentElement,s=d.style,t=localStorage.getItem('lite-ui-theme')||'default',c=localStorage.getItem('lite-ui-color-mode'),r=c;if(!c||c==='system')r=matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light';d.setAttribute('data-theme',t);d.setAttribute('data-color-mode',r);s.colorScheme=r;if(r==='dark'){d.classList.add('dark');s.setProperty('--background','0 0% 3.9%');s.setProperty('--foreground','0 0% 98%');s.setProperty('--card','0 0% 5.9%');s.setProperty('--card-foreground','0 0% 98%');s.setProperty('--popover','0 0% 5.9%');s.setProperty('--popover-foreground','0 0% 98%');s.setProperty('--muted','0 0% 14.9%');s.setProperty('--muted-foreground','0 0% 63.9%');s.setProperty('--border','0 0% 25%');s.setProperty('--input','0 0% 25%');s.setProperty('--primary','217 91% 60%');s.setProperty('--secondary','0 0% 14.9%');s.setProperty('--accent','0 0% 14.9%');s.backgroundColor='hsl(0,0%,3.9%)';s.color='hsl(0,0%,98%)'}else{d.classList.remove('dark');s.backgroundColor='hsl(0,0%,100%)';s.color='hsl(0,0%,3.9%)'}d.setAttribute('data-theme-initialized','true')}catch(e){document.documentElement.setAttribute('data-theme-initialized','true')}})()";
1352
1354
  declare function getThemeScript(): string;
1353
1355
 
1354
1356
  interface IconProps {
package/dist/index.js CHANGED
@@ -9109,36 +9109,7 @@ var CalendarHeatmap = ({
9109
9109
  // src/theme/ColorSchemeScript.tsx
9110
9110
  var import_jsx_runtime121 = require("react/jsx-runtime");
9111
9111
  function getColorSchemeScript(defaultColorMode, localStorageKey) {
9112
- return `
9113
- (function(){
9114
- try {
9115
- var d = document.documentElement;
9116
- var stored = localStorage.getItem('${localStorageKey}');
9117
- var mode = stored || '${defaultColorMode}';
9118
- var resolved = mode;
9119
-
9120
- if (mode === 'system' || !mode) {
9121
- resolved = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
9122
- }
9123
-
9124
- d.setAttribute('data-color-mode', resolved);
9125
- d.style.colorScheme = resolved;
9126
-
9127
- if (resolved === 'dark') {
9128
- d.classList.add('dark');
9129
- d.style.backgroundColor = 'hsl(0,0%,3.9%)';
9130
- d.style.color = 'hsl(0,0%,98%)';
9131
- } else {
9132
- d.classList.remove('dark');
9133
- d.style.backgroundColor = 'hsl(0,0%,100%)';
9134
- d.style.color = 'hsl(0,0%,3.9%)';
9135
- }
9136
-
9137
- // Mark as initialized to prevent FOUC
9138
- d.setAttribute('data-theme-initialized', 'true');
9139
- } catch(e) {}
9140
- })();
9141
- `.trim();
9112
+ return `(function(){try{var d=document.documentElement,s=d.style,c=localStorage.getItem('${localStorageKey}')||'${defaultColorMode}',r=c;if(c==='system'||!c)r=matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light';d.setAttribute('data-color-mode',r);s.colorScheme=r;if(r==='dark'){d.classList.add('dark');s.setProperty('--background','0 0% 3.9%');s.setProperty('--foreground','0 0% 98%');s.setProperty('--card','0 0% 5.9%');s.setProperty('--card-foreground','0 0% 98%');s.setProperty('--popover','0 0% 5.9%');s.setProperty('--popover-foreground','0 0% 98%');s.setProperty('--muted','0 0% 14.9%');s.setProperty('--muted-foreground','0 0% 63.9%');s.setProperty('--border','0 0% 25%');s.setProperty('--input','0 0% 25%');s.setProperty('--primary','217 91% 60%');s.setProperty('--secondary','0 0% 14.9%');s.setProperty('--accent','0 0% 14.9%');s.backgroundColor='hsl(0,0%,3.9%)';s.color='hsl(0,0%,98%)'}else{d.classList.remove('dark');s.backgroundColor='hsl(0,0%,100%)';s.color='hsl(0,0%,3.9%)'}d.setAttribute('data-theme-initialized','true')}catch(e){}})()`;
9142
9113
  }
9143
9114
  function ColorSchemeScript({
9144
9115
  defaultColorMode = "system",
@@ -9160,47 +9131,7 @@ function getColorSchemeScriptString(defaultColorMode = "system", localStorageKey
9160
9131
  }
9161
9132
 
9162
9133
  // src/utils/theme-script.ts
9163
- var themeScript = `
9164
- (function() {
9165
- try {
9166
- // Get stored preferences
9167
- var storedTheme = localStorage.getItem('lite-ui-theme') || 'default';
9168
- var storedColorMode = localStorage.getItem('lite-ui-color-mode');
9169
-
9170
- // Determine color mode (system, light, or dark)
9171
- var colorMode = storedColorMode;
9172
- if (!colorMode || colorMode === 'system') {
9173
- colorMode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
9174
- }
9175
-
9176
- // Set attributes before render
9177
- var html = document.documentElement;
9178
- html.setAttribute('data-theme', storedTheme);
9179
- html.setAttribute('data-color-mode', colorMode);
9180
-
9181
- // Set color-scheme for browser UI elements (scrollbars, form controls)
9182
- html.style.colorScheme = colorMode;
9183
-
9184
- // Add dark class for Tailwind
9185
- if (colorMode === 'dark') {
9186
- html.classList.add('dark');
9187
- // Set critical inline styles as fallback before CSS loads
9188
- html.style.backgroundColor = 'hsl(0 0% 3.9%)';
9189
- html.style.color = 'hsl(0 0% 98%)';
9190
- } else {
9191
- html.classList.remove('dark');
9192
- html.style.backgroundColor = 'hsl(0 0% 100%)';
9193
- html.style.color = 'hsl(0 0% 3.9%)';
9194
- }
9195
-
9196
- // Mark as initialized to prevent FOUC
9197
- html.setAttribute('data-theme-initialized', 'true');
9198
- } catch (e) {
9199
- // Ensure page shows even on error
9200
- document.documentElement.setAttribute('data-theme-initialized', 'true');
9201
- }
9202
- })();
9203
- `;
9134
+ var themeScript = `(function(){try{var d=document.documentElement,s=d.style,t=localStorage.getItem('lite-ui-theme')||'default',c=localStorage.getItem('lite-ui-color-mode'),r=c;if(!c||c==='system')r=matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light';d.setAttribute('data-theme',t);d.setAttribute('data-color-mode',r);s.colorScheme=r;if(r==='dark'){d.classList.add('dark');s.setProperty('--background','0 0% 3.9%');s.setProperty('--foreground','0 0% 98%');s.setProperty('--card','0 0% 5.9%');s.setProperty('--card-foreground','0 0% 98%');s.setProperty('--popover','0 0% 5.9%');s.setProperty('--popover-foreground','0 0% 98%');s.setProperty('--muted','0 0% 14.9%');s.setProperty('--muted-foreground','0 0% 63.9%');s.setProperty('--border','0 0% 25%');s.setProperty('--input','0 0% 25%');s.setProperty('--primary','217 91% 60%');s.setProperty('--secondary','0 0% 14.9%');s.setProperty('--accent','0 0% 14.9%');s.backgroundColor='hsl(0,0%,3.9%)';s.color='hsl(0,0%,98%)'}else{d.classList.remove('dark');s.backgroundColor='hsl(0,0%,100%)';s.color='hsl(0,0%,3.9%)'}d.setAttribute('data-theme-initialized','true')}catch(e){document.documentElement.setAttribute('data-theme-initialized','true')}})()`;
9204
9135
  function getThemeScript() {
9205
9136
  return themeScript;
9206
9137
  }