@arqel-dev/theme 0.11.0 → 0.14.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/dist/index.d.ts +13 -56
- package/dist/index.js +5 -93
- package/dist/index.js.map +1 -1
- package/package.json +8 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,57 +1,24 @@
|
|
|
1
|
+
import { Theme } from '@arqel-dev/react/providers';
|
|
2
|
+
export { PreventFlashOptions, ResolvedTheme, Theme, ThemeContext, ThemeContextValue, ThemeProvider, preventFlashScript, useTheme } from '@arqel-dev/react/providers';
|
|
1
3
|
import * as react from 'react';
|
|
2
4
|
import { ReactNode } from 'react';
|
|
3
5
|
|
|
4
|
-
interface PreventFlashOptions {
|
|
5
|
-
storageKey?: string;
|
|
6
|
-
darkClass?: string;
|
|
7
|
-
attribute?: 'class' | 'data-theme';
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Retorna um snippet JS (string) para inserir inline no `<head>` do HTML
|
|
11
|
-
* **antes** dos bundles React. Lê localStorage + system pref e aplica
|
|
12
|
-
* imediatamente a classe `dark` em `<html>`, evitando flash branco no
|
|
13
|
-
* carregamento (FOUC).
|
|
14
|
-
*
|
|
15
|
-
* Uso (Blade):
|
|
16
|
-
* <script>{!! \Arqel\Theme\preventFlashScript() !!}</script>
|
|
17
|
-
*
|
|
18
|
-
* Uso direto:
|
|
19
|
-
* <script dangerouslySetInnerHTML={{ __html: preventFlashScript() }} />
|
|
20
|
-
*
|
|
21
|
-
* O snippet é IIFE que falha silenciosamente — nenhuma corrupção de página
|
|
22
|
-
* mesmo se localStorage estiver bloqueado.
|
|
23
|
-
*/
|
|
24
|
-
declare function preventFlashScript(options?: PreventFlashOptions): string;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Theme preference armazenada em localStorage / Context.
|
|
28
|
-
*
|
|
29
|
-
* - `light` — força modo claro
|
|
30
|
-
* - `dark` — força modo escuro
|
|
31
|
-
* - `system` — segue `prefers-color-scheme` do SO
|
|
32
|
-
*/
|
|
33
|
-
type Theme = 'light' | 'dark' | 'system';
|
|
34
|
-
/**
|
|
35
|
-
* Tema efetivamente aplicado ao DOM (já resolvido a partir de `Theme`).
|
|
36
|
-
*/
|
|
37
|
-
type ResolvedTheme = 'light' | 'dark';
|
|
38
|
-
interface ThemeContextValue {
|
|
39
|
-
/** Preferência do utilizador (incluindo `system`). */
|
|
40
|
-
theme: Theme;
|
|
41
|
-
/** Tema concreto aplicado no `<html>` (sempre `light` ou `dark`). */
|
|
42
|
-
resolvedTheme: ResolvedTheme;
|
|
43
|
-
/** Atualiza preferência (persiste em localStorage). */
|
|
44
|
-
setTheme: (theme: Theme) => void;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
6
|
declare const DEFAULT_STORAGE_KEY = "arqel-theme";
|
|
48
7
|
declare function isTheme(value: unknown): value is Theme;
|
|
49
8
|
/**
|
|
50
9
|
* Lê preferência do localStorage. SSR-safe: retorna `null` no servidor.
|
|
10
|
+
*
|
|
11
|
+
* @deprecated Após a unificação de contexto (issue #236), o `ThemeProvider`
|
|
12
|
+
* passou a gerenciar a leitura do `localStorage` internamente. Ler a chave
|
|
13
|
+
* por fora não reflete o estado do provider. Prefira `useTheme().theme`.
|
|
51
14
|
*/
|
|
52
15
|
declare function readStoredTheme(key?: string): Theme | null;
|
|
53
16
|
/**
|
|
54
17
|
* Persiste preferência em localStorage. Silencioso em erro.
|
|
18
|
+
*
|
|
19
|
+
* @deprecated Após a unificação de contexto (issue #236), o `ThemeProvider`
|
|
20
|
+
* é a única fonte de escrita do `localStorage`. Chamar isto em runtime grava
|
|
21
|
+
* a chave mas NÃO atualiza o estado do provider. Prefira `useTheme().setTheme`.
|
|
55
22
|
*/
|
|
56
23
|
declare function writeStoredTheme(theme: Theme, key?: string): void;
|
|
57
24
|
/**
|
|
@@ -59,19 +26,17 @@ declare function writeStoredTheme(theme: Theme, key?: string): void;
|
|
|
59
26
|
*/
|
|
60
27
|
declare function getSystemTheme(): 'light' | 'dark';
|
|
61
28
|
|
|
62
|
-
declare const ThemeContext: react.Context<ThemeContextValue | null>;
|
|
63
29
|
interface ThemeProviderProps {
|
|
64
|
-
children: ReactNode;
|
|
30
|
+
children: react.ReactNode;
|
|
65
31
|
/** Tema padrão quando nada está armazenado. Default: `system`. */
|
|
66
32
|
defaultTheme?: Theme;
|
|
67
33
|
/** Chave usada em localStorage. Default: `arqel-theme`. */
|
|
68
34
|
storageKey?: string;
|
|
69
35
|
/** Classe aplicada no `<html>` quando dark. Default: `dark`. */
|
|
70
36
|
darkClass?: string;
|
|
71
|
-
/** Atributo opcional usado em vez de classe (`data-theme`).
|
|
37
|
+
/** Atributo opcional usado em vez de classe (`data-theme`). */
|
|
72
38
|
attribute?: 'class' | 'data-theme';
|
|
73
39
|
}
|
|
74
|
-
declare function ThemeProvider({ children, defaultTheme, storageKey, darkClass, attribute, }: ThemeProviderProps): ReactNode;
|
|
75
40
|
|
|
76
41
|
interface ThemeToggleProps {
|
|
77
42
|
className?: string;
|
|
@@ -83,12 +48,4 @@ interface ThemeToggleProps {
|
|
|
83
48
|
*/
|
|
84
49
|
declare function ThemeToggle({ className }?: ThemeToggleProps): ReactNode;
|
|
85
50
|
|
|
86
|
-
|
|
87
|
-
* Hook para acessar o tema atual e atualizá-lo.
|
|
88
|
-
*
|
|
89
|
-
* Lança erro se chamado fora de `<ThemeProvider>` (fail-fast — evita
|
|
90
|
-
* componentes silenciosamente mostrando defaults errados).
|
|
91
|
-
*/
|
|
92
|
-
declare function useTheme(): ThemeContextValue;
|
|
93
|
-
|
|
94
|
-
export { DEFAULT_STORAGE_KEY, type PreventFlashOptions, type ResolvedTheme, type Theme, ThemeContext, type ThemeContextValue, ThemeProvider, type ThemeProviderProps, ThemeToggle, type ThemeToggleProps, getSystemTheme, isTheme, preventFlashScript, readStoredTheme, useTheme, writeStoredTheme };
|
|
51
|
+
export { DEFAULT_STORAGE_KEY, type ThemeProviderProps, ThemeToggle, type ThemeToggleProps, getSystemTheme, isTheme, readStoredTheme, writeStoredTheme };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useTheme } from '@arqel-dev/react/providers';
|
|
2
|
+
export { ThemeContext, ThemeProvider, preventFlashScript, useTheme } from '@arqel-dev/react/providers';
|
|
2
3
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
4
|
|
|
5
|
+
// src/preventFlash.ts
|
|
6
|
+
|
|
4
7
|
// src/storage.ts
|
|
5
8
|
var DEFAULT_STORAGE_KEY = "arqel-theme";
|
|
6
9
|
var VALID_THEMES = ["light", "dark", "system"];
|
|
@@ -29,97 +32,6 @@ function getSystemTheme() {
|
|
|
29
32
|
}
|
|
30
33
|
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
31
34
|
}
|
|
32
|
-
|
|
33
|
-
// src/preventFlash.ts
|
|
34
|
-
function preventFlashScript(options = {}) {
|
|
35
|
-
const { storageKey = DEFAULT_STORAGE_KEY, darkClass = "dark", attribute = "class" } = options;
|
|
36
|
-
const k = JSON.stringify(storageKey);
|
|
37
|
-
const c = JSON.stringify(darkClass);
|
|
38
|
-
const a = JSON.stringify(attribute);
|
|
39
|
-
return [
|
|
40
|
-
"(function(){try{",
|
|
41
|
-
"var k=",
|
|
42
|
-
k,
|
|
43
|
-
",c=",
|
|
44
|
-
c,
|
|
45
|
-
",a=",
|
|
46
|
-
a,
|
|
47
|
-
";",
|
|
48
|
-
"var t=null;try{t=localStorage.getItem(k);}catch(e){}",
|
|
49
|
-
'if(t!=="light"&&t!=="dark"&&t!=="system")t="system";',
|
|
50
|
-
"var r=t;",
|
|
51
|
-
'if(t==="system"){',
|
|
52
|
-
'r=(window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches)?"dark":"light";',
|
|
53
|
-
"}",
|
|
54
|
-
"var el=document.documentElement;",
|
|
55
|
-
'if(a==="class"){if(r==="dark")el.classList.add(c);else el.classList.remove(c);}',
|
|
56
|
-
'else{el.setAttribute("data-theme",r);}',
|
|
57
|
-
"el.style.colorScheme=r;",
|
|
58
|
-
"}catch(e){}})();"
|
|
59
|
-
].join("");
|
|
60
|
-
}
|
|
61
|
-
var ThemeContext = createContext(null);
|
|
62
|
-
function resolve(theme) {
|
|
63
|
-
return theme === "system" ? getSystemTheme() : theme;
|
|
64
|
-
}
|
|
65
|
-
function applyToDom(resolved, darkClass, attribute) {
|
|
66
|
-
if (typeof document === "undefined") return;
|
|
67
|
-
const root = document.documentElement;
|
|
68
|
-
if (attribute === "class") {
|
|
69
|
-
if (resolved === "dark") root.classList.add(darkClass);
|
|
70
|
-
else root.classList.remove(darkClass);
|
|
71
|
-
} else {
|
|
72
|
-
root.setAttribute("data-theme", resolved);
|
|
73
|
-
}
|
|
74
|
-
root.style.colorScheme = resolved;
|
|
75
|
-
}
|
|
76
|
-
function ThemeProvider({
|
|
77
|
-
children,
|
|
78
|
-
defaultTheme = "system",
|
|
79
|
-
storageKey = DEFAULT_STORAGE_KEY,
|
|
80
|
-
darkClass = "dark",
|
|
81
|
-
attribute = "class"
|
|
82
|
-
}) {
|
|
83
|
-
const [theme, setThemeState] = useState(() => readStoredTheme(storageKey) ?? defaultTheme);
|
|
84
|
-
const [resolvedTheme, setResolvedTheme] = useState(() => resolve(theme));
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
const next = resolve(theme);
|
|
87
|
-
setResolvedTheme(next);
|
|
88
|
-
applyToDom(next, darkClass, attribute);
|
|
89
|
-
if (theme !== "system" || typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
93
|
-
const onChange = () => {
|
|
94
|
-
const r = media.matches ? "dark" : "light";
|
|
95
|
-
setResolvedTheme(r);
|
|
96
|
-
applyToDom(r, darkClass, attribute);
|
|
97
|
-
};
|
|
98
|
-
media.addEventListener("change", onChange);
|
|
99
|
-
return () => {
|
|
100
|
-
media.removeEventListener("change", onChange);
|
|
101
|
-
};
|
|
102
|
-
}, [theme, darkClass, attribute]);
|
|
103
|
-
const setTheme = useCallback(
|
|
104
|
-
(t) => {
|
|
105
|
-
setThemeState(t);
|
|
106
|
-
writeStoredTheme(t, storageKey);
|
|
107
|
-
},
|
|
108
|
-
[storageKey]
|
|
109
|
-
);
|
|
110
|
-
const value = useMemo(
|
|
111
|
-
() => ({ theme, resolvedTheme, setTheme }),
|
|
112
|
-
[theme, resolvedTheme, setTheme]
|
|
113
|
-
);
|
|
114
|
-
return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
|
|
115
|
-
}
|
|
116
|
-
function useTheme() {
|
|
117
|
-
const ctx = useContext(ThemeContext);
|
|
118
|
-
if (!ctx) {
|
|
119
|
-
throw new Error("[arqel-dev/theme] useTheme() deve ser usado dentro de <ThemeProvider>.");
|
|
120
|
-
}
|
|
121
|
-
return ctx;
|
|
122
|
-
}
|
|
123
35
|
var CYCLE = ["system", "light", "dark"];
|
|
124
36
|
var LABELS = {
|
|
125
37
|
system: "Tema: sistema (clique para claro)",
|
|
@@ -204,6 +116,6 @@ function ThemeToggle({ className } = {}) {
|
|
|
204
116
|
);
|
|
205
117
|
}
|
|
206
118
|
|
|
207
|
-
export { DEFAULT_STORAGE_KEY,
|
|
119
|
+
export { DEFAULT_STORAGE_KEY, ThemeToggle, getSystemTheme, isTheme, readStoredTheme, writeStoredTheme };
|
|
208
120
|
//# sourceMappingURL=index.js.map
|
|
209
121
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/storage.ts","../src/preventFlash.ts","../src/ThemeProvider.tsx","../src/useTheme.ts","../src/ThemeToggle.tsx"],"names":["jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { Theme } from './types';\n\nexport const DEFAULT_STORAGE_KEY = 'arqel-theme';\n\nconst VALID_THEMES: readonly Theme[] = ['light', 'dark', 'system'];\n\nexport function isTheme(value: unknown): value is Theme {\n return typeof value === 'string' && (VALID_THEMES as readonly string[]).includes(value);\n}\n\n/**\n * Lê preferência do localStorage. SSR-safe: retorna `null` no servidor.\n */\nexport function readStoredTheme(key: string = DEFAULT_STORAGE_KEY): Theme | null {\n if (typeof window === 'undefined') return null;\n try {\n const raw = window.localStorage.getItem(key);\n return isTheme(raw) ? raw : null;\n } catch {\n // localStorage pode estar bloqueado (Safari private, iframes, etc).\n return null;\n }\n}\n\n/**\n * Persiste preferência em localStorage. Silencioso em erro.\n */\nexport function writeStoredTheme(theme: Theme, key: string = DEFAULT_STORAGE_KEY): void {\n if (typeof window === 'undefined') return;\n try {\n window.localStorage.setItem(key, theme);\n } catch {\n // ignore\n }\n}\n\n/**\n * Detecta system preference. SSR-safe: retorna `light` no servidor.\n */\nexport function getSystemTheme(): 'light' | 'dark' {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n return 'light';\n }\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n}\n","import { DEFAULT_STORAGE_KEY } from './storage';\n\nexport interface PreventFlashOptions {\n storageKey?: string;\n darkClass?: string;\n attribute?: 'class' | 'data-theme';\n}\n\n/**\n * Retorna um snippet JS (string) para inserir inline no `<head>` do HTML\n * **antes** dos bundles React. Lê localStorage + system pref e aplica\n * imediatamente a classe `dark` em `<html>`, evitando flash branco no\n * carregamento (FOUC).\n *\n * Uso (Blade):\n * <script>{!! \\Arqel\\Theme\\preventFlashScript() !!}</script>\n *\n * Uso direto:\n * <script dangerouslySetInnerHTML={{ __html: preventFlashScript() }} />\n *\n * O snippet é IIFE que falha silenciosamente — nenhuma corrupção de página\n * mesmo se localStorage estiver bloqueado.\n */\nexport function preventFlashScript(options: PreventFlashOptions = {}): string {\n const { storageKey = DEFAULT_STORAGE_KEY, darkClass = 'dark', attribute = 'class' } = options;\n\n // JSON.stringify garante escape correto de aspas e quebras.\n const k = JSON.stringify(storageKey);\n const c = JSON.stringify(darkClass);\n const a = JSON.stringify(attribute);\n\n return [\n '(function(){try{',\n 'var k=',\n k,\n ',c=',\n c,\n ',a=',\n a,\n ';',\n 'var t=null;try{t=localStorage.getItem(k);}catch(e){}',\n 'if(t!==\"light\"&&t!==\"dark\"&&t!==\"system\")t=\"system\";',\n 'var r=t;',\n 'if(t===\"system\"){',\n 'r=(window.matchMedia&&window.matchMedia(\"(prefers-color-scheme: dark)\").matches)?\"dark\":\"light\";',\n '}',\n 'var el=document.documentElement;',\n 'if(a===\"class\"){if(r===\"dark\")el.classList.add(c);else el.classList.remove(c);}',\n 'else{el.setAttribute(\"data-theme\",r);}',\n 'el.style.colorScheme=r;',\n '}catch(e){}})();',\n ].join('');\n}\n","import { createContext, type ReactNode, useCallback, useEffect, useMemo, useState } from 'react';\n\nimport { DEFAULT_STORAGE_KEY, getSystemTheme, readStoredTheme, writeStoredTheme } from './storage';\nimport type { ResolvedTheme, Theme, ThemeContextValue } from './types';\n\nexport const ThemeContext = createContext<ThemeContextValue | null>(null);\n\nexport interface ThemeProviderProps {\n children: ReactNode;\n /** Tema padrão quando nada está armazenado. Default: `system`. */\n defaultTheme?: Theme;\n /** Chave usada em localStorage. Default: `arqel-theme`. */\n storageKey?: string;\n /** Classe aplicada no `<html>` quando dark. Default: `dark`. */\n darkClass?: string;\n /** Atributo opcional usado em vez de classe (`data-theme`). Útil para integrações. */\n attribute?: 'class' | 'data-theme';\n}\n\nfunction resolve(theme: Theme): ResolvedTheme {\n return theme === 'system' ? getSystemTheme() : theme;\n}\n\nfunction applyToDom(\n resolved: ResolvedTheme,\n darkClass: string,\n attribute: 'class' | 'data-theme',\n): void {\n if (typeof document === 'undefined') return;\n const root = document.documentElement;\n if (attribute === 'class') {\n if (resolved === 'dark') root.classList.add(darkClass);\n else root.classList.remove(darkClass);\n } else {\n root.setAttribute('data-theme', resolved);\n }\n // colorScheme ajuda nativos (scrollbars, form controls)\n root.style.colorScheme = resolved;\n}\n\nexport function ThemeProvider({\n children,\n defaultTheme = 'system',\n storageKey = DEFAULT_STORAGE_KEY,\n darkClass = 'dark',\n attribute = 'class',\n}: ThemeProviderProps): ReactNode {\n // Inicialização lazy — lê localStorage 1x.\n const [theme, setThemeState] = useState<Theme>(() => readStoredTheme(storageKey) ?? defaultTheme);\n const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>(() => resolve(theme));\n\n // Aplica no DOM e mantém em sync com matchMedia se theme === 'system'.\n useEffect(() => {\n const next = resolve(theme);\n setResolvedTheme(next);\n applyToDom(next, darkClass, attribute);\n\n if (\n theme !== 'system' ||\n typeof window === 'undefined' ||\n typeof window.matchMedia !== 'function'\n ) {\n return;\n }\n\n const media = window.matchMedia('(prefers-color-scheme: dark)');\n const onChange = (): void => {\n const r: ResolvedTheme = media.matches ? 'dark' : 'light';\n setResolvedTheme(r);\n applyToDom(r, darkClass, attribute);\n };\n media.addEventListener('change', onChange);\n return () => {\n media.removeEventListener('change', onChange);\n };\n }, [theme, darkClass, attribute]);\n\n const setTheme = useCallback(\n (t: Theme) => {\n setThemeState(t);\n writeStoredTheme(t, storageKey);\n },\n [storageKey],\n );\n\n const value = useMemo<ThemeContextValue>(\n () => ({ theme, resolvedTheme, setTheme }),\n [theme, resolvedTheme, setTheme],\n );\n\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;\n}\n","import { useContext } from 'react';\n\nimport { ThemeContext } from './ThemeProvider';\nimport type { ThemeContextValue } from './types';\n\n/**\n * Hook para acessar o tema atual e atualizá-lo.\n *\n * Lança erro se chamado fora de `<ThemeProvider>` (fail-fast — evita\n * componentes silenciosamente mostrando defaults errados).\n */\nexport function useTheme(): ThemeContextValue {\n const ctx = useContext(ThemeContext);\n if (!ctx) {\n throw new Error('[arqel-dev/theme] useTheme() deve ser usado dentro de <ThemeProvider>.');\n }\n return ctx;\n}\n","import type { ReactNode } from 'react';\nimport type { Theme } from './types';\nimport { useTheme } from './useTheme';\n\nconst CYCLE: readonly Theme[] = ['system', 'light', 'dark'];\n\nconst LABELS: Record<Theme, string> = {\n system: 'Tema: sistema (clique para claro)',\n light: 'Tema: claro (clique para escuro)',\n dark: 'Tema: escuro (clique para sistema)',\n};\n\nfunction nextTheme(current: Theme): Theme {\n const idx = CYCLE.indexOf(current);\n return CYCLE[(idx + 1) % CYCLE.length] ?? 'system';\n}\n\nfunction SunIcon(): ReactNode {\n return (\n <svg\n aria-hidden=\"true\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"4\" />\n <path d=\"M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41\" />\n </svg>\n );\n}\n\nfunction MoonIcon(): ReactNode {\n return (\n <svg\n aria-hidden=\"true\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\" />\n </svg>\n );\n}\n\nfunction MonitorIcon(): ReactNode {\n return (\n <svg\n aria-hidden=\"true\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <rect x=\"2\" y=\"3\" width=\"20\" height=\"14\" rx=\"2\" />\n <path d=\"M8 21h8M12 17v4\" />\n </svg>\n );\n}\n\nexport interface ThemeToggleProps {\n className?: string;\n}\n\n/**\n * Botão que cicla entre `system → light → dark → system`.\n * Mostra ícone correspondente ao **tema atualmente selecionado**\n * (não ao próximo) e usa `aria-label` descritivo para acessibilidade.\n */\nexport function ThemeToggle({ className }: ThemeToggleProps = {}): ReactNode {\n const { theme, setTheme } = useTheme();\n\n const icon = theme === 'light' ? <SunIcon /> : theme === 'dark' ? <MoonIcon /> : <MonitorIcon />;\n\n return (\n <button\n type=\"button\"\n onClick={() => setTheme(nextTheme(theme))}\n aria-label={LABELS[theme]}\n title={LABELS[theme]}\n data-theme-current={theme}\n className={className}\n >\n {icon}\n </button>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/storage.ts","../src/ThemeToggle.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Theme } from './types';\n\nexport const DEFAULT_STORAGE_KEY = 'arqel-theme';\n\nconst VALID_THEMES: readonly Theme[] = ['light', 'dark', 'system'];\n\nexport function isTheme(value: unknown): value is Theme {\n return typeof value === 'string' && (VALID_THEMES as readonly string[]).includes(value);\n}\n\n/**\n * Lê preferência do localStorage. SSR-safe: retorna `null` no servidor.\n *\n * @deprecated Após a unificação de contexto (issue #236), o `ThemeProvider`\n * passou a gerenciar a leitura do `localStorage` internamente. Ler a chave\n * por fora não reflete o estado do provider. Prefira `useTheme().theme`.\n */\nexport function readStoredTheme(key: string = DEFAULT_STORAGE_KEY): Theme | null {\n if (typeof window === 'undefined') return null;\n try {\n const raw = window.localStorage.getItem(key);\n return isTheme(raw) ? raw : null;\n } catch {\n // localStorage pode estar bloqueado (Safari private, iframes, etc).\n return null;\n }\n}\n\n/**\n * Persiste preferência em localStorage. Silencioso em erro.\n *\n * @deprecated Após a unificação de contexto (issue #236), o `ThemeProvider`\n * é a única fonte de escrita do `localStorage`. Chamar isto em runtime grava\n * a chave mas NÃO atualiza o estado do provider. Prefira `useTheme().setTheme`.\n */\nexport function writeStoredTheme(theme: Theme, key: string = DEFAULT_STORAGE_KEY): void {\n if (typeof window === 'undefined') return;\n try {\n window.localStorage.setItem(key, theme);\n } catch {\n // ignore\n }\n}\n\n/**\n * Detecta system preference. SSR-safe: retorna `light` no servidor.\n */\nexport function getSystemTheme(): 'light' | 'dark' {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n return 'light';\n }\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n}\n","import type { ReactNode } from 'react';\nimport type { Theme } from './types';\nimport { useTheme } from './useTheme';\n\nconst CYCLE: readonly Theme[] = ['system', 'light', 'dark'];\n\nconst LABELS: Record<Theme, string> = {\n system: 'Tema: sistema (clique para claro)',\n light: 'Tema: claro (clique para escuro)',\n dark: 'Tema: escuro (clique para sistema)',\n};\n\nfunction nextTheme(current: Theme): Theme {\n const idx = CYCLE.indexOf(current);\n return CYCLE[(idx + 1) % CYCLE.length] ?? 'system';\n}\n\nfunction SunIcon(): ReactNode {\n return (\n <svg\n aria-hidden=\"true\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"4\" />\n <path d=\"M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41\" />\n </svg>\n );\n}\n\nfunction MoonIcon(): ReactNode {\n return (\n <svg\n aria-hidden=\"true\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\" />\n </svg>\n );\n}\n\nfunction MonitorIcon(): ReactNode {\n return (\n <svg\n aria-hidden=\"true\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <rect x=\"2\" y=\"3\" width=\"20\" height=\"14\" rx=\"2\" />\n <path d=\"M8 21h8M12 17v4\" />\n </svg>\n );\n}\n\nexport interface ThemeToggleProps {\n className?: string;\n}\n\n/**\n * Botão que cicla entre `system → light → dark → system`.\n * Mostra ícone correspondente ao **tema atualmente selecionado**\n * (não ao próximo) e usa `aria-label` descritivo para acessibilidade.\n */\nexport function ThemeToggle({ className }: ThemeToggleProps = {}): ReactNode {\n const { theme, setTheme } = useTheme();\n\n const icon = theme === 'light' ? <SunIcon /> : theme === 'dark' ? <MoonIcon /> : <MonitorIcon />;\n\n return (\n <button\n type=\"button\"\n onClick={() => setTheme(nextTheme(theme))}\n aria-label={LABELS[theme]}\n title={LABELS[theme]}\n data-theme-current={theme}\n className={className}\n >\n {icon}\n </button>\n );\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arqel-dev/theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Tokens semânticos + dark-mode + ThemeProvider/ThemeToggle para painéis Arqel.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://arqel.dev",
|
|
@@ -34,19 +34,22 @@
|
|
|
34
34
|
"README.md",
|
|
35
35
|
"SKILL.md"
|
|
36
36
|
],
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@arqel-dev/react": "0.14.0"
|
|
39
|
+
},
|
|
37
40
|
"peerDependencies": {
|
|
38
|
-
"react": "^19.
|
|
41
|
+
"react": "^19.2.0"
|
|
39
42
|
},
|
|
40
43
|
"devDependencies": {
|
|
41
44
|
"@testing-library/react": "^16.1.0",
|
|
42
45
|
"@types/react": "^19.0.0",
|
|
43
46
|
"@types/react-dom": "^19.0.0",
|
|
44
47
|
"jsdom": "^25.0.1",
|
|
45
|
-
"react": "^19.
|
|
46
|
-
"react-dom": "^19.
|
|
48
|
+
"react": "^19.2.0",
|
|
49
|
+
"react-dom": "^19.2.0",
|
|
47
50
|
"tsup": "^8.5.0",
|
|
48
51
|
"typescript": "^5.6.3",
|
|
49
|
-
"vitest": "^
|
|
52
|
+
"vitest": "^4.1.0"
|
|
50
53
|
},
|
|
51
54
|
"publishConfig": {
|
|
52
55
|
"access": "public"
|