@ship-it-ui/next 0.0.2 → 0.0.3
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/chunk-K3H6CPFL.js +30 -0
- package/dist/chunk-K3H6CPFL.js.map +1 -0
- package/dist/index.d.cts +2 -38
- package/dist/index.d.ts +2 -38
- package/dist/index.js +8 -20
- package/dist/index.js.map +1 -1
- package/dist/server-DE2Fqp12.d.cts +40 -0
- package/dist/server-DE2Fqp12.d.ts +40 -0
- package/dist/server.cjs +47 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +2 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +13 -0
- package/dist/server.js.map +1 -0
- package/package.json +9 -4
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// src/theme-cookie.ts
|
|
2
|
+
var THEME_COOKIE_NAME = "ship-it-theme";
|
|
3
|
+
var THEME_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
|
|
4
|
+
function parseThemeCookie(value) {
|
|
5
|
+
if (value === "dark" || value === "light") return value;
|
|
6
|
+
return void 0;
|
|
7
|
+
}
|
|
8
|
+
function getThemeFromCookies(cookieStore) {
|
|
9
|
+
return parseThemeCookie(cookieStore.get(THEME_COOKIE_NAME)?.value);
|
|
10
|
+
}
|
|
11
|
+
function writeThemeCookie(theme) {
|
|
12
|
+
if (typeof document === "undefined") return;
|
|
13
|
+
document.cookie = `${THEME_COOKIE_NAME}=${theme}; path=/; max-age=${THEME_COOKIE_MAX_AGE}; samesite=lax`;
|
|
14
|
+
}
|
|
15
|
+
function readThemeCookie() {
|
|
16
|
+
if (typeof document === "undefined") return void 0;
|
|
17
|
+
const match = document.cookie.split(";").map((part) => part.trim()).find((part) => part.startsWith(`${THEME_COOKIE_NAME}=`));
|
|
18
|
+
if (!match) return void 0;
|
|
19
|
+
return parseThemeCookie(match.slice(THEME_COOKIE_NAME.length + 1));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
THEME_COOKIE_NAME,
|
|
24
|
+
THEME_COOKIE_MAX_AGE,
|
|
25
|
+
parseThemeCookie,
|
|
26
|
+
getThemeFromCookies,
|
|
27
|
+
writeThemeCookie,
|
|
28
|
+
readThemeCookie
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=chunk-K3H6CPFL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/theme-cookie.ts"],"sourcesContent":["/**\n * Theme cookie helpers shared between the server-side `getThemeFromCookies`\n * and the client-side `ThemeBootstrap` / `ThemeToggle`. The cookie value is\n * the literal string `'dark'` or `'light'`; anything else falls back to\n * undefined and the consumer's default (typically dark) applies.\n */\n\nimport type { Theme } from '@ship-it-ui/ui';\n\nexport const THEME_COOKIE_NAME = 'ship-it-theme';\n\n/** One year. The cookie is non-sensitive, so a long lifetime is fine. */\nexport const THEME_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;\n\n/** Parse a raw cookie string value into a `Theme` or `undefined`. */\nexport function parseThemeCookie(value: string | null | undefined): Theme | undefined {\n if (value === 'dark' || value === 'light') return value;\n return undefined;\n}\n\n/**\n * Server helper for the App Router `cookies()` API. Pass `cookies()` (or any\n * object with `.get(name)` that returns `{ value: string }`) and receive the\n * stored theme.\n *\n * ```ts\n * import { cookies } from 'next/headers';\n * const theme = getThemeFromCookies(await cookies());\n * ```\n */\nexport interface CookieGetter {\n get(name: string): { value: string } | undefined;\n}\n\nexport function getThemeFromCookies(cookieStore: CookieGetter): Theme | undefined {\n return parseThemeCookie(cookieStore.get(THEME_COOKIE_NAME)?.value);\n}\n\n/**\n * Client-side cookie writer. Sets a path-`/` cookie with a year-long TTL and\n * `SameSite=Lax`, which is safe for theme preferences (non-sensitive, not\n * cross-site). No-op on the server.\n */\nexport function writeThemeCookie(theme: Theme): void {\n if (typeof document === 'undefined') return;\n document.cookie = `${THEME_COOKIE_NAME}=${theme}; path=/; max-age=${THEME_COOKIE_MAX_AGE}; samesite=lax`;\n}\n\n/** Read the theme cookie from `document.cookie`. No-op on the server. */\nexport function readThemeCookie(): Theme | undefined {\n if (typeof document === 'undefined') return undefined;\n const match = document.cookie\n .split(';')\n .map((part) => part.trim())\n .find((part) => part.startsWith(`${THEME_COOKIE_NAME}=`));\n if (!match) return undefined;\n return parseThemeCookie(match.slice(THEME_COOKIE_NAME.length + 1));\n}\n"],"mappings":";AASO,IAAM,oBAAoB;AAG1B,IAAM,uBAAuB,KAAK,KAAK,KAAK;AAG5C,SAAS,iBAAiB,OAAqD;AACpF,MAAI,UAAU,UAAU,UAAU,QAAS,QAAO;AAClD,SAAO;AACT;AAgBO,SAAS,oBAAoB,aAA8C;AAChF,SAAO,iBAAiB,YAAY,IAAI,iBAAiB,GAAG,KAAK;AACnE;AAOO,SAAS,iBAAiB,OAAoB;AACnD,MAAI,OAAO,aAAa,YAAa;AACrC,WAAS,SAAS,GAAG,iBAAiB,IAAI,KAAK,qBAAqB,oBAAoB;AAC1F;AAGO,SAAS,kBAAqC;AACnD,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,QAAQ,SAAS,OACpB,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EACzB,KAAK,CAAC,SAAS,KAAK,WAAW,GAAG,iBAAiB,GAAG,CAAC;AAC1D,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,iBAAiB,MAAM,MAAM,kBAAkB,SAAS,CAAC,CAAC;AACnE;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Theme } from '@ship-it-ui/ui';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
+
export { C as CookieGetter, T as THEME_COOKIE_MAX_AGE, a as THEME_COOKIE_NAME, g as getThemeFromCookies, p as parseThemeCookie, r as readThemeCookie, w as writeThemeCookie } from './server-DE2Fqp12.cjs';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* ThemeBootstrap — render inside the `<head>` of your App Router root layout.
|
|
@@ -57,41 +58,4 @@ declare namespace ThemeToggle {
|
|
|
57
58
|
var displayName: string;
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
* Theme cookie helpers shared between the server-side `getThemeFromCookies`
|
|
62
|
-
* and the client-side `ThemeBootstrap` / `ThemeToggle`. The cookie value is
|
|
63
|
-
* the literal string `'dark'` or `'light'`; anything else falls back to
|
|
64
|
-
* undefined and the consumer's default (typically dark) applies.
|
|
65
|
-
*/
|
|
66
|
-
|
|
67
|
-
declare const THEME_COOKIE_NAME = "ship-it-theme";
|
|
68
|
-
/** One year. The cookie is non-sensitive, so a long lifetime is fine. */
|
|
69
|
-
declare const THEME_COOKIE_MAX_AGE: number;
|
|
70
|
-
/** Parse a raw cookie string value into a `Theme` or `undefined`. */
|
|
71
|
-
declare function parseThemeCookie(value: string | null | undefined): Theme | undefined;
|
|
72
|
-
/**
|
|
73
|
-
* Server helper for the App Router `cookies()` API. Pass `cookies()` (or any
|
|
74
|
-
* object with `.get(name)` that returns `{ value: string }`) and receive the
|
|
75
|
-
* stored theme.
|
|
76
|
-
*
|
|
77
|
-
* ```ts
|
|
78
|
-
* import { cookies } from 'next/headers';
|
|
79
|
-
* const theme = getThemeFromCookies(await cookies());
|
|
80
|
-
* ```
|
|
81
|
-
*/
|
|
82
|
-
interface CookieGetter {
|
|
83
|
-
get(name: string): {
|
|
84
|
-
value: string;
|
|
85
|
-
} | undefined;
|
|
86
|
-
}
|
|
87
|
-
declare function getThemeFromCookies(cookieStore: CookieGetter): Theme | undefined;
|
|
88
|
-
/**
|
|
89
|
-
* Client-side cookie writer. Sets a path-`/` cookie with a year-long TTL and
|
|
90
|
-
* `SameSite=Lax`, which is safe for theme preferences (non-sensitive, not
|
|
91
|
-
* cross-site). No-op on the server.
|
|
92
|
-
*/
|
|
93
|
-
declare function writeThemeCookie(theme: Theme): void;
|
|
94
|
-
/** Read the theme cookie from `document.cookie`. No-op on the server. */
|
|
95
|
-
declare function readThemeCookie(): Theme | undefined;
|
|
96
|
-
|
|
97
|
-
export { type CookieGetter, THEME_COOKIE_MAX_AGE, THEME_COOKIE_NAME, ThemeBootstrap, ThemeToggle, type ThemeToggleProps, buildBootstrapScript, getThemeFromCookies, parseThemeCookie, readThemeCookie, writeThemeCookie };
|
|
61
|
+
export { ThemeBootstrap, ThemeToggle, type ThemeToggleProps, buildBootstrapScript };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Theme } from '@ship-it-ui/ui';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
+
export { C as CookieGetter, T as THEME_COOKIE_MAX_AGE, a as THEME_COOKIE_NAME, g as getThemeFromCookies, p as parseThemeCookie, r as readThemeCookie, w as writeThemeCookie } from './server-DE2Fqp12.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* ThemeBootstrap — render inside the `<head>` of your App Router root layout.
|
|
@@ -57,41 +58,4 @@ declare namespace ThemeToggle {
|
|
|
57
58
|
var displayName: string;
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
* Theme cookie helpers shared between the server-side `getThemeFromCookies`
|
|
62
|
-
* and the client-side `ThemeBootstrap` / `ThemeToggle`. The cookie value is
|
|
63
|
-
* the literal string `'dark'` or `'light'`; anything else falls back to
|
|
64
|
-
* undefined and the consumer's default (typically dark) applies.
|
|
65
|
-
*/
|
|
66
|
-
|
|
67
|
-
declare const THEME_COOKIE_NAME = "ship-it-theme";
|
|
68
|
-
/** One year. The cookie is non-sensitive, so a long lifetime is fine. */
|
|
69
|
-
declare const THEME_COOKIE_MAX_AGE: number;
|
|
70
|
-
/** Parse a raw cookie string value into a `Theme` or `undefined`. */
|
|
71
|
-
declare function parseThemeCookie(value: string | null | undefined): Theme | undefined;
|
|
72
|
-
/**
|
|
73
|
-
* Server helper for the App Router `cookies()` API. Pass `cookies()` (or any
|
|
74
|
-
* object with `.get(name)` that returns `{ value: string }`) and receive the
|
|
75
|
-
* stored theme.
|
|
76
|
-
*
|
|
77
|
-
* ```ts
|
|
78
|
-
* import { cookies } from 'next/headers';
|
|
79
|
-
* const theme = getThemeFromCookies(await cookies());
|
|
80
|
-
* ```
|
|
81
|
-
*/
|
|
82
|
-
interface CookieGetter {
|
|
83
|
-
get(name: string): {
|
|
84
|
-
value: string;
|
|
85
|
-
} | undefined;
|
|
86
|
-
}
|
|
87
|
-
declare function getThemeFromCookies(cookieStore: CookieGetter): Theme | undefined;
|
|
88
|
-
/**
|
|
89
|
-
* Client-side cookie writer. Sets a path-`/` cookie with a year-long TTL and
|
|
90
|
-
* `SameSite=Lax`, which is safe for theme preferences (non-sensitive, not
|
|
91
|
-
* cross-site). No-op on the server.
|
|
92
|
-
*/
|
|
93
|
-
declare function writeThemeCookie(theme: Theme): void;
|
|
94
|
-
/** Read the theme cookie from `document.cookie`. No-op on the server. */
|
|
95
|
-
declare function readThemeCookie(): Theme | undefined;
|
|
96
|
-
|
|
97
|
-
export { type CookieGetter, THEME_COOKIE_MAX_AGE, THEME_COOKIE_NAME, ThemeBootstrap, ThemeToggle, type ThemeToggleProps, buildBootstrapScript, getThemeFromCookies, parseThemeCookie, readThemeCookie, writeThemeCookie };
|
|
61
|
+
export { ThemeBootstrap, ThemeToggle, type ThemeToggleProps, buildBootstrapScript };
|
package/dist/index.js
CHANGED
|
@@ -1,25 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return parseThemeCookie(cookieStore.get(THEME_COOKIE_NAME)?.value);
|
|
12
|
-
}
|
|
13
|
-
function writeThemeCookie(theme) {
|
|
14
|
-
if (typeof document === "undefined") return;
|
|
15
|
-
document.cookie = `${THEME_COOKIE_NAME}=${theme}; path=/; max-age=${THEME_COOKIE_MAX_AGE}; samesite=lax`;
|
|
16
|
-
}
|
|
17
|
-
function readThemeCookie() {
|
|
18
|
-
if (typeof document === "undefined") return void 0;
|
|
19
|
-
const match = document.cookie.split(";").map((part) => part.trim()).find((part) => part.startsWith(`${THEME_COOKIE_NAME}=`));
|
|
20
|
-
if (!match) return void 0;
|
|
21
|
-
return parseThemeCookie(match.slice(THEME_COOKIE_NAME.length + 1));
|
|
22
|
-
}
|
|
3
|
+
import {
|
|
4
|
+
THEME_COOKIE_MAX_AGE,
|
|
5
|
+
THEME_COOKIE_NAME,
|
|
6
|
+
getThemeFromCookies,
|
|
7
|
+
parseThemeCookie,
|
|
8
|
+
readThemeCookie,
|
|
9
|
+
writeThemeCookie
|
|
10
|
+
} from "./chunk-K3H6CPFL.js";
|
|
23
11
|
|
|
24
12
|
// src/ThemeBootstrap.tsx
|
|
25
13
|
import { jsx } from "react/jsx-runtime";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/
|
|
1
|
+
{"version":3,"sources":["../src/ThemeBootstrap.tsx","../src/ThemeToggle.tsx"],"sourcesContent":["import { THEME_COOKIE_NAME } from './theme-cookie';\n\n/**\n * ThemeBootstrap — render inside the `<head>` of your App Router root layout.\n * Injects a synchronous inline script that reads the theme cookie and sets\n * `<html data-theme>` *before* the first paint, which kills the dark→light\n * flash of unstyled content for users on the light theme.\n *\n * Pair with `getThemeFromCookies(cookies())` to seed the server-rendered\n * `data-theme` on `<html>` for the very first frame.\n *\n * ```tsx\n * import { cookies } from 'next/headers';\n * import { ThemeBootstrap, getThemeFromCookies } from '@ship-it-ui/next';\n *\n * export default function RootLayout({ children }) {\n * const theme = getThemeFromCookies(cookies());\n * return (\n * <html lang=\"en\" data-theme={theme}>\n * <head><ThemeBootstrap /></head>\n * <body>{children}</body>\n * </html>\n * );\n * }\n * ```\n */\nexport function ThemeBootstrap(): JSX.Element {\n return (\n <script\n // The script must be synchronous and inline — there's no place else to\n // intercept the document before paint. Build the cookie name into the\n // script so consumers don't have to mirror it.\n dangerouslySetInnerHTML={{ __html: buildBootstrapScript() }}\n />\n );\n}\n\nThemeBootstrap.displayName = 'ThemeBootstrap';\n\n/**\n * Returns the inline-script body that `ThemeBootstrap` injects. Exported so\n * tests (and consumers with a custom layout) can inspect or reuse the source.\n */\nexport function buildBootstrapScript(): string {\n return `(function(){try{var m=document.cookie.match(/(?:^|; )${THEME_COOKIE_NAME}=(dark|light)/);var t=m&&m[1];if(t==='light'){document.documentElement.setAttribute('data-theme','light');}else if(t==='dark'){document.documentElement.removeAttribute('data-theme');}}catch(_){}})();`;\n}\n","'use client';\n\nimport { Switch, useTheme, type Theme } from '@ship-it-ui/ui';\nimport { useCallback, type ReactNode } from 'react';\n\nimport { writeThemeCookie } from './theme-cookie';\n\n/**\n * ThemeToggle — token-styled `Switch` bound to the active theme. Reuses the\n * shared `useTheme` hook from `@ship-it-ui/ui` for in-page state and writes\n * a year-long cookie so the next request's `ThemeBootstrap` can render the\n * correct theme synchronously.\n *\n * Pass `label` for a visible labelled control, or supply your own wrapper if\n * you want a chip / menu-item layout.\n */\n\nexport interface ThemeToggleProps {\n /** Visible label rendered next to the switch. */\n label?: ReactNode;\n /** Override the accessible name. Falls back to `label` when it is a string. */\n 'aria-label'?: string;\n /** Fires after the theme has been persisted. Useful for analytics. */\n onThemeChange?: (next: Theme) => void;\n}\n\nexport function ThemeToggle({\n label,\n 'aria-label': ariaLabel,\n onThemeChange,\n}: ThemeToggleProps): JSX.Element {\n const { theme, setTheme } = useTheme();\n\n const handleChange = useCallback(\n (next: boolean) => {\n const target: Theme = next ? 'light' : 'dark';\n setTheme(target);\n writeThemeCookie(target);\n onThemeChange?.(target);\n },\n [setTheme, onThemeChange],\n );\n\n const accessibleName = ariaLabel ?? (typeof label === 'string' ? label : 'Toggle theme');\n\n return (\n <span className=\"inline-flex items-center gap-2\">\n <Switch\n checked={theme === 'light'}\n onCheckedChange={handleChange}\n aria-label={accessibleName}\n />\n {label && <span className=\"text-text-muted text-[12px]\">{label}</span>}\n </span>\n );\n}\n\nThemeToggle.displayName = 'ThemeToggle';\n"],"mappings":";;;;;;;;;;AA4BI;AAFG,SAAS,iBAA8B;AAC5C,SACE;AAAA,IAAC;AAAA;AAAA,MAIC,yBAAyB,EAAE,QAAQ,qBAAqB,EAAE;AAAA;AAAA,EAC5D;AAEJ;AAEA,eAAe,cAAc;AAMtB,SAAS,uBAA+B;AAC7C,SAAO,wDAAwD,iBAAiB;AAClF;;;AC3CA,SAAS,QAAQ,gBAA4B;AAC7C,SAAS,mBAAmC;AA2CxC,SACE,OAAAA,MADF;AApBG,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,cAAc;AAAA,EACd;AACF,GAAkC;AAChC,QAAM,EAAE,OAAO,SAAS,IAAI,SAAS;AAErC,QAAM,eAAe;AAAA,IACnB,CAAC,SAAkB;AACjB,YAAM,SAAgB,OAAO,UAAU;AACvC,eAAS,MAAM;AACf,uBAAiB,MAAM;AACvB,sBAAgB,MAAM;AAAA,IACxB;AAAA,IACA,CAAC,UAAU,aAAa;AAAA,EAC1B;AAEA,QAAM,iBAAiB,cAAc,OAAO,UAAU,WAAW,QAAQ;AAEzE,SACE,qBAAC,UAAK,WAAU,kCACd;AAAA,oBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,SAAS,UAAU;AAAA,QACnB,iBAAiB;AAAA,QACjB,cAAY;AAAA;AAAA,IACd;AAAA,IACC,SAAS,gBAAAA,KAAC,UAAK,WAAU,+BAA+B,iBAAM;AAAA,KACjE;AAEJ;AAEA,YAAY,cAAc;","names":["jsx"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Theme } from '@ship-it-ui/ui';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Theme cookie helpers shared between the server-side `getThemeFromCookies`
|
|
5
|
+
* and the client-side `ThemeBootstrap` / `ThemeToggle`. The cookie value is
|
|
6
|
+
* the literal string `'dark'` or `'light'`; anything else falls back to
|
|
7
|
+
* undefined and the consumer's default (typically dark) applies.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
declare const THEME_COOKIE_NAME = "ship-it-theme";
|
|
11
|
+
/** One year. The cookie is non-sensitive, so a long lifetime is fine. */
|
|
12
|
+
declare const THEME_COOKIE_MAX_AGE: number;
|
|
13
|
+
/** Parse a raw cookie string value into a `Theme` or `undefined`. */
|
|
14
|
+
declare function parseThemeCookie(value: string | null | undefined): Theme | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Server helper for the App Router `cookies()` API. Pass `cookies()` (or any
|
|
17
|
+
* object with `.get(name)` that returns `{ value: string }`) and receive the
|
|
18
|
+
* stored theme.
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { cookies } from 'next/headers';
|
|
22
|
+
* const theme = getThemeFromCookies(await cookies());
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
interface CookieGetter {
|
|
26
|
+
get(name: string): {
|
|
27
|
+
value: string;
|
|
28
|
+
} | undefined;
|
|
29
|
+
}
|
|
30
|
+
declare function getThemeFromCookies(cookieStore: CookieGetter): Theme | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Client-side cookie writer. Sets a path-`/` cookie with a year-long TTL and
|
|
33
|
+
* `SameSite=Lax`, which is safe for theme preferences (non-sensitive, not
|
|
34
|
+
* cross-site). No-op on the server.
|
|
35
|
+
*/
|
|
36
|
+
declare function writeThemeCookie(theme: Theme): void;
|
|
37
|
+
/** Read the theme cookie from `document.cookie`. No-op on the server. */
|
|
38
|
+
declare function readThemeCookie(): Theme | undefined;
|
|
39
|
+
|
|
40
|
+
export { type CookieGetter as C, THEME_COOKIE_MAX_AGE as T, THEME_COOKIE_NAME as a, getThemeFromCookies as g, parseThemeCookie as p, readThemeCookie as r, writeThemeCookie as w };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Theme } from '@ship-it-ui/ui';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Theme cookie helpers shared between the server-side `getThemeFromCookies`
|
|
5
|
+
* and the client-side `ThemeBootstrap` / `ThemeToggle`. The cookie value is
|
|
6
|
+
* the literal string `'dark'` or `'light'`; anything else falls back to
|
|
7
|
+
* undefined and the consumer's default (typically dark) applies.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
declare const THEME_COOKIE_NAME = "ship-it-theme";
|
|
11
|
+
/** One year. The cookie is non-sensitive, so a long lifetime is fine. */
|
|
12
|
+
declare const THEME_COOKIE_MAX_AGE: number;
|
|
13
|
+
/** Parse a raw cookie string value into a `Theme` or `undefined`. */
|
|
14
|
+
declare function parseThemeCookie(value: string | null | undefined): Theme | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Server helper for the App Router `cookies()` API. Pass `cookies()` (or any
|
|
17
|
+
* object with `.get(name)` that returns `{ value: string }`) and receive the
|
|
18
|
+
* stored theme.
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { cookies } from 'next/headers';
|
|
22
|
+
* const theme = getThemeFromCookies(await cookies());
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
interface CookieGetter {
|
|
26
|
+
get(name: string): {
|
|
27
|
+
value: string;
|
|
28
|
+
} | undefined;
|
|
29
|
+
}
|
|
30
|
+
declare function getThemeFromCookies(cookieStore: CookieGetter): Theme | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Client-side cookie writer. Sets a path-`/` cookie with a year-long TTL and
|
|
33
|
+
* `SameSite=Lax`, which is safe for theme preferences (non-sensitive, not
|
|
34
|
+
* cross-site). No-op on the server.
|
|
35
|
+
*/
|
|
36
|
+
declare function writeThemeCookie(theme: Theme): void;
|
|
37
|
+
/** Read the theme cookie from `document.cookie`. No-op on the server. */
|
|
38
|
+
declare function readThemeCookie(): Theme | undefined;
|
|
39
|
+
|
|
40
|
+
export { type CookieGetter as C, THEME_COOKIE_MAX_AGE as T, THEME_COOKIE_NAME as a, getThemeFromCookies as g, parseThemeCookie as p, readThemeCookie as r, writeThemeCookie as w };
|
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/server.ts
|
|
21
|
+
var server_exports = {};
|
|
22
|
+
__export(server_exports, {
|
|
23
|
+
THEME_COOKIE_MAX_AGE: () => THEME_COOKIE_MAX_AGE,
|
|
24
|
+
THEME_COOKIE_NAME: () => THEME_COOKIE_NAME,
|
|
25
|
+
getThemeFromCookies: () => getThemeFromCookies,
|
|
26
|
+
parseThemeCookie: () => parseThemeCookie
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(server_exports);
|
|
29
|
+
|
|
30
|
+
// src/theme-cookie.ts
|
|
31
|
+
var THEME_COOKIE_NAME = "ship-it-theme";
|
|
32
|
+
var THEME_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
|
|
33
|
+
function parseThemeCookie(value) {
|
|
34
|
+
if (value === "dark" || value === "light") return value;
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
function getThemeFromCookies(cookieStore) {
|
|
38
|
+
return parseThemeCookie(cookieStore.get(THEME_COOKIE_NAME)?.value);
|
|
39
|
+
}
|
|
40
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
41
|
+
0 && (module.exports = {
|
|
42
|
+
THEME_COOKIE_MAX_AGE,
|
|
43
|
+
THEME_COOKIE_NAME,
|
|
44
|
+
getThemeFromCookies,
|
|
45
|
+
parseThemeCookie
|
|
46
|
+
});
|
|
47
|
+
//# sourceMappingURL=server.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/theme-cookie.ts"],"sourcesContent":["/**\n * @ship-it-ui/next/server — server-safe subset of `@ship-it-ui/next`.\n *\n * Re-exports only the cookie helpers that have no React dependency, so they\n * can be imported from a server `layout.tsx` without `tsup`'s preserved\n * `'use client'` directive (hoisted from `ThemeToggle.tsx` in the root entry)\n * tagging the whole module as a client reference.\n */\n\nexport {\n getThemeFromCookies,\n parseThemeCookie,\n THEME_COOKIE_NAME,\n THEME_COOKIE_MAX_AGE,\n type CookieGetter,\n} from './theme-cookie';\n","/**\n * Theme cookie helpers shared between the server-side `getThemeFromCookies`\n * and the client-side `ThemeBootstrap` / `ThemeToggle`. The cookie value is\n * the literal string `'dark'` or `'light'`; anything else falls back to\n * undefined and the consumer's default (typically dark) applies.\n */\n\nimport type { Theme } from '@ship-it-ui/ui';\n\nexport const THEME_COOKIE_NAME = 'ship-it-theme';\n\n/** One year. The cookie is non-sensitive, so a long lifetime is fine. */\nexport const THEME_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;\n\n/** Parse a raw cookie string value into a `Theme` or `undefined`. */\nexport function parseThemeCookie(value: string | null | undefined): Theme | undefined {\n if (value === 'dark' || value === 'light') return value;\n return undefined;\n}\n\n/**\n * Server helper for the App Router `cookies()` API. Pass `cookies()` (or any\n * object with `.get(name)` that returns `{ value: string }`) and receive the\n * stored theme.\n *\n * ```ts\n * import { cookies } from 'next/headers';\n * const theme = getThemeFromCookies(await cookies());\n * ```\n */\nexport interface CookieGetter {\n get(name: string): { value: string } | undefined;\n}\n\nexport function getThemeFromCookies(cookieStore: CookieGetter): Theme | undefined {\n return parseThemeCookie(cookieStore.get(THEME_COOKIE_NAME)?.value);\n}\n\n/**\n * Client-side cookie writer. Sets a path-`/` cookie with a year-long TTL and\n * `SameSite=Lax`, which is safe for theme preferences (non-sensitive, not\n * cross-site). No-op on the server.\n */\nexport function writeThemeCookie(theme: Theme): void {\n if (typeof document === 'undefined') return;\n document.cookie = `${THEME_COOKIE_NAME}=${theme}; path=/; max-age=${THEME_COOKIE_MAX_AGE}; samesite=lax`;\n}\n\n/** Read the theme cookie from `document.cookie`. No-op on the server. */\nexport function readThemeCookie(): Theme | undefined {\n if (typeof document === 'undefined') return undefined;\n const match = document.cookie\n .split(';')\n .map((part) => part.trim())\n .find((part) => part.startsWith(`${THEME_COOKIE_NAME}=`));\n if (!match) return undefined;\n return parseThemeCookie(match.slice(THEME_COOKIE_NAME.length + 1));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACSO,IAAM,oBAAoB;AAG1B,IAAM,uBAAuB,KAAK,KAAK,KAAK;AAG5C,SAAS,iBAAiB,OAAqD;AACpF,MAAI,UAAU,UAAU,UAAU,QAAS,QAAO;AAClD,SAAO;AACT;AAgBO,SAAS,oBAAoB,aAA8C;AAChF,SAAO,iBAAiB,YAAY,IAAI,iBAAiB,GAAG,KAAK;AACnE;","names":[]}
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
THEME_COOKIE_MAX_AGE,
|
|
3
|
+
THEME_COOKIE_NAME,
|
|
4
|
+
getThemeFromCookies,
|
|
5
|
+
parseThemeCookie
|
|
6
|
+
} from "./chunk-K3H6CPFL.js";
|
|
7
|
+
export {
|
|
8
|
+
THEME_COOKIE_MAX_AGE,
|
|
9
|
+
THEME_COOKIE_NAME,
|
|
10
|
+
getThemeFromCookies,
|
|
11
|
+
parseThemeCookie
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ship-it-ui/next",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Next.js (App Router) helpers for the Ship-It design system: SSR-safe theme persistence with FOUC prevention, cookie helpers, and a token-styled theme toggle.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://ship-it-ops.github.io/ship-it-design/",
|
|
@@ -30,6 +30,11 @@
|
|
|
30
30
|
"types": "./dist/index.d.ts",
|
|
31
31
|
"import": "./dist/index.js",
|
|
32
32
|
"require": "./dist/index.cjs"
|
|
33
|
+
},
|
|
34
|
+
"./server": {
|
|
35
|
+
"types": "./dist/server.d.ts",
|
|
36
|
+
"import": "./dist/server.js",
|
|
37
|
+
"require": "./dist/server.cjs"
|
|
33
38
|
}
|
|
34
39
|
},
|
|
35
40
|
"files": [
|
|
@@ -45,7 +50,7 @@
|
|
|
45
50
|
"next": "^14.0.0 || ^15.0.0",
|
|
46
51
|
"react": "^18.0.0 || ^19.0.0",
|
|
47
52
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
48
|
-
"@ship-it-ui/ui": "0.0.
|
|
53
|
+
"@ship-it-ui/ui": "0.0.5"
|
|
49
54
|
},
|
|
50
55
|
"peerDependenciesMeta": {
|
|
51
56
|
"next": {
|
|
@@ -68,9 +73,9 @@
|
|
|
68
73
|
"vitest": "^2.1.3",
|
|
69
74
|
"vitest-axe": "^0.1.0",
|
|
70
75
|
"@ship-it-ui/eslint-config": "0.0.1",
|
|
71
|
-
"@ship-it-ui/tokens": "0.0.
|
|
76
|
+
"@ship-it-ui/tokens": "0.0.5",
|
|
72
77
|
"@ship-it-ui/tsconfig": "0.0.1",
|
|
73
|
-
"@ship-it-ui/ui": "0.0.
|
|
78
|
+
"@ship-it-ui/ui": "0.0.5"
|
|
74
79
|
},
|
|
75
80
|
"scripts": {
|
|
76
81
|
"build": "tsup",
|