@ship-it-ui/next 0.0.2
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/LICENSE +21 -0
- package/dist/index.cjs +118 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +97 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.js +83 -0
- package/dist/index.js.map +1 -0
- package/package.json +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ship-it-ops
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
+
|
|
22
|
+
// src/index.ts
|
|
23
|
+
var index_exports = {};
|
|
24
|
+
__export(index_exports, {
|
|
25
|
+
THEME_COOKIE_MAX_AGE: () => THEME_COOKIE_MAX_AGE,
|
|
26
|
+
THEME_COOKIE_NAME: () => THEME_COOKIE_NAME,
|
|
27
|
+
ThemeBootstrap: () => ThemeBootstrap,
|
|
28
|
+
ThemeToggle: () => ThemeToggle,
|
|
29
|
+
buildBootstrapScript: () => buildBootstrapScript,
|
|
30
|
+
getThemeFromCookies: () => getThemeFromCookies,
|
|
31
|
+
parseThemeCookie: () => parseThemeCookie,
|
|
32
|
+
readThemeCookie: () => readThemeCookie,
|
|
33
|
+
writeThemeCookie: () => writeThemeCookie
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// src/theme-cookie.ts
|
|
38
|
+
var THEME_COOKIE_NAME = "ship-it-theme";
|
|
39
|
+
var THEME_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
|
|
40
|
+
function parseThemeCookie(value) {
|
|
41
|
+
if (value === "dark" || value === "light") return value;
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
function getThemeFromCookies(cookieStore) {
|
|
45
|
+
return parseThemeCookie(cookieStore.get(THEME_COOKIE_NAME)?.value);
|
|
46
|
+
}
|
|
47
|
+
function writeThemeCookie(theme) {
|
|
48
|
+
if (typeof document === "undefined") return;
|
|
49
|
+
document.cookie = `${THEME_COOKIE_NAME}=${theme}; path=/; max-age=${THEME_COOKIE_MAX_AGE}; samesite=lax`;
|
|
50
|
+
}
|
|
51
|
+
function readThemeCookie() {
|
|
52
|
+
if (typeof document === "undefined") return void 0;
|
|
53
|
+
const match = document.cookie.split(";").map((part) => part.trim()).find((part) => part.startsWith(`${THEME_COOKIE_NAME}=`));
|
|
54
|
+
if (!match) return void 0;
|
|
55
|
+
return parseThemeCookie(match.slice(THEME_COOKIE_NAME.length + 1));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/ThemeBootstrap.tsx
|
|
59
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
60
|
+
function ThemeBootstrap() {
|
|
61
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
62
|
+
"script",
|
|
63
|
+
{
|
|
64
|
+
dangerouslySetInnerHTML: { __html: buildBootstrapScript() }
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
ThemeBootstrap.displayName = "ThemeBootstrap";
|
|
69
|
+
function buildBootstrapScript() {
|
|
70
|
+
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(_){}})();`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/ThemeToggle.tsx
|
|
74
|
+
var import_ui = require("@ship-it-ui/ui");
|
|
75
|
+
var import_react = require("react");
|
|
76
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
77
|
+
function ThemeToggle({
|
|
78
|
+
label,
|
|
79
|
+
"aria-label": ariaLabel,
|
|
80
|
+
onThemeChange
|
|
81
|
+
}) {
|
|
82
|
+
const { theme, setTheme } = (0, import_ui.useTheme)();
|
|
83
|
+
const handleChange = (0, import_react.useCallback)(
|
|
84
|
+
(next) => {
|
|
85
|
+
const target = next ? "light" : "dark";
|
|
86
|
+
setTheme(target);
|
|
87
|
+
writeThemeCookie(target);
|
|
88
|
+
onThemeChange?.(target);
|
|
89
|
+
},
|
|
90
|
+
[setTheme, onThemeChange]
|
|
91
|
+
);
|
|
92
|
+
const accessibleName = ariaLabel ?? (typeof label === "string" ? label : "Toggle theme");
|
|
93
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "inline-flex items-center gap-2", children: [
|
|
94
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
95
|
+
import_ui.Switch,
|
|
96
|
+
{
|
|
97
|
+
checked: theme === "light",
|
|
98
|
+
onCheckedChange: handleChange,
|
|
99
|
+
"aria-label": accessibleName
|
|
100
|
+
}
|
|
101
|
+
),
|
|
102
|
+
label && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-text-muted text-[12px]", children: label })
|
|
103
|
+
] });
|
|
104
|
+
}
|
|
105
|
+
ThemeToggle.displayName = "ThemeToggle";
|
|
106
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
107
|
+
0 && (module.exports = {
|
|
108
|
+
THEME_COOKIE_MAX_AGE,
|
|
109
|
+
THEME_COOKIE_NAME,
|
|
110
|
+
ThemeBootstrap,
|
|
111
|
+
ThemeToggle,
|
|
112
|
+
buildBootstrapScript,
|
|
113
|
+
getThemeFromCookies,
|
|
114
|
+
parseThemeCookie,
|
|
115
|
+
readThemeCookie,
|
|
116
|
+
writeThemeCookie
|
|
117
|
+
});
|
|
118
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/theme-cookie.ts","../src/ThemeBootstrap.tsx","../src/ThemeToggle.tsx"],"sourcesContent":["/**\n * @ship-it-ui/next — Next.js (App Router) helpers for the Ship-It design\n * system. SSR-safe theme persistence with FOUC prevention, cookie helpers,\n * and a token-styled theme toggle that reuses `useTheme` from\n * `@ship-it-ui/ui`.\n */\n\nexport { ThemeBootstrap, buildBootstrapScript } from './ThemeBootstrap';\nexport { ThemeToggle, type ThemeToggleProps } from './ThemeToggle';\nexport {\n getThemeFromCookies,\n parseThemeCookie,\n readThemeCookie,\n writeThemeCookie,\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","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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;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;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;;;AC7BI;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,gBAA6C;AAC7C,mBAA4C;AA2CxC,IAAAA,sBAAA;AApBG,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,cAAc;AAAA,EACd;AACF,GAAkC;AAChC,QAAM,EAAE,OAAO,SAAS,QAAI,oBAAS;AAErC,QAAM,mBAAe;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,8CAAC,UAAK,WAAU,kCACd;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,SAAS,UAAU;AAAA,QACnB,iBAAiB;AAAA,QACjB,cAAY;AAAA;AAAA,IACd;AAAA,IACC,SAAS,6CAAC,UAAK,WAAU,+BAA+B,iBAAM;AAAA,KACjE;AAEJ;AAEA,YAAY,cAAc;","names":["import_jsx_runtime"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Theme } from '@ship-it-ui/ui';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ThemeBootstrap — render inside the `<head>` of your App Router root layout.
|
|
6
|
+
* Injects a synchronous inline script that reads the theme cookie and sets
|
|
7
|
+
* `<html data-theme>` *before* the first paint, which kills the dark→light
|
|
8
|
+
* flash of unstyled content for users on the light theme.
|
|
9
|
+
*
|
|
10
|
+
* Pair with `getThemeFromCookies(cookies())` to seed the server-rendered
|
|
11
|
+
* `data-theme` on `<html>` for the very first frame.
|
|
12
|
+
*
|
|
13
|
+
* ```tsx
|
|
14
|
+
* import { cookies } from 'next/headers';
|
|
15
|
+
* import { ThemeBootstrap, getThemeFromCookies } from '@ship-it-ui/next';
|
|
16
|
+
*
|
|
17
|
+
* export default function RootLayout({ children }) {
|
|
18
|
+
* const theme = getThemeFromCookies(cookies());
|
|
19
|
+
* return (
|
|
20
|
+
* <html lang="en" data-theme={theme}>
|
|
21
|
+
* <head><ThemeBootstrap /></head>
|
|
22
|
+
* <body>{children}</body>
|
|
23
|
+
* </html>
|
|
24
|
+
* );
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
declare function ThemeBootstrap(): JSX.Element;
|
|
29
|
+
declare namespace ThemeBootstrap {
|
|
30
|
+
var displayName: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns the inline-script body that `ThemeBootstrap` injects. Exported so
|
|
34
|
+
* tests (and consumers with a custom layout) can inspect or reuse the source.
|
|
35
|
+
*/
|
|
36
|
+
declare function buildBootstrapScript(): string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* ThemeToggle — token-styled `Switch` bound to the active theme. Reuses the
|
|
40
|
+
* shared `useTheme` hook from `@ship-it-ui/ui` for in-page state and writes
|
|
41
|
+
* a year-long cookie so the next request's `ThemeBootstrap` can render the
|
|
42
|
+
* correct theme synchronously.
|
|
43
|
+
*
|
|
44
|
+
* Pass `label` for a visible labelled control, or supply your own wrapper if
|
|
45
|
+
* you want a chip / menu-item layout.
|
|
46
|
+
*/
|
|
47
|
+
interface ThemeToggleProps {
|
|
48
|
+
/** Visible label rendered next to the switch. */
|
|
49
|
+
label?: ReactNode;
|
|
50
|
+
/** Override the accessible name. Falls back to `label` when it is a string. */
|
|
51
|
+
'aria-label'?: string;
|
|
52
|
+
/** Fires after the theme has been persisted. Useful for analytics. */
|
|
53
|
+
onThemeChange?: (next: Theme) => void;
|
|
54
|
+
}
|
|
55
|
+
declare function ThemeToggle({ label, 'aria-label': ariaLabel, onThemeChange, }: ThemeToggleProps): JSX.Element;
|
|
56
|
+
declare namespace ThemeToggle {
|
|
57
|
+
var displayName: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
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 };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Theme } from '@ship-it-ui/ui';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ThemeBootstrap — render inside the `<head>` of your App Router root layout.
|
|
6
|
+
* Injects a synchronous inline script that reads the theme cookie and sets
|
|
7
|
+
* `<html data-theme>` *before* the first paint, which kills the dark→light
|
|
8
|
+
* flash of unstyled content for users on the light theme.
|
|
9
|
+
*
|
|
10
|
+
* Pair with `getThemeFromCookies(cookies())` to seed the server-rendered
|
|
11
|
+
* `data-theme` on `<html>` for the very first frame.
|
|
12
|
+
*
|
|
13
|
+
* ```tsx
|
|
14
|
+
* import { cookies } from 'next/headers';
|
|
15
|
+
* import { ThemeBootstrap, getThemeFromCookies } from '@ship-it-ui/next';
|
|
16
|
+
*
|
|
17
|
+
* export default function RootLayout({ children }) {
|
|
18
|
+
* const theme = getThemeFromCookies(cookies());
|
|
19
|
+
* return (
|
|
20
|
+
* <html lang="en" data-theme={theme}>
|
|
21
|
+
* <head><ThemeBootstrap /></head>
|
|
22
|
+
* <body>{children}</body>
|
|
23
|
+
* </html>
|
|
24
|
+
* );
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
declare function ThemeBootstrap(): JSX.Element;
|
|
29
|
+
declare namespace ThemeBootstrap {
|
|
30
|
+
var displayName: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns the inline-script body that `ThemeBootstrap` injects. Exported so
|
|
34
|
+
* tests (and consumers with a custom layout) can inspect or reuse the source.
|
|
35
|
+
*/
|
|
36
|
+
declare function buildBootstrapScript(): string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* ThemeToggle — token-styled `Switch` bound to the active theme. Reuses the
|
|
40
|
+
* shared `useTheme` hook from `@ship-it-ui/ui` for in-page state and writes
|
|
41
|
+
* a year-long cookie so the next request's `ThemeBootstrap` can render the
|
|
42
|
+
* correct theme synchronously.
|
|
43
|
+
*
|
|
44
|
+
* Pass `label` for a visible labelled control, or supply your own wrapper if
|
|
45
|
+
* you want a chip / menu-item layout.
|
|
46
|
+
*/
|
|
47
|
+
interface ThemeToggleProps {
|
|
48
|
+
/** Visible label rendered next to the switch. */
|
|
49
|
+
label?: ReactNode;
|
|
50
|
+
/** Override the accessible name. Falls back to `label` when it is a string. */
|
|
51
|
+
'aria-label'?: string;
|
|
52
|
+
/** Fires after the theme has been persisted. Useful for analytics. */
|
|
53
|
+
onThemeChange?: (next: Theme) => void;
|
|
54
|
+
}
|
|
55
|
+
declare function ThemeToggle({ label, 'aria-label': ariaLabel, onThemeChange, }: ThemeToggleProps): JSX.Element;
|
|
56
|
+
declare namespace ThemeToggle {
|
|
57
|
+
var displayName: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
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 };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// src/theme-cookie.ts
|
|
4
|
+
var THEME_COOKIE_NAME = "ship-it-theme";
|
|
5
|
+
var THEME_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
|
|
6
|
+
function parseThemeCookie(value) {
|
|
7
|
+
if (value === "dark" || value === "light") return value;
|
|
8
|
+
return void 0;
|
|
9
|
+
}
|
|
10
|
+
function getThemeFromCookies(cookieStore) {
|
|
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
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/ThemeBootstrap.tsx
|
|
25
|
+
import { jsx } from "react/jsx-runtime";
|
|
26
|
+
function ThemeBootstrap() {
|
|
27
|
+
return /* @__PURE__ */ jsx(
|
|
28
|
+
"script",
|
|
29
|
+
{
|
|
30
|
+
dangerouslySetInnerHTML: { __html: buildBootstrapScript() }
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
ThemeBootstrap.displayName = "ThemeBootstrap";
|
|
35
|
+
function buildBootstrapScript() {
|
|
36
|
+
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(_){}})();`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/ThemeToggle.tsx
|
|
40
|
+
import { Switch, useTheme } from "@ship-it-ui/ui";
|
|
41
|
+
import { useCallback } from "react";
|
|
42
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
43
|
+
function ThemeToggle({
|
|
44
|
+
label,
|
|
45
|
+
"aria-label": ariaLabel,
|
|
46
|
+
onThemeChange
|
|
47
|
+
}) {
|
|
48
|
+
const { theme, setTheme } = useTheme();
|
|
49
|
+
const handleChange = useCallback(
|
|
50
|
+
(next) => {
|
|
51
|
+
const target = next ? "light" : "dark";
|
|
52
|
+
setTheme(target);
|
|
53
|
+
writeThemeCookie(target);
|
|
54
|
+
onThemeChange?.(target);
|
|
55
|
+
},
|
|
56
|
+
[setTheme, onThemeChange]
|
|
57
|
+
);
|
|
58
|
+
const accessibleName = ariaLabel ?? (typeof label === "string" ? label : "Toggle theme");
|
|
59
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2", children: [
|
|
60
|
+
/* @__PURE__ */ jsx2(
|
|
61
|
+
Switch,
|
|
62
|
+
{
|
|
63
|
+
checked: theme === "light",
|
|
64
|
+
onCheckedChange: handleChange,
|
|
65
|
+
"aria-label": accessibleName
|
|
66
|
+
}
|
|
67
|
+
),
|
|
68
|
+
label && /* @__PURE__ */ jsx2("span", { className: "text-text-muted text-[12px]", children: label })
|
|
69
|
+
] });
|
|
70
|
+
}
|
|
71
|
+
ThemeToggle.displayName = "ThemeToggle";
|
|
72
|
+
export {
|
|
73
|
+
THEME_COOKIE_MAX_AGE,
|
|
74
|
+
THEME_COOKIE_NAME,
|
|
75
|
+
ThemeBootstrap,
|
|
76
|
+
ThemeToggle,
|
|
77
|
+
buildBootstrapScript,
|
|
78
|
+
getThemeFromCookies,
|
|
79
|
+
parseThemeCookie,
|
|
80
|
+
readThemeCookie,
|
|
81
|
+
writeThemeCookie
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/theme-cookie.ts","../src/ThemeBootstrap.tsx","../src/ThemeToggle.tsx"],"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","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":";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;;;AC7BI;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"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ship-it-ui/next",
|
|
3
|
+
"version": "0.0.2",
|
|
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
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://ship-it-ops.github.io/ship-it-design/",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/ship-it-ops/ship-it-design/issues"
|
|
9
|
+
},
|
|
10
|
+
"author": "Ship-It Ops",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"design-system",
|
|
13
|
+
"next",
|
|
14
|
+
"next.js",
|
|
15
|
+
"app-router",
|
|
16
|
+
"theme",
|
|
17
|
+
"shipit"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/ship-it-ops/ship-it-design.git",
|
|
22
|
+
"directory": "packages/next"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"module": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js",
|
|
32
|
+
"require": "./dist/index.cjs"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"sideEffects": false,
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public",
|
|
42
|
+
"provenance": true
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"next": "^14.0.0 || ^15.0.0",
|
|
46
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
47
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
48
|
+
"@ship-it-ui/ui": "0.0.4"
|
|
49
|
+
},
|
|
50
|
+
"peerDependenciesMeta": {
|
|
51
|
+
"next": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
57
|
+
"@testing-library/react": "^16.0.1",
|
|
58
|
+
"@testing-library/user-event": "^14.5.2",
|
|
59
|
+
"@types/react": "^18.3.12",
|
|
60
|
+
"@types/react-dom": "^18.3.1",
|
|
61
|
+
"axe-core": "^4.10.2",
|
|
62
|
+
"esbuild-plugin-preserve-directives": "^0.0.11",
|
|
63
|
+
"jsdom": "^29.1.1",
|
|
64
|
+
"react": "^18.3.1",
|
|
65
|
+
"react-dom": "^18.3.1",
|
|
66
|
+
"tsup": "^8.3.0",
|
|
67
|
+
"typescript": "^5.6.3",
|
|
68
|
+
"vitest": "^2.1.3",
|
|
69
|
+
"vitest-axe": "^0.1.0",
|
|
70
|
+
"@ship-it-ui/eslint-config": "0.0.1",
|
|
71
|
+
"@ship-it-ui/tokens": "0.0.4",
|
|
72
|
+
"@ship-it-ui/tsconfig": "0.0.1",
|
|
73
|
+
"@ship-it-ui/ui": "0.0.4"
|
|
74
|
+
},
|
|
75
|
+
"scripts": {
|
|
76
|
+
"build": "tsup",
|
|
77
|
+
"dev": "tsup --watch",
|
|
78
|
+
"lint": "eslint src",
|
|
79
|
+
"lint:fix": "eslint src --fix",
|
|
80
|
+
"test": "vitest run --passWithNoTests",
|
|
81
|
+
"test:watch": "vitest",
|
|
82
|
+
"typecheck": "tsc --noEmit",
|
|
83
|
+
"clean": "rm -rf dist .turbo"
|
|
84
|
+
}
|
|
85
|
+
}
|