@korioinc/next-core 2.0.32 → 2.0.35

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.
Files changed (38) hide show
  1. package/README.md +10 -1
  2. package/dist/components/locale-switcher/index.d.ts.map +1 -1
  3. package/dist/components/locale-switcher/index.js +14 -0
  4. package/dist/components/theme-switcher/index.js +1 -1
  5. package/dist/i18n/routing.client.d.ts +2 -0
  6. package/dist/i18n/routing.client.d.ts.map +1 -0
  7. package/dist/i18n/routing.client.js +7 -0
  8. package/dist/i18n/routing.d.ts +5 -0
  9. package/dist/i18n/routing.d.ts.map +1 -1
  10. package/dist/i18n/routing.js +54 -15
  11. package/dist/theme/context.d.ts +5 -0
  12. package/dist/theme/context.d.ts.map +1 -0
  13. package/dist/theme/context.js +9 -0
  14. package/dist/theme/index.d.ts +3 -0
  15. package/dist/theme/index.d.ts.map +1 -0
  16. package/dist/theme/index.js +2 -0
  17. package/dist/theme/index.server.d.ts +4 -0
  18. package/dist/theme/index.server.d.ts.map +1 -0
  19. package/dist/theme/index.server.js +21 -0
  20. package/dist/theme/script.d.ts +2 -0
  21. package/dist/theme/script.d.ts.map +1 -0
  22. package/dist/theme/script.js +92 -0
  23. package/dist/theme/shared.d.ts +44 -0
  24. package/dist/theme/shared.d.ts.map +1 -0
  25. package/dist/theme/shared.js +153 -0
  26. package/dist/theme/store.d.ts +13 -0
  27. package/dist/theme/store.d.ts.map +1 -0
  28. package/dist/theme/store.js +47 -0
  29. package/dist/theme/theme-provider.client.d.ts +5 -0
  30. package/dist/theme/theme-provider.client.d.ts.map +1 -0
  31. package/dist/theme/theme-provider.client.js +130 -0
  32. package/dist/theme/theme-script.d.ts +3 -0
  33. package/dist/theme/theme-script.d.ts.map +1 -0
  34. package/dist/theme/theme-script.js +22 -0
  35. package/dist/theme/types.d.ts +32 -0
  36. package/dist/theme/types.d.ts.map +1 -0
  37. package/dist/theme/types.js +1 -0
  38. package/package.json +20 -11
package/README.md CHANGED
@@ -49,6 +49,15 @@ export default function middleware(request: NextRequest) {
49
49
 
50
50
  ```typescript
51
51
  import { ThemeSwitcher, LocaleSwitcher } from '@korioinc/next-core/components';
52
+ import { ThemeProvider } from '@korioinc/next-core/theme/server';
53
+
54
+ export function Layout({ children }: { children: React.ReactNode }) {
55
+ return (
56
+ <ThemeProvider attribute="class" enableSystem defaultTheme="system">
57
+ {children}
58
+ </ThemeProvider>
59
+ );
60
+ }
52
61
 
53
62
  export function Header() {
54
63
  return (
@@ -87,4 +96,4 @@ MIT
87
96
 
88
97
  ## Contributing
89
98
 
90
- Contributions are welcome! Please feel free to submit a Pull Request.
99
+ Contributions are welcome! Please feel free to submit a Pull Request.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/locale-switcher/index.tsx"],"names":[],"mappings":"AAcA,UAAU,mBAAmB;IAC3B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EAAE,SAAS,EAAE,GAAE,mBAAwB,kDAgF7E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/locale-switcher/index.tsx"],"names":[],"mappings":"AAeA,UAAU,mBAAmB;IAC3B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EAAE,SAAS,EAAE,GAAE,mBAAwB,kDAiG7E"}
@@ -7,6 +7,7 @@ import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/r
7
7
  import { useLingui } from '@lingui/react';
8
8
  import { allLanguages } from './language-options';
9
9
  import { resolveVisibleLanguages } from './resolve-visible-languages';
10
+ import { setCookie } from 'cookies-next/client';
10
11
  import linguiConfig from 'lingui.config';
11
12
  export default function LocaleSwitcher({ languages } = {}) {
12
13
  const router = useRouter();
@@ -16,6 +17,15 @@ export default function LocaleSwitcher({ languages } = {}) {
16
17
  useEffect(() => {
17
18
  setMounted(true);
18
19
  }, []);
20
+ useEffect(() => {
21
+ if (!mounted || !i18n.locale) {
22
+ return;
23
+ }
24
+ setCookie('NEXT_LOCALE', i18n.locale, {
25
+ path: '/',
26
+ sameSite: 'lax',
27
+ });
28
+ }, [mounted, i18n.locale]);
19
29
  const availableLanguages = resolveVisibleLanguages({
20
30
  allLanguages,
21
31
  defaultLanguageCodes: linguiConfig.locales,
@@ -30,6 +40,10 @@ export default function LocaleSwitcher({ languages } = {}) {
30
40
  const changeLanguage = (nextLocale) => {
31
41
  const currentLocale = i18n.locale;
32
42
  const newPath = getChangeLocalePath(pathname, currentLocale, nextLocale);
43
+ setCookie('NEXT_LOCALE', nextLocale, {
44
+ path: '/',
45
+ sameSite: 'lax',
46
+ });
33
47
  router.replace(newPath);
34
48
  router.refresh();
35
49
  };
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from 'react';
4
- import { useTheme } from 'next-themes';
4
+ import { useTheme } from '../../theme';
5
5
  const FALLBACK_THEME_ORDER = ['light', 'dark'];
6
6
  const isThemeMode = (value) => value === 'light' || value === 'dark' || value === 'system';
7
7
  const getThemeOrder = (themes) => {
@@ -0,0 +1,2 @@
1
+ export declare function useToLocalePath(): (href?: string | null) => string;
2
+ //# sourceMappingURL=routing.client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing.client.d.ts","sourceRoot":"","sources":["../../src/i18n/routing.client.ts"],"names":[],"mappings":"AAMA,wBAAgB,eAAe,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,MAAM,CAIlE"}
@@ -0,0 +1,7 @@
1
+ 'use client';
2
+ import { usePathname } from 'next/navigation';
3
+ import { toLocalePath as buildLocalePath } from './routing';
4
+ export function useToLocalePath() {
5
+ const pathname = usePathname();
6
+ return (href) => buildLocalePath(pathname, href);
7
+ }
@@ -8,6 +8,7 @@ export declare function getLocales(): string[];
8
8
  * Get default locale
9
9
  */
10
10
  export declare function getDefaultLocale(): string;
11
+ export declare function getPathLocale(pathname: string): string | undefined;
11
12
  export declare function handleLocaleRouting(request: NextRequest): NextResponse<unknown>;
12
13
  export declare function getRequestLocale(requestHeaders: Headers): string;
13
14
  export declare function getCookieLocale(requestHeaders: Headers, pathname?: string): string | undefined;
@@ -18,6 +19,10 @@ export declare function getPathname({ locale, href }: {
18
19
  }): string;
19
20
  export declare function getLanguageAlternates(pathname?: string): Record<string, string>;
20
21
  export declare function getCurrentPathname(): Promise<string>;
22
+ export declare function getCurrentPathLocale(): Promise<string | undefined>;
23
+ export declare function getPathLocalePathname(pathname: string, href?: string | null): string;
24
+ export declare function toLocalePath(pathname: string, href?: string | null): string;
25
+ export declare function getLocalePathname(href: string): Promise<string>;
21
26
  export declare function getCurrentSearchParams(): Promise<URLSearchParams>;
22
27
  export declare function getDefaultPathname(): Promise<string>;
23
28
  export declare function getCurrentLanguageAlternates(): Promise<Record<string, string>>;
@@ -1 +1 @@
1
- {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/i18n/routing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsC7D;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,EAAE,CAErC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAiGD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,yBAsEvD;AAED,wBAAgB,gBAAgB,CAAC,cAAc,EAAE,OAAO,GAAG,MAAM,CAShE;AAED,wBAAgB,eAAe,CAAC,cAAc,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAW9F;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAoBvG;AAED,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,UAY7E;AAsBD,wBAAgB,qBAAqB,CAAC,QAAQ,GAAE,MAAY,0BAwB3D;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAK1D;AAED,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,eAAe,CAAC,CAMvE;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAU1D;AAED,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAIpF;AAED,KAAK,aAAa,GACd,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GAC7C,eAAe,GACf,uBAAuB,GACvB,SAAS,CAAC;AAiDd,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,MAAM,CAErF;AAED,wBAAsB,UAAU,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAIxE;AAED,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,MAAM,CAAC,CAKnE"}
1
+ {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/i18n/routing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAoC7D;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,EAAE,CAErC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AA4BD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAElE;AAiFD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,yBA2EvD;AAED,wBAAgB,gBAAgB,CAAC,cAAc,EAAE,OAAO,GAAG,MAAM,CAShE;AAED,wBAAgB,eAAe,CAAC,cAAc,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAW9F;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAevG;AAED,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,UAY7E;AAsBD,wBAAgB,qBAAqB,CAAC,QAAQ,GAAE,MAAY,0BAwB3D;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAK1D;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAKxE;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAuBpF;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAE3E;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKrE;AAED,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,eAAe,CAAC,CAMvE;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAU1D;AAED,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAIpF;AAED,KAAK,aAAa,GACd,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GAC7C,eAAe,GACf,uBAAuB,GACvB,SAAS,CAAC;AAiDd,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,MAAM,CAErF;AAED,wBAAsB,UAAU,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAIxE;AAED,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,MAAM,CAAC,CAKnE"}
@@ -19,11 +19,10 @@ const DEFAULT_FALLBACK = () => {
19
19
  const LOCALE_COOKIE_NAME = 'NEXT_LOCALE';
20
20
  const LOCALE_PREFIX = 'as-needed';
21
21
  const PATHNAME_HEADER_NAME = 'x-pathname';
22
+ const PATH_LOCALE_HEADER_NAME = 'x-path-locale';
22
23
  const SEARCH_PARAMS_HEADER_NAME = 'x-search-params';
23
- const setRoutingHeaders = (response, pathname, search) => {
24
- response.headers.set(PATHNAME_HEADER_NAME, pathname);
25
- response.headers.set(SEARCH_PARAMS_HEADER_NAME, search.replace(/^\?/, ''));
26
- return response;
24
+ const isDocumentRequest = (request) => {
25
+ return request.headers.get('accept')?.includes('text/html') || false;
27
26
  };
28
27
  /**
29
28
  * Get all available locales
@@ -57,6 +56,16 @@ const getPathLocaleSegment = (pathname) => {
57
56
  const pathMatch = pathname.match(/^\/([^\\/]+)(?:\/|$)/);
58
57
  return pathMatch?.[1];
59
58
  };
59
+ export function getPathLocale(pathname) {
60
+ return getCanonicalLocale(getPathLocaleSegment(pathname));
61
+ }
62
+ const setRoutingHeaders = (response, pathname, search) => {
63
+ const pathLocale = getCanonicalLocale(getPathLocaleSegment(pathname));
64
+ response.headers.set(PATHNAME_HEADER_NAME, pathname);
65
+ response.headers.set(PATH_LOCALE_HEADER_NAME, pathLocale || '');
66
+ response.headers.set(SEARCH_PARAMS_HEADER_NAME, search.replace(/^\?/, ''));
67
+ return response;
68
+ };
60
69
  const replacePathLocale = (pathname, locale) => {
61
70
  return pathname.replace(/^\/[^\\/]+(?=\/|$)/, `/${locale}`);
62
71
  };
@@ -107,6 +116,7 @@ export function handleLocaleRouting(request) {
107
116
  const localeCookie = request.cookies.get(LOCALE_COOKIE_NAME);
108
117
  const cookieLocale = getCanonicalLocale(localeCookie?.value);
109
118
  const defaultFallback = DEFAULT_FALLBACK();
119
+ const documentRequest = isDocumentRequest(request);
110
120
  // 1. URL에서 locale 감지
111
121
  const pathLocaleSegment = getPathLocaleSegment(pathname);
112
122
  const pathLocale = getCanonicalLocale(pathLocaleSegment);
@@ -114,7 +124,7 @@ export function handleLocaleRouting(request) {
114
124
  // 2. URL에 locale이 포함된 경우
115
125
  if (pathnameHasLocale) {
116
126
  if (pathLocaleSegment !== pathLocale) {
117
- return redirectWithLocale(request, pathname, pathLocale, cookieLocale !== pathLocale);
127
+ return redirectWithLocale(request, pathname, pathLocale, cookieLocale !== pathLocale && documentRequest);
118
128
  }
119
129
  // 이미 path에 locale이 있을 때
120
130
  const response = setRoutingHeaders(NextResponse.next(), pathname, request.nextUrl.search);
@@ -122,9 +132,12 @@ export function handleLocaleRouting(request) {
122
132
  if (cookieLocale !== pathLocale) {
123
133
  // 기본 locale인 경우 URL에서 제거
124
134
  if (LOCALE_PREFIX === 'as-needed' && pathLocale === defaultFallback) {
125
- return redirectWithoutLocale(request, pathname, pathLocale, true);
135
+ return redirectWithoutLocale(request, pathname, pathLocale, documentRequest);
126
136
  }
127
137
  // 다른 locale의 경우 쿠키 설정
138
+ if (!documentRequest) {
139
+ return response;
140
+ }
128
141
  return setCookie(response, pathLocale);
129
142
  }
130
143
  // locale은 같지만 기본 locale이고 as-needed 설정인 경우
@@ -154,7 +167,7 @@ export function handleLocaleRouting(request) {
154
167
  request.nextUrl.pathname = newPathname;
155
168
  const response = setRoutingHeaders(NextResponse.redirect(request.nextUrl), newPathname, request.nextUrl.search);
156
169
  // 쿠키가 없는 경우 설정
157
- if (localeCookie?.value === undefined) {
170
+ if (localeCookie?.value === undefined && documentRequest) {
158
171
  setCookie(response, locale);
159
172
  }
160
173
  return response;
@@ -178,25 +191,20 @@ export function getCookieLocale(requestHeaders, pathname) {
178
191
  return getCanonicalLocale(getPathLocaleSegment(pathname));
179
192
  }
180
193
  export function getChangeLocalePath(pathname, currentLocale, nextLocale) {
181
- // Create the new path based on whether the current path has a locale segment or not
182
194
  let pathNameWithoutLocale;
183
- // Check if pathname starts with current locale
184
195
  if (pathname === `/${currentLocale}` || pathname.startsWith(`/${currentLocale}/`)) {
185
- // Path has locale: /ko or /ko/something
186
- pathNameWithoutLocale = pathname.slice(currentLocale.length + 1); // Remove /locale part
196
+ pathNameWithoutLocale = pathname.slice(currentLocale.length + 1);
187
197
  if (pathNameWithoutLocale.startsWith('/')) {
188
- pathNameWithoutLocale = pathNameWithoutLocale.slice(1); // Remove leading slash if any
198
+ pathNameWithoutLocale = pathNameWithoutLocale.slice(1);
189
199
  }
190
200
  }
191
201
  else if (pathname === '/') {
192
- // Root path
193
202
  pathNameWithoutLocale = '';
194
203
  }
195
204
  else {
196
- // Path doesn't have locale prefix: /something
197
205
  pathNameWithoutLocale = pathname.startsWith('/') ? pathname.slice(1) : pathname;
198
206
  }
199
- return `/${nextLocale}${pathNameWithoutLocale ? '/' + pathNameWithoutLocale : ''}`;
207
+ return `/${nextLocale}${pathNameWithoutLocale ? `/${pathNameWithoutLocale}` : ''}`;
200
208
  }
201
209
  export function getPathname({ locale, href }) {
202
210
  const defaultFallback = DEFAULT_FALLBACK();
@@ -251,6 +259,37 @@ export async function getCurrentPathname() {
251
259
  const headersList = await headers();
252
260
  return headersList.get(PATHNAME_HEADER_NAME) || '';
253
261
  }
262
+ export async function getCurrentPathLocale() {
263
+ const { headers } = await import('next/headers');
264
+ const headersList = await headers();
265
+ return getCanonicalLocale(headersList.get(PATH_LOCALE_HEADER_NAME) || undefined);
266
+ }
267
+ export function getPathLocalePathname(pathname, href) {
268
+ if (href == null || href === '#') {
269
+ return '#';
270
+ }
271
+ const normalizedHref = href === '' ? '/' : href.startsWith('/') ? href : `/${href}`;
272
+ const hrefLocale = getPathLocale(normalizedHref);
273
+ if (hrefLocale) {
274
+ return normalizedHref;
275
+ }
276
+ const pathLocale = getPathLocale(pathname);
277
+ if (!pathLocale) {
278
+ return normalizedHref;
279
+ }
280
+ if (normalizedHref === '/') {
281
+ return `/${pathLocale}`;
282
+ }
283
+ return `/${pathLocale}${normalizedHref}`;
284
+ }
285
+ export function toLocalePath(pathname, href) {
286
+ return getPathLocalePathname(pathname, href);
287
+ }
288
+ export async function getLocalePathname(href) {
289
+ const pathLocale = await getCurrentPathLocale();
290
+ const currentPathname = pathLocale ? `/${pathLocale}` : '/';
291
+ return getPathLocalePathname(currentPathname, href);
292
+ }
254
293
  export async function getCurrentSearchParams() {
255
294
  const { headers } = await import('next/headers');
256
295
  const headersList = await headers();
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+ import type { UseThemeProps } from './types';
3
+ export declare const ThemeContext: React.Context<UseThemeProps | undefined>;
4
+ export declare function useTheme(): UseThemeProps;
5
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/theme/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,eAAO,MAAM,YAAY,0CAA4D,CAAC;AAOtF,wBAAgB,QAAQ,kBAEvB"}
@@ -0,0 +1,9 @@
1
+ import * as React from 'react';
2
+ export const ThemeContext = React.createContext(undefined);
3
+ const DEFAULT_CONTEXT = {
4
+ setTheme: () => undefined,
5
+ themes: [],
6
+ };
7
+ export function useTheme() {
8
+ return React.useContext(ThemeContext) ?? DEFAULT_CONTEXT;
9
+ }
@@ -0,0 +1,3 @@
1
+ export { ThemeProvider, useTheme } from './theme-provider.client';
2
+ export type { ThemeProviderProps, ThemeStorage, ThemeValueMap, UseThemeProps } from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/theme/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAClE,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,2 @@
1
+ 'use client';
2
+ export { ThemeProvider, useTheme } from './theme-provider.client';
@@ -0,0 +1,4 @@
1
+ import type { ThemeProviderProps, ThemeStorage, ThemeValueMap, UseThemeProps } from './types';
2
+ export declare function ThemeProvider(props: ThemeProviderProps): Promise<import("react/jsx-runtime").JSX.Element>;
3
+ export type { ThemeProviderProps, ThemeStorage, ThemeValueMap, UseThemeProps };
4
+ //# sourceMappingURL=index.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.server.d.ts","sourceRoot":"","sources":["../../src/theme/index.server.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE9F,wBAAsB,aAAa,CAAC,KAAK,EAAE,kBAAkB,oDAkB5D;AAED,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cookies } from 'next/headers';
3
+ import { getCookieStorageKey, isValidTheme, normalizeThemeProviderProps } from './shared';
4
+ import { ThemeProvider as ClientThemeProvider } from './theme-provider.client';
5
+ export async function ThemeProvider(props) {
6
+ const normalizedProps = normalizeThemeProviderProps(props);
7
+ let initialTheme = props.initialTheme;
8
+ if (!initialTheme && normalizedProps.storage === 'cookie') {
9
+ try {
10
+ const cookieStore = await cookies();
11
+ const storedTheme = cookieStore.get(getCookieStorageKey(normalizedProps.storageKey))?.value;
12
+ if (isValidTheme(storedTheme, normalizedProps.themes, normalizedProps.enableSystem)) {
13
+ initialTheme = storedTheme;
14
+ }
15
+ }
16
+ catch {
17
+ // Ignore missing request context and let the client resolve its own theme.
18
+ }
19
+ }
20
+ return _jsx(ClientThemeProvider, { ...props, initialTheme: initialTheme });
21
+ }
@@ -0,0 +1,2 @@
1
+ export declare function themeScript(attribute: 'class' | `data-${string}` | Array<'class' | `data-${string}`>, storageKey: string, defaultTheme: string, forcedTheme: string | undefined, themes: string[], value: Record<string, string> | undefined, enableSystem: boolean, enableColorScheme: boolean, storage: 'localStorage' | 'sessionStorage' | 'cookie' | 'none', initialTheme: string | undefined, disableTransitionOnChange: boolean): void;
2
+ //# sourceMappingURL=script.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"script.d.ts","sourceRoot":"","sources":["../../src/theme/script.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CACzB,SAAS,EAAE,OAAO,GAAG,QAAQ,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,GAAG,QAAQ,MAAM,EAAE,CAAC,EACzE,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,MAAM,EAAE,MAAM,EAAE,EAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EACzC,YAAY,EAAE,OAAO,EACrB,iBAAiB,EAAE,OAAO,EAC1B,OAAO,EAAE,cAAc,GAAG,gBAAgB,GAAG,QAAQ,GAAG,MAAM,EAC9D,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,yBAAyB,EAAE,OAAO,QAyHnC"}
@@ -0,0 +1,92 @@
1
+ export function themeScript(attribute, storageKey, defaultTheme, forcedTheme, themes, value, enableSystem, enableColorScheme, storage, initialTheme, disableTransitionOnChange) {
2
+ const root = document.documentElement;
3
+ const colorSchemes = ['light', 'dark'];
4
+ function resolveValue(theme) {
5
+ return value && value[theme] ? value[theme] : theme;
6
+ }
7
+ function getThemeTokens(themeValue) {
8
+ return themeValue.split(/\s+/).filter(Boolean);
9
+ }
10
+ function isValidTheme(theme) {
11
+ if (!theme) {
12
+ return false;
13
+ }
14
+ if (themes.includes(theme)) {
15
+ return true;
16
+ }
17
+ return enableSystem && theme === 'system';
18
+ }
19
+ function getCookieStorageKey() {
20
+ return encodeURIComponent(storageKey);
21
+ }
22
+ function readThemeFromCookie() {
23
+ const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${getCookieStorageKey().replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}=([^;]*)`));
24
+ return match?.[1] != null ? decodeURIComponent(match[1]) : null;
25
+ }
26
+ function readStoredTheme() {
27
+ if (storage === 'none') {
28
+ return null;
29
+ }
30
+ if (storage === 'cookie') {
31
+ return readThemeFromCookie();
32
+ }
33
+ if (storage === 'sessionStorage') {
34
+ return sessionStorage.getItem(storageKey);
35
+ }
36
+ return localStorage.getItem(storageKey);
37
+ }
38
+ function disableTransitions() {
39
+ if (!disableTransitionOnChange) {
40
+ return;
41
+ }
42
+ const style = document.createElement('style');
43
+ style.textContent =
44
+ '*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}';
45
+ document.head.appendChild(style);
46
+ const scheduleFrame = typeof window.requestAnimationFrame === 'function'
47
+ ? window.requestAnimationFrame.bind(window)
48
+ : (callback) => window.setTimeout(() => callback(0), 0);
49
+ scheduleFrame(() => {
50
+ scheduleFrame(() => {
51
+ style.remove();
52
+ });
53
+ });
54
+ }
55
+ function applyTheme(theme) {
56
+ if (!theme) {
57
+ return;
58
+ }
59
+ disableTransitions();
60
+ const nextTheme = resolveValue(theme);
61
+ const attributes = Array.isArray(attribute) ? attribute : [attribute];
62
+ attributes.forEach((currentAttribute) => {
63
+ if (currentAttribute === 'class') {
64
+ const classes = themes.flatMap((currentTheme) => getThemeTokens(resolveValue(currentTheme)));
65
+ root.classList.remove(...classes);
66
+ root.classList.add(...getThemeTokens(nextTheme));
67
+ return;
68
+ }
69
+ root.setAttribute(currentAttribute, nextTheme);
70
+ });
71
+ if (enableColorScheme) {
72
+ const fallbackColorScheme = colorSchemes.includes(defaultTheme) ? defaultTheme : null;
73
+ const colorScheme = colorSchemes.includes(theme) ? theme : fallbackColorScheme;
74
+ root.style.colorScheme = colorScheme || '';
75
+ }
76
+ }
77
+ function getSystemTheme() {
78
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
79
+ }
80
+ try {
81
+ const storedTheme = isValidTheme(initialTheme) ? initialTheme : readStoredTheme();
82
+ const selectedStoredTheme = isValidTheme(storedTheme ?? undefined) ? (storedTheme ?? defaultTheme) : defaultTheme;
83
+ const selectedTheme = forcedTheme || selectedStoredTheme;
84
+ const resolvedTheme = selectedTheme === 'system' && enableSystem ? getSystemTheme() : selectedTheme;
85
+ applyTheme(resolvedTheme);
86
+ }
87
+ catch {
88
+ const fallbackTheme = forcedTheme || defaultTheme;
89
+ const resolvedTheme = fallbackTheme === 'system' && enableSystem ? getSystemTheme() : fallbackTheme;
90
+ applyTheme(resolvedTheme);
91
+ }
92
+ }
@@ -0,0 +1,44 @@
1
+ import type { ThemeAttribute, ThemeProviderProps, ThemeStorage, ThemeValueMap } from './types';
2
+ export declare const DEFAULT_THEMES: string[];
3
+ export declare const DEFAULT_STORAGE_KEY = "theme";
4
+ export declare const DEFAULT_STORAGE: ThemeStorage;
5
+ export declare const MEDIA_QUERY = "(prefers-color-scheme: dark)";
6
+ type NormalizedThemeProviderProps = Omit<ThemeProviderProps, 'children'> & {
7
+ attribute: ThemeAttribute | ThemeAttribute[];
8
+ defaultTheme: string;
9
+ disableTransitionOnChange: boolean;
10
+ enableColorScheme: boolean;
11
+ enableSystem: boolean;
12
+ storage: ThemeStorage;
13
+ storageKey: string;
14
+ themes: string[];
15
+ };
16
+ type ApplyThemeOptions = Pick<NormalizedThemeProviderProps, 'attribute' | 'defaultTheme' | 'disableTransitionOnChange' | 'enableColorScheme' | 'themes' | 'value'> & {
17
+ resolvedTheme: string;
18
+ };
19
+ export declare function getCookieStorageKey(storageKey: string): string;
20
+ export declare function normalizeThemeProviderProps(props: ThemeProviderProps): NormalizedThemeProviderProps;
21
+ export declare function isValidTheme(theme: string | undefined, themes: string[], enableSystem: boolean): boolean;
22
+ export declare function getStoredTheme({ defaultTheme, enableSystem, storage, storageKey, themes, }: {
23
+ defaultTheme: string;
24
+ enableSystem: boolean;
25
+ storage: ThemeStorage;
26
+ storageKey: string;
27
+ themes: string[];
28
+ }): string;
29
+ export declare function saveTheme(storage: ThemeStorage, storageKey: string, value: string): void;
30
+ export declare function getServerThemeFromCookieString({ cookieString, defaultTheme, enableSystem, storageKey, themes, }: {
31
+ cookieString: string;
32
+ defaultTheme: string;
33
+ enableSystem: boolean;
34
+ storageKey: string;
35
+ themes: string[];
36
+ }): string;
37
+ export declare function getSystemTheme(mediaQueryList?: MediaQueryList | MediaQueryListEvent): "light" | "dark" | undefined;
38
+ export declare function resolveThemeValue(theme: string, value?: ThemeValueMap): string;
39
+ export declare function splitThemeValueTokens(themeValue: string): string[];
40
+ export declare function getThemeClassValues(themes: string[], value?: ThemeValueMap): string[];
41
+ export declare function resolveActiveTheme(theme: string | undefined, enableSystem: boolean, systemTheme?: string): string | undefined;
42
+ export declare function applyThemeToDocument({ attribute, defaultTheme, disableTransitionOnChange, enableColorScheme, resolvedTheme, themes, value, }: ApplyThemeOptions): void;
43
+ export {};
44
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/theme/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE/F,eAAO,MAAM,cAAc,UAAoB,CAAC;AAChD,eAAO,MAAM,mBAAmB,UAAU,CAAC;AAC3C,eAAO,MAAM,eAAe,EAAE,YAA6B,CAAC;AAC5D,eAAO,MAAM,WAAW,iCAAiC,CAAC;AAE1D,KAAK,4BAA4B,GAAG,IAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,GAAG;IACzE,SAAS,EAAE,cAAc,GAAG,cAAc,EAAE,CAAC;IAC7C,YAAY,EAAE,MAAM,CAAC;IACrB,yBAAyB,EAAE,OAAO,CAAC;IACnC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,YAAY,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,KAAK,iBAAiB,GAAG,IAAI,CAC3B,4BAA4B,EAC5B,WAAW,GAAG,cAAc,GAAG,2BAA2B,GAAG,mBAAmB,GAAG,QAAQ,GAAG,OAAO,CACtG,GAAG;IACF,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAIF,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,UAErD;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,kBAAkB,GAAG,4BAA4B,CAgCnG;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,OAAO,WAU9F;AAwBD,wBAAgB,cAAc,CAAC,EAC7B,YAAY,EACZ,YAAY,EACZ,OAAO,EACP,UAAU,EACV,MAAM,GACP,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,YAAY,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,UAYA;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAkBjF;AAED,wBAAgB,8BAA8B,CAAC,EAC7C,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,MAAM,GACP,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,UAIA;AAED,wBAAgB,cAAc,CAAC,cAAc,CAAC,EAAE,cAAc,GAAG,mBAAmB,gCAQnF;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa,UAErE;AAED,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,YAEvD;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,aAAa,YAE1E;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,MAAM,sBAUxG;AAED,wBAAgB,oBAAoB,CAAC,EACnC,SAAS,EACT,YAAY,EACZ,yBAAyB,EACzB,iBAAiB,EACjB,aAAa,EACb,MAAM,EACN,KAAK,GACN,EAAE,iBAAiB,QA6BnB"}
@@ -0,0 +1,153 @@
1
+ export const DEFAULT_THEMES = ['light', 'dark'];
2
+ export const DEFAULT_STORAGE_KEY = 'theme';
3
+ export const DEFAULT_STORAGE = 'localStorage';
4
+ export const MEDIA_QUERY = '(prefers-color-scheme: dark)';
5
+ const COLOR_SCHEMES = new Set(['light', 'dark']);
6
+ export function getCookieStorageKey(storageKey) {
7
+ return encodeURIComponent(storageKey);
8
+ }
9
+ export function normalizeThemeProviderProps(props) {
10
+ const { attribute = 'data-theme', defaultTheme, disableTransitionOnChange = false, enableColorScheme = true, enableSystem = true, forcedTheme, initialTheme, nonce, scriptProps, storage = DEFAULT_STORAGE, storageKey = DEFAULT_STORAGE_KEY, themes = DEFAULT_THEMES, value, } = props;
11
+ return {
12
+ attribute,
13
+ defaultTheme: defaultTheme ?? (enableSystem ? 'system' : 'light'),
14
+ disableTransitionOnChange,
15
+ enableColorScheme,
16
+ enableSystem,
17
+ forcedTheme,
18
+ initialTheme,
19
+ nonce,
20
+ scriptProps,
21
+ storage,
22
+ storageKey,
23
+ themes,
24
+ value,
25
+ };
26
+ }
27
+ export function isValidTheme(theme, themes, enableSystem) {
28
+ if (!theme) {
29
+ return false;
30
+ }
31
+ if (themes.includes(theme)) {
32
+ return true;
33
+ }
34
+ return enableSystem && theme === 'system';
35
+ }
36
+ function readThemeFromCookieString(cookieString, storageKey) {
37
+ const match = cookieString.match(new RegExp(`(?:^|;\\s*)${escapeRegExp(getCookieStorageKey(storageKey))}=([^;]*)`));
38
+ return match?.[1] != null ? decodeURIComponent(match[1]) : null;
39
+ }
40
+ function readThemeFromStorageArea(storage, storageKey) {
41
+ if (typeof window === 'undefined') {
42
+ return null;
43
+ }
44
+ if (storage === 'cookie') {
45
+ return readThemeFromCookieString(document.cookie, storageKey);
46
+ }
47
+ if (storage === 'sessionStorage') {
48
+ return sessionStorage.getItem(storageKey);
49
+ }
50
+ return localStorage.getItem(storageKey);
51
+ }
52
+ export function getStoredTheme({ defaultTheme, enableSystem, storage, storageKey, themes, }) {
53
+ if (typeof window === 'undefined' || storage === 'none') {
54
+ return defaultTheme;
55
+ }
56
+ try {
57
+ const storedTheme = readThemeFromStorageArea(storage, storageKey);
58
+ return isValidTheme(storedTheme ?? undefined, themes, enableSystem) ? (storedTheme ?? defaultTheme) : defaultTheme;
59
+ }
60
+ catch {
61
+ return defaultTheme;
62
+ }
63
+ }
64
+ export function saveTheme(storage, storageKey, value) {
65
+ try {
66
+ if (storage === 'none') {
67
+ return;
68
+ }
69
+ if (storage === 'cookie') {
70
+ const secureAttribute = typeof location !== 'undefined' && location.protocol === 'https:' ? '; Secure' : '';
71
+ document.cookie = `${getCookieStorageKey(storageKey)}=${encodeURIComponent(value)}; path=/; max-age=31536000; SameSite=Lax${secureAttribute}`;
72
+ return;
73
+ }
74
+ const storageArea = storage === 'sessionStorage' ? sessionStorage : localStorage;
75
+ storageArea.setItem(storageKey, value);
76
+ }
77
+ catch {
78
+ // Ignore storage failures in unsupported or restricted environments.
79
+ }
80
+ }
81
+ export function getServerThemeFromCookieString({ cookieString, defaultTheme, enableSystem, storageKey, themes, }) {
82
+ const storedTheme = readThemeFromCookieString(cookieString, storageKey);
83
+ return isValidTheme(storedTheme ?? undefined, themes, enableSystem) ? (storedTheme ?? defaultTheme) : defaultTheme;
84
+ }
85
+ export function getSystemTheme(mediaQueryList) {
86
+ if (typeof window === 'undefined') {
87
+ return undefined;
88
+ }
89
+ const query = mediaQueryList ?? window.matchMedia(MEDIA_QUERY);
90
+ return query.matches ? 'dark' : 'light';
91
+ }
92
+ export function resolveThemeValue(theme, value) {
93
+ return value?.[theme] ?? theme;
94
+ }
95
+ export function splitThemeValueTokens(themeValue) {
96
+ return themeValue.split(/\s+/).filter(Boolean);
97
+ }
98
+ export function getThemeClassValues(themes, value) {
99
+ return themes.flatMap((theme) => splitThemeValueTokens(resolveThemeValue(theme, value)));
100
+ }
101
+ export function resolveActiveTheme(theme, enableSystem, systemTheme) {
102
+ if (!theme) {
103
+ return undefined;
104
+ }
105
+ if (theme === 'system' && enableSystem) {
106
+ return systemTheme ?? 'light';
107
+ }
108
+ return theme;
109
+ }
110
+ export function applyThemeToDocument({ attribute, defaultTheme, disableTransitionOnChange, enableColorScheme, resolvedTheme, themes, value, }) {
111
+ if (typeof document === 'undefined' || !resolvedTheme) {
112
+ return;
113
+ }
114
+ const cleanupTransitions = disableTransitionOnChange ? disableTransitions() : null;
115
+ const root = document.documentElement;
116
+ const nextValue = resolveThemeValue(resolvedTheme, value);
117
+ const attributes = Array.isArray(attribute) ? attribute : [attribute];
118
+ attributes.forEach((currentAttribute) => {
119
+ if (currentAttribute === 'class') {
120
+ root.classList.remove(...getThemeClassValues(themes, value));
121
+ root.classList.add(...splitThemeValueTokens(nextValue));
122
+ return;
123
+ }
124
+ root.setAttribute(currentAttribute, nextValue);
125
+ });
126
+ if (enableColorScheme) {
127
+ const fallbackColorScheme = COLOR_SCHEMES.has(defaultTheme) ? defaultTheme : null;
128
+ const colorScheme = COLOR_SCHEMES.has(resolvedTheme) ? resolvedTheme : fallbackColorScheme;
129
+ root.style.colorScheme = colorScheme ?? '';
130
+ }
131
+ cleanupTransitions?.();
132
+ }
133
+ function disableTransitions() {
134
+ if (typeof document === 'undefined') {
135
+ return undefined;
136
+ }
137
+ const style = document.createElement('style');
138
+ style.appendChild(document.createTextNode('*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}'));
139
+ document.head.appendChild(style);
140
+ return () => {
141
+ const scheduleFrame = typeof window.requestAnimationFrame === 'function'
142
+ ? window.requestAnimationFrame.bind(window)
143
+ : (callback) => window.setTimeout(() => callback(0), 0);
144
+ scheduleFrame(() => {
145
+ scheduleFrame(() => {
146
+ style.remove();
147
+ });
148
+ });
149
+ };
150
+ }
151
+ function escapeRegExp(value) {
152
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
153
+ }
@@ -0,0 +1,13 @@
1
+ export interface ThemeState {
2
+ theme?: string;
3
+ systemTheme?: 'light' | 'dark';
4
+ }
5
+ export interface ThemeStore {
6
+ subscribe(listener: () => void): () => void;
7
+ getSnapshot(): ThemeState;
8
+ getServerSnapshot(): ThemeState;
9
+ setTheme(theme: string | undefined): void;
10
+ setSystemTheme(systemTheme: 'light' | 'dark' | undefined): void;
11
+ }
12
+ export declare function createThemeStore(initialState?: ThemeState): ThemeStore;
13
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/theme/store.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C,WAAW,IAAI,UAAU,CAAC;IAC1B,iBAAiB,IAAI,UAAU,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IAC1C,cAAc,CAAC,WAAW,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;CACjE;AAOD,wBAAgB,gBAAgB,CAAC,YAAY,GAAE,UAA4B,GAAG,UAAU,CA+CvF"}
@@ -0,0 +1,47 @@
1
+ const SERVER_SNAPSHOT = {
2
+ systemTheme: undefined,
3
+ theme: undefined,
4
+ };
5
+ export function createThemeStore(initialState = SERVER_SNAPSHOT) {
6
+ let state = initialState;
7
+ const listeners = new Set();
8
+ const emit = () => {
9
+ for (const listener of listeners) {
10
+ listener();
11
+ }
12
+ };
13
+ return {
14
+ subscribe(listener) {
15
+ listeners.add(listener);
16
+ return () => {
17
+ listeners.delete(listener);
18
+ };
19
+ },
20
+ getSnapshot() {
21
+ return state;
22
+ },
23
+ getServerSnapshot() {
24
+ return SERVER_SNAPSHOT;
25
+ },
26
+ setTheme(theme) {
27
+ if (state.theme === theme) {
28
+ return;
29
+ }
30
+ state = {
31
+ ...state,
32
+ theme,
33
+ };
34
+ emit();
35
+ },
36
+ setSystemTheme(systemTheme) {
37
+ if (state.systemTheme === systemTheme) {
38
+ return;
39
+ }
40
+ state = {
41
+ ...state,
42
+ systemTheme,
43
+ };
44
+ emit();
45
+ },
46
+ };
47
+ }
@@ -0,0 +1,5 @@
1
+ import { useTheme } from './context';
2
+ import type { ThemeProviderProps } from './types';
3
+ export { useTheme };
4
+ export declare function ThemeProvider(props: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=theme-provider.client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-provider.client.d.ts","sourceRoot":"","sources":["../../src/theme/theme-provider.client.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAgB,QAAQ,EAAE,MAAM,WAAW,CAAC;AAanD,OAAO,KAAK,EAAE,kBAAkB,EAAiB,MAAM,SAAS,CAAC;AAEjE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAEpB,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,2CAQtD"}
@@ -0,0 +1,130 @@
1
+ 'use client';
2
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import { useServerInsertedHTML } from 'next/navigation';
5
+ import { ThemeContext, useTheme } from './context';
6
+ import { MEDIA_QUERY, applyThemeToDocument, getStoredTheme, getSystemTheme, isValidTheme, normalizeThemeProviderProps, resolveActiveTheme, saveTheme, } from './shared';
7
+ import { createThemeStore } from './store';
8
+ import { ThemeScript } from './theme-script';
9
+ export { useTheme };
10
+ export function ThemeProvider(props) {
11
+ const context = React.useContext(ThemeContext);
12
+ if (context) {
13
+ return _jsx(_Fragment, { children: props.children });
14
+ }
15
+ return _jsx(ThemeProviderInner, { ...props });
16
+ }
17
+ function ThemeProviderInner(props) {
18
+ const { attribute, defaultTheme, disableTransitionOnChange, enableColorScheme, enableSystem, forcedTheme, initialTheme, nonce, scriptProps, storage, storageKey, themes, value, } = normalizeThemeProviderProps(props);
19
+ useServerInsertedHTML(() => (_jsx(ThemeScript, { attribute: attribute, defaultTheme: defaultTheme, disableTransitionOnChange: disableTransitionOnChange, enableColorScheme: enableColorScheme, enableSystem: enableSystem, forcedTheme: forcedTheme, initialTheme: initialTheme, nonce: nonce, scriptProps: scriptProps, storage: storage, storageKey: storageKey, themes: themes, value: value })));
20
+ const storeRef = React.useRef(createThemeStore({
21
+ systemTheme: getSystemTheme(),
22
+ theme: isValidTheme(initialTheme, themes, enableSystem)
23
+ ? initialTheme
24
+ : getStoredTheme({
25
+ defaultTheme,
26
+ enableSystem,
27
+ storage,
28
+ storageKey,
29
+ themes,
30
+ }),
31
+ }));
32
+ const store = storeRef.current;
33
+ const { systemTheme, theme } = React.useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot);
34
+ const applyResolvedTheme = React.useCallback((selectedTheme) => {
35
+ const resolvedTheme = resolveActiveTheme(selectedTheme, enableSystem, store.getSnapshot().systemTheme);
36
+ if (!resolvedTheme) {
37
+ return;
38
+ }
39
+ applyThemeToDocument({
40
+ attribute,
41
+ defaultTheme,
42
+ disableTransitionOnChange,
43
+ enableColorScheme,
44
+ resolvedTheme,
45
+ themes,
46
+ value,
47
+ });
48
+ }, [attribute, defaultTheme, disableTransitionOnChange, enableColorScheme, enableSystem, store, themes, value]);
49
+ const setTheme = React.useCallback((nextTheme) => {
50
+ if (forcedTheme) {
51
+ return;
52
+ }
53
+ const currentTheme = store.getSnapshot().theme ?? defaultTheme;
54
+ const selectedTheme = typeof nextTheme === 'function' ? nextTheme(currentTheme) : nextTheme;
55
+ store.setTheme(selectedTheme);
56
+ saveTheme(storage, storageKey, selectedTheme);
57
+ }, [defaultTheme, forcedTheme, storage, storageKey, store]);
58
+ React.useEffect(() => {
59
+ const mediaQueryList = window.matchMedia(MEDIA_QUERY);
60
+ const handleMediaQueryChange = (event) => {
61
+ store.setSystemTheme(getSystemTheme(event));
62
+ };
63
+ if (typeof mediaQueryList.addEventListener === 'function') {
64
+ mediaQueryList.addEventListener('change', handleMediaQueryChange);
65
+ }
66
+ else {
67
+ mediaQueryList.addListener(handleMediaQueryChange);
68
+ }
69
+ handleMediaQueryChange(mediaQueryList);
70
+ return () => {
71
+ if (typeof mediaQueryList.removeEventListener === 'function') {
72
+ mediaQueryList.removeEventListener('change', handleMediaQueryChange);
73
+ }
74
+ else {
75
+ mediaQueryList.removeListener(handleMediaQueryChange);
76
+ }
77
+ };
78
+ }, [store]);
79
+ React.useEffect(() => {
80
+ if (initialTheme && isValidTheme(initialTheme, themes, enableSystem)) {
81
+ store.setTheme(initialTheme);
82
+ saveTheme(storage, storageKey, initialTheme);
83
+ return;
84
+ }
85
+ const nextTheme = getStoredTheme({
86
+ defaultTheme,
87
+ enableSystem,
88
+ storage,
89
+ storageKey,
90
+ themes,
91
+ });
92
+ store.setTheme(nextTheme);
93
+ }, [defaultTheme, enableSystem, initialTheme, storage, storageKey, store, themes]);
94
+ React.useEffect(() => {
95
+ if (storage !== 'localStorage') {
96
+ return;
97
+ }
98
+ const handleStorage = (event) => {
99
+ if (event.key !== storageKey) {
100
+ return;
101
+ }
102
+ if (event.storageArea && event.storageArea !== localStorage) {
103
+ return;
104
+ }
105
+ if (!event.newValue) {
106
+ store.setTheme(defaultTheme);
107
+ return;
108
+ }
109
+ if (!isValidTheme(event.newValue, themes, enableSystem)) {
110
+ store.setTheme(defaultTheme);
111
+ return;
112
+ }
113
+ store.setTheme(event.newValue);
114
+ };
115
+ window.addEventListener('storage', handleStorage);
116
+ return () => window.removeEventListener('storage', handleStorage);
117
+ }, [defaultTheme, enableSystem, storage, storageKey, store, themes]);
118
+ React.useEffect(() => {
119
+ applyResolvedTheme(forcedTheme ?? theme);
120
+ }, [applyResolvedTheme, forcedTheme, theme]);
121
+ const contextValue = React.useMemo(() => ({
122
+ forcedTheme,
123
+ resolvedTheme: theme === 'system' ? systemTheme : theme,
124
+ setTheme,
125
+ systemTheme: enableSystem ? systemTheme : undefined,
126
+ theme,
127
+ themes: enableSystem ? [...themes, 'system'] : themes,
128
+ }), [enableSystem, forcedTheme, setTheme, systemTheme, theme, themes]);
129
+ return _jsx(ThemeContext.Provider, { value: contextValue, children: props.children });
130
+ }
@@ -0,0 +1,3 @@
1
+ import type { ThemeProviderProps } from './types';
2
+ export declare function ThemeScript(props: Omit<ThemeProviderProps, 'children'>): import("react/jsx-runtime").JSX.Element;
3
+ //# sourceMappingURL=theme-script.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-script.d.ts","sourceRoot":"","sources":["../../src/theme/theme-script.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD,wBAAgB,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,2CAyCtE"}
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { themeScript } from './script';
3
+ import { normalizeThemeProviderProps } from './shared';
4
+ export function ThemeScript(props) {
5
+ const { attribute, defaultTheme, disableTransitionOnChange, enableColorScheme, enableSystem, forcedTheme, initialTheme, nonce, scriptProps, storage, storageKey, themes, value, } = normalizeThemeProviderProps(props);
6
+ const scriptArguments = JSON.stringify([
7
+ attribute,
8
+ storageKey,
9
+ defaultTheme,
10
+ forcedTheme,
11
+ themes,
12
+ value,
13
+ enableSystem,
14
+ enableColorScheme,
15
+ storage,
16
+ initialTheme,
17
+ disableTransitionOnChange,
18
+ ]).slice(1, -1);
19
+ return (_jsx("script", { ...scriptProps, suppressHydrationWarning: true, nonce: nonce, dangerouslySetInnerHTML: {
20
+ __html: `(${themeScript.toString()})(${scriptArguments})`,
21
+ } }));
22
+ }
@@ -0,0 +1,32 @@
1
+ import type * as React from 'react';
2
+ export type ThemeAttribute = 'class' | `data-${string}`;
3
+ export type ThemeValueMap = Record<string, string>;
4
+ export type ThemeStorage = 'localStorage' | 'sessionStorage' | 'cookie' | 'none';
5
+ export interface ThemeScriptProps extends React.DetailedHTMLProps<React.ScriptHTMLAttributes<HTMLScriptElement>, HTMLScriptElement> {
6
+ [dataAttribute: `data-${string}`]: unknown;
7
+ }
8
+ export type SetThemeValue = React.SetStateAction<string>;
9
+ export interface UseThemeProps {
10
+ themes: string[];
11
+ forcedTheme?: string;
12
+ setTheme: React.Dispatch<SetThemeValue>;
13
+ theme?: string;
14
+ resolvedTheme?: string;
15
+ systemTheme?: 'light' | 'dark';
16
+ }
17
+ export interface ThemeProviderProps extends React.PropsWithChildren {
18
+ themes?: string[];
19
+ forcedTheme?: string;
20
+ enableSystem?: boolean;
21
+ disableTransitionOnChange?: boolean;
22
+ enableColorScheme?: boolean;
23
+ storageKey?: string;
24
+ defaultTheme?: string;
25
+ attribute?: ThemeAttribute | ThemeAttribute[];
26
+ value?: ThemeValueMap;
27
+ storage?: ThemeStorage;
28
+ initialTheme?: string;
29
+ nonce?: string;
30
+ scriptProps?: ThemeScriptProps;
31
+ }
32
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/theme/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAEpC,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,QAAQ,MAAM,EAAE,CAAC;AAExD,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACnD,MAAM,MAAM,YAAY,GAAG,cAAc,GAAG,gBAAgB,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEjF,MAAM,WAAW,gBAAiB,SAAQ,KAAK,CAAC,iBAAiB,CAC/D,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAC7C,iBAAiB,CAClB;IACC,CAAC,aAAa,EAAE,QAAQ,MAAM,EAAE,GAAG,OAAO,CAAC;CAC5C;AAED,MAAM,MAAM,aAAa,GAAG,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,kBAAmB,SAAQ,KAAK,CAAC,iBAAiB;IACjE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,cAAc,GAAG,cAAc,EAAE,CAAC;IAC9C,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC"}
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@korioinc/next-core",
3
- "version": "2.0.32",
3
+ "version": "2.0.35",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./ads": {
@@ -53,6 +53,16 @@
53
53
  "import": "./dist/components/index.js",
54
54
  "default": "./dist/components/index.js"
55
55
  },
56
+ "./theme": {
57
+ "types": "./dist/theme/index.d.ts",
58
+ "import": "./dist/theme/index.js",
59
+ "default": "./dist/theme/index.js"
60
+ },
61
+ "./theme/server": {
62
+ "types": "./dist/theme/index.server.d.ts",
63
+ "import": "./dist/theme/index.server.js",
64
+ "default": "./dist/theme/index.server.js"
65
+ },
56
66
  "./styles/*": "./dist/styles/*",
57
67
  "./types/*": "./dist/types/*"
58
68
  },
@@ -63,16 +73,16 @@
63
73
  ],
64
74
  "devDependencies": {
65
75
  "@headlessui/react": "^2.2.9",
66
- "@lingui/conf": "^5.9.3",
67
- "@lingui/core": "^5.9.3",
68
- "@lingui/react": "^5.9.3",
76
+ "@lingui/conf": "^5.9.4",
77
+ "@lingui/core": "^5.9.4",
78
+ "@lingui/react": "^5.9.4",
69
79
  "@tailwindcss/typography": "^0.5.19",
70
80
  "@types/negotiator": "^0.6.4",
71
81
  "@types/node": "^25.5.0",
72
82
  "@types/react": "^19.2.14",
73
83
  "@types/react-dom": "^19.2.3",
74
84
  "eslint": "^10.1.0",
75
- "next-themes": "^0.4.6",
85
+ "jsdom": "^29.0.1",
76
86
  "react": "^19.2.4",
77
87
  "react-dom": "^19.2.4",
78
88
  "tailwindcss": "^4.2.2",
@@ -80,7 +90,7 @@
80
90
  "tw-animate-css": "^1.4.0",
81
91
  "typescript": "^6.0.2",
82
92
  "vitest": "^4.1.2",
83
- "@korioinc/next-configs": "2.0.32"
93
+ "@korioinc/next-configs": "2.0.35"
84
94
  },
85
95
  "dependencies": {
86
96
  "@floating-ui/react": "^0.27.19",
@@ -95,7 +105,7 @@
95
105
  "schema-dts": "^2.0.0",
96
106
  "tailwind-merge": "^3.5.0",
97
107
  "valtio": "^2.3.1",
98
- "@korioinc/next-conf": "2.0.32"
108
+ "@korioinc/next-conf": "2.0.35"
99
109
  },
100
110
  "publishConfig": {
101
111
  "access": "public",
@@ -109,10 +119,9 @@
109
119
  "author": "Korio Inc.",
110
120
  "peerDependencies": {
111
121
  "@headlessui/react": ">=2.2.0",
112
- "@lingui/core": ">=5.9.3",
113
- "@lingui/react": ">=5.9.3",
114
- "next": ">=16.1.5",
115
- "next-themes": ">=0.4.6",
122
+ "@lingui/core": ">=5.9.4",
123
+ "@lingui/react": ">=5.9.4",
124
+ "next": ">=16.2.1",
116
125
  "react": ">=19.2.4",
117
126
  "react-dom": ">=19.2.4",
118
127
  "tailwindcss": ">=4.0.0"