@noxlovette/material 0.1.11 → 0.1.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/dist/components/buttons/ButtonIcon.svelte +3 -3
- package/dist/components/containers/app/App.svelte +5 -10
- package/dist/components/containers/app/types.d.ts +0 -14
- package/dist/components/nav/rail/Rail.svelte +7 -0
- package/dist/components/nav/rail/types.d.ts +2 -0
- package/dist/utils/Theme.svelte +26 -51
- package/dist/utils/ThemeScript.svelte +17 -0
- package/dist/utils/ThemeScript.svelte.d.ts +27 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/theme.svelte.d.ts +11 -0
- package/dist/utils/theme.svelte.js +16 -0
- package/package.json +1 -1
|
@@ -49,13 +49,13 @@ Icon buttons help people take minor actions and are often used in app bars.
|
|
|
49
49
|
</script>
|
|
50
50
|
|
|
51
51
|
<Tooltip {triggerClass} variant="snack" supportingText={tooltipContent}>
|
|
52
|
-
{#snippet trigger(triggerAttrs)}
|
|
52
|
+
{#snippet trigger({ class: triggerClass_, ...triggerAttrs })}
|
|
53
53
|
{#if variation === 'toggle'}
|
|
54
54
|
<Toggle.Root
|
|
55
55
|
bind:pressed
|
|
56
56
|
{onPressedChange}
|
|
57
57
|
{disabled}
|
|
58
|
-
class={btnCls}
|
|
58
|
+
class={clsx(btnCls, triggerClass_ as string)}
|
|
59
59
|
data-cy="m3-button-icon"
|
|
60
60
|
{...restProps as ToggleRootProps}
|
|
61
61
|
{...triggerAttrs}
|
|
@@ -71,7 +71,7 @@ Icon buttons help people take minor actions and are often used in app bars.
|
|
|
71
71
|
<Button.Root
|
|
72
72
|
{disabled}
|
|
73
73
|
{formaction}
|
|
74
|
-
class={btnCls}
|
|
74
|
+
class={clsx(btnCls, triggerClass_ as string)}
|
|
75
75
|
data-cy="m3-button-icon"
|
|
76
76
|
{...restProps as ButtonRootProps}
|
|
77
77
|
{...triggerAttrs}
|
|
@@ -9,20 +9,15 @@ It manages the Material 3 Dynamic Theme, dark mode, and icon providers.
|
|
|
9
9
|
import { app } from './theme';
|
|
10
10
|
import type { AppProps } from './types';
|
|
11
11
|
import { ModeWatcher } from 'mode-watcher';
|
|
12
|
+
import ThemeScript from '../../../utils/ThemeScript.svelte';
|
|
12
13
|
|
|
13
|
-
let {
|
|
14
|
-
children,
|
|
15
|
-
class: className,
|
|
16
|
-
iconProviderProps = {},
|
|
17
|
-
themeConfig,
|
|
18
|
-
isDark,
|
|
19
|
-
...restProps
|
|
20
|
-
}: AppProps = $props();
|
|
14
|
+
let { children, class: className, iconProviderProps = {}, ...restProps }: AppProps = $props();
|
|
21
15
|
|
|
22
|
-
const baseCls = $derived(app({ class:
|
|
16
|
+
const baseCls = $derived(app({ class: className }));
|
|
23
17
|
</script>
|
|
24
18
|
|
|
25
|
-
<
|
|
19
|
+
<ThemeScript />
|
|
20
|
+
<Theme />
|
|
26
21
|
<MaterialSymbolsProvider {...iconProviderProps} />
|
|
27
22
|
<ModeWatcher />
|
|
28
23
|
<Tooltip.Provider>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { MaterialSymbolsProviderProps } from '../../../utils/index.js';
|
|
2
|
-
import type { ThemeConfig } from '../../../utils/theme.svelte.js';
|
|
3
2
|
import type { DivAttrs } from '../../../utils/types.js';
|
|
4
3
|
import type { Snippet } from 'svelte';
|
|
5
4
|
export type AppProps = DivAttrs & {
|
|
@@ -9,17 +8,4 @@ export type AppProps = DivAttrs & {
|
|
|
9
8
|
class?: string;
|
|
10
9
|
/** Props to pass to the material symbols provider */
|
|
11
10
|
iconProviderProps?: Partial<MaterialSymbolsProviderProps>;
|
|
12
|
-
/**
|
|
13
|
-
* Optional CSS generated on the server to prevent FOUC.
|
|
14
|
-
*/
|
|
15
|
-
ssrThemeCSS?: string;
|
|
16
|
-
/**
|
|
17
|
-
* Optional theme configuration passed from the server.
|
|
18
|
-
* If provided, the App component will generate the theme CSS automatically.
|
|
19
|
-
*/
|
|
20
|
-
themeConfig?: ThemeConfig;
|
|
21
|
-
/**
|
|
22
|
-
* Optional dark mode state passed from the server to prevent flashing.
|
|
23
|
-
*/
|
|
24
|
-
isDark?: boolean;
|
|
25
11
|
};
|
|
@@ -19,6 +19,7 @@ Navigation rails provide access to primary destinations in apps when using table
|
|
|
19
19
|
rounded = false,
|
|
20
20
|
expandable = true,
|
|
21
21
|
fab,
|
|
22
|
+
railFooter,
|
|
22
23
|
collapsed = $bindable(true),
|
|
23
24
|
anchor = 'viewport',
|
|
24
25
|
railTop = 0,
|
|
@@ -60,6 +61,12 @@ Navigation rails provide access to primary destinations in apps when using table
|
|
|
60
61
|
{@render children?.()}
|
|
61
62
|
</NavigationMenu.List>
|
|
62
63
|
</NavigationMenu.Root>
|
|
64
|
+
|
|
65
|
+
{#if railFooter}
|
|
66
|
+
<div class="mt-auto flex flex-col items-center pb-2">
|
|
67
|
+
{@render railFooter()}
|
|
68
|
+
</div>
|
|
69
|
+
{/if}
|
|
63
70
|
</div>
|
|
64
71
|
|
|
65
72
|
<style>
|
|
@@ -35,6 +35,8 @@ export type RailProps = RailVariants & HTMLAttributes<HTMLDivElement> & {
|
|
|
35
35
|
expandable?: boolean;
|
|
36
36
|
/** An optional Floating Action Button snippet. */
|
|
37
37
|
fab?: Snippet;
|
|
38
|
+
/** An optional snippet rendered at the bottom of the rail (e.g. avatar/settings). */
|
|
39
|
+
railFooter?: Snippet;
|
|
38
40
|
/** Whether the rail is currently collapsed. */
|
|
39
41
|
collapsed?: boolean;
|
|
40
42
|
/** Where the rail should be anchored. */
|
package/dist/utils/Theme.svelte
CHANGED
|
@@ -1,47 +1,23 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { generateThemeCSS, isDarkScheme, themeState,
|
|
2
|
+
import { generateThemeCSS, isDarkScheme, themeState, STORAGE_KEY } from './theme.svelte.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Initial configuration for the theme.
|
|
7
|
-
* Useful for SSR when reading from cookies.
|
|
8
|
-
*/
|
|
9
|
-
config?: Partial<ThemeConfig>;
|
|
10
|
-
/**
|
|
11
|
-
* Optional dark mode state passed from the server to prevent flashing.
|
|
12
|
-
* If provided, it overrides the auto-detection.
|
|
13
|
-
*/
|
|
14
|
-
isDark?: boolean;
|
|
15
|
-
/**
|
|
16
|
-
* Whether to persist changes to localStorage and cookies.
|
|
17
|
-
* Defaults to true.
|
|
18
|
-
*/
|
|
19
|
-
persist?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
let { config, isDark: isDarkProp, persist = true }: Props = $props();
|
|
23
|
-
|
|
24
|
-
// Sync config to global state
|
|
25
|
-
// We use an effect to keep it in sync if the prop changes,
|
|
26
|
-
// but also run it during initialization for SSR.
|
|
27
|
-
function syncConfig(c?: Partial<ThemeConfig>) {
|
|
28
|
-
if (!c) return;
|
|
29
|
-
if (c.sourceColor) themeState.sourceColor = c.sourceColor;
|
|
30
|
-
if (c.scheme) themeState.scheme = c.scheme;
|
|
31
|
-
if (c.contrast) themeState.contrast = c.contrast;
|
|
32
|
-
if (c.variant) themeState.variant = c.variant;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Run immediately for SSR
|
|
36
|
-
syncConfig(config);
|
|
37
|
-
|
|
38
|
-
// Keep in sync if prop updates on client
|
|
4
|
+
// Restore from localStorage on mount
|
|
39
5
|
$effect(() => {
|
|
40
|
-
|
|
6
|
+
const saved = localStorage.getItem(STORAGE_KEY);
|
|
7
|
+
if (!saved) return;
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(saved);
|
|
10
|
+
if (parsed.sourceColor) themeState.sourceColor = parsed.sourceColor;
|
|
11
|
+
if (parsed.scheme) themeState.scheme = parsed.scheme;
|
|
12
|
+
if (parsed.contrast) themeState.contrast = parsed.contrast;
|
|
13
|
+
if (parsed.variant) themeState.variant = parsed.variant;
|
|
14
|
+
} catch {
|
|
15
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
16
|
+
}
|
|
41
17
|
});
|
|
42
18
|
|
|
43
|
-
//
|
|
44
|
-
let finalIsDark = $state(
|
|
19
|
+
// Track dark mode, including system preference changes
|
|
20
|
+
let finalIsDark = $state(isDarkScheme(themeState.scheme));
|
|
45
21
|
|
|
46
22
|
$effect(() => {
|
|
47
23
|
if (themeState.scheme !== 'system') {
|
|
@@ -61,21 +37,20 @@
|
|
|
61
37
|
|
|
62
38
|
const themeStyles = $derived(generateThemeCSS(themeState, finalIsDark));
|
|
63
39
|
|
|
64
|
-
//
|
|
40
|
+
// Persist to localStorage on any change
|
|
65
41
|
$effect(() => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
localStorage.setItem('ogonek-m3-theme-config', configStr);
|
|
42
|
+
localStorage.setItem(
|
|
43
|
+
STORAGE_KEY,
|
|
44
|
+
JSON.stringify({
|
|
45
|
+
sourceColor: themeState.sourceColor,
|
|
46
|
+
scheme: themeState.scheme,
|
|
47
|
+
contrast: themeState.contrast,
|
|
48
|
+
variant: themeState.variant
|
|
49
|
+
})
|
|
50
|
+
);
|
|
76
51
|
});
|
|
77
52
|
|
|
78
|
-
//
|
|
53
|
+
// Sync dark class on <html>
|
|
79
54
|
$effect(() => {
|
|
80
55
|
if (finalIsDark) {
|
|
81
56
|
document.documentElement.classList.add('dark');
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
Injects the theme initialisation script into <head> before first paint,
|
|
4
|
+
preventing a flash of unstyled/wrong-scheme content.
|
|
5
|
+
|
|
6
|
+
Add this to your root +layout.svelte (or any layout that wraps the whole app):
|
|
7
|
+
@example
|
|
8
|
+
<ThemeScript />
|
|
9
|
+
<App>...</App>
|
|
10
|
+
-->
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
import { THEME_INIT_SCRIPT } from './theme.svelte.js';
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<svelte:head>
|
|
16
|
+
{@html `<script>${THEME_INIT_SCRIPT}</script>`}
|
|
17
|
+
</svelte:head>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Injects the theme initialisation script into <head> before first paint,
|
|
16
|
+
* preventing a flash of unstyled/wrong-scheme content.
|
|
17
|
+
*
|
|
18
|
+
* Add this to your root +layout.svelte (or any layout that wraps the whole app):
|
|
19
|
+
* @example
|
|
20
|
+
* <ThemeScript />
|
|
21
|
+
* <App>...</App>
|
|
22
|
+
*/
|
|
23
|
+
declare const ThemeScript: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
24
|
+
[evt: string]: CustomEvent<any>;
|
|
25
|
+
}, {}, {}, string>;
|
|
26
|
+
type ThemeScript = InstanceType<typeof ThemeScript>;
|
|
27
|
+
export default ThemeScript;
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -5,3 +5,4 @@ export * from './ui.js';
|
|
|
5
5
|
export type { ColorScheme, ContrastMode, ThemeConfig, ThemeVariant } from './theme.svelte.js';
|
|
6
6
|
export { default as Layer } from './Layer.svelte';
|
|
7
7
|
export { default as Theme } from './Theme.svelte';
|
|
8
|
+
export { default as ThemeScript } from './ThemeScript.svelte';
|
package/dist/utils/index.js
CHANGED
|
@@ -8,6 +8,17 @@ export interface ThemeConfig {
|
|
|
8
8
|
variant: ThemeVariant;
|
|
9
9
|
}
|
|
10
10
|
export declare const DEFAULT_CONFIG: ThemeConfig;
|
|
11
|
+
export declare const STORAGE_KEY = "ogonek-m3-theme-config";
|
|
12
|
+
/**
|
|
13
|
+
* Inline script that reads the saved theme from localStorage and applies the
|
|
14
|
+
* `dark` class to <html> before first paint, preventing a flash of unstyled content.
|
|
15
|
+
*
|
|
16
|
+
* Inject this into your app.html <head> (or use the <ThemeScript /> component):
|
|
17
|
+
* @example
|
|
18
|
+
* // app.html
|
|
19
|
+
* <script>{THEME_INIT_SCRIPT}</script>
|
|
20
|
+
*/
|
|
21
|
+
export declare const THEME_INIT_SCRIPT = "try {\n const saved = JSON.parse(localStorage.getItem('ogonek-m3-theme-config') || '{}');\n const scheme = saved.scheme || 'system';\n const dark = scheme === 'dark' || (scheme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);\n if (dark) document.documentElement.classList.add('dark');\n} catch (e) {}";
|
|
11
22
|
/**
|
|
12
23
|
* Global reactive theme state.
|
|
13
24
|
* Changes to this state will automatically update the theme if the <Theme /> component is mounted.
|
|
@@ -13,6 +13,22 @@ const SCHEME_VARIANTS = {
|
|
|
13
13
|
'fruit-salad': SchemeFruitSalad,
|
|
14
14
|
rainbow: SchemeRainbow
|
|
15
15
|
};
|
|
16
|
+
export const STORAGE_KEY = 'ogonek-m3-theme-config';
|
|
17
|
+
/**
|
|
18
|
+
* Inline script that reads the saved theme from localStorage and applies the
|
|
19
|
+
* `dark` class to <html> before first paint, preventing a flash of unstyled content.
|
|
20
|
+
*
|
|
21
|
+
* Inject this into your app.html <head> (or use the <ThemeScript /> component):
|
|
22
|
+
* @example
|
|
23
|
+
* // app.html
|
|
24
|
+
* <script>{THEME_INIT_SCRIPT}</script>
|
|
25
|
+
*/
|
|
26
|
+
export const THEME_INIT_SCRIPT = `try {
|
|
27
|
+
const saved = JSON.parse(localStorage.getItem('${STORAGE_KEY}') || '{}');
|
|
28
|
+
const scheme = saved.scheme || 'system';
|
|
29
|
+
const dark = scheme === 'dark' || (scheme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
30
|
+
if (dark) document.documentElement.classList.add('dark');
|
|
31
|
+
} catch (e) {}`;
|
|
16
32
|
// --- State ---
|
|
17
33
|
/**
|
|
18
34
|
* Global reactive theme state.
|