lucentia-ui 0.2.9 → 0.2.10

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/README.md CHANGED
@@ -1,3 +1,9 @@
1
1
  # lucentia-ui
2
2
 
3
3
  React UI design token and component system based on neumorphism, featuring two color themes: light and dark.
4
+
5
+ ---
6
+
7
+ Not stable until version 1.00
8
+
9
+ ---
@@ -1,3 +1 @@
1
- import React from "react";
2
- import type { ThemeSwitchProps } from "./types";
3
- export declare const ThemeSwitch: React.FC<ThemeSwitchProps>;
1
+ export declare function ThemeSwitch(): import("react/jsx-runtime").JSX.Element;
@@ -1,17 +1,9 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useTheme } from "../../ThemeProvider";
2
4
  import styles from "./ThemeSwitch.module.css";
3
- import clsx from "clsx";
4
- export const ThemeSwitch = ({ checked, defaultChecked, onChange, onCheckedChange, disabled = false, id, name, className, }) => {
5
- const handleChange = (e) => {
6
- if (disabled)
7
- return;
8
- onChange === null || onChange === void 0 ? void 0 : onChange(e);
9
- onCheckedChange === null || onCheckedChange === void 0 ? void 0 : onCheckedChange(e.target.checked);
10
- };
11
- return (_jsxs("label", { className: clsx(styles.themeSwitch, className), style: {
12
- opacity: disabled ? 0.6 : 1,
13
- cursor: disabled ? "not-allowed" : "pointer",
14
- }, children: [_jsx("input", { type: "checkbox", id: id, name: name,
15
- // CSS Modules側のクラス名と一致させる(styles.input もしくは styles.themeSwitchCheckbox)
16
- className: styles.themeSwitchCheckbox, checked: checked, defaultChecked: defaultChecked, disabled: disabled, onChange: handleChange }), _jsx("span", { className: styles.slider, children: _jsxs("span", { className: styles.circle, children: [[...Array(8)].map((_, i) => (_jsx("span", { className: clsx(styles.shine, styles[`shine-${i + 1}`]) }, i))), _jsx("span", { className: styles.moon })] }) })] }));
17
- };
5
+ export function ThemeSwitch() {
6
+ const { theme, toggleTheme } = useTheme();
7
+ const isDark = theme === "dark";
8
+ return (_jsxs("label", { className: styles.switch, children: [_jsx("input", { type: "checkbox", checked: isDark, onChange: toggleTheme, "aria-label": "Toggle theme" }), _jsx("span", { className: styles.slider, children: _jsxs("span", { className: styles.circle, children: [Array.from({ length: 8 }).map((_, i) => (_jsx("span", { className: `${styles.shine} ${styles[`shine${i + 1}`]}` }, i))), _jsx("span", { className: styles.moon })] }) })] }));
9
+ }
@@ -1,99 +1,112 @@
1
- .themeSwitch {
2
- font-size: 18px;
3
- position: relative;
4
- display: inline-block;
5
- width: 72px;
6
- height: 40px;
1
+ /* The switch - the box around the slider */
2
+ .switch {
3
+ font-size: 17px;
4
+ position: relative;
5
+ display: inline-block;
6
+ width: 4.8em;
7
+ height: 2.4em;
7
8
  }
8
9
 
9
- /* input単体にスタイルを当てる */
10
- .input {
11
- opacity: 0;
12
- width: 0;
13
- height: 0;
10
+ /* Hide default HTML checkbox */
11
+ .switch input {
12
+ opacity: 0;
13
+ width: 0;
14
+ height: 0;
14
15
  }
15
16
 
17
+ /* The slider */
16
18
  .slider {
17
- position: absolute;
18
- cursor: pointer;
19
- top: 0;
20
- left: 0;
21
- right: 0;
22
- bottom: 0;
23
- background: var(--color-surface);
24
- box-shadow: var(--shadow-sm-in);
25
- transition: 0.4s;
26
- border-radius: 30px;
27
- overflow: hidden;
19
+ position: absolute;
20
+ cursor: pointer;
21
+ inset: 0;
22
+ background-color: var(--color-surface-container);
23
+ box-shadow: var(--shadow-md-in);
24
+ transition: 0.4s;
25
+ border-radius: 30px;
26
+ overflow: hidden;
28
27
  }
29
28
 
30
29
  .circle {
31
- position: absolute;
32
- height: 1.1em;
33
- width: 1.1em;
34
- border-radius: 20px;
35
- left: 0.6em;
36
- bottom: 0.45em;
37
- background-color: var(--color-primary);
38
- transition: 0.4s;
30
+ position: absolute;
31
+ height: 1.2em;
32
+ width: 1.2em;
33
+ border-radius: 20px;
34
+ left: 1em;
35
+ bottom: 0.7em;
36
+ background-color: var(--color-primary);
37
+ transition: 0.4s;
39
38
  }
40
39
 
41
- /* ThemeSwitch.module.css */
40
+ /* checked state */
41
+ .switch input:checked+.slider .circle {
42
+ transform: translateX(1.5em);
43
+ }
42
44
 
43
- /* inputのクラス名をTSXと合わせる */
44
- .themeSwitchCheckbox {
45
- opacity: 0;
46
- width: 0;
47
- height: 0;
48
- position: absolute;
45
+ .switch input:checked+.slider .circle .shine {
46
+ transform: translate(0%, 0%);
49
47
  }
50
48
 
51
- /* チェック時の隣接セレクタを調整 */
52
- .themeSwitchCheckbox:checked + .slider .circle {
53
- transform: rotate(0deg) translateX(1.5em) !important;
49
+ .switch input:checked+.slider .circle .moon {
50
+ left: -10%;
51
+ opacity: 1;
52
+ transform: translateY(-60%);
54
53
  }
55
54
 
56
- .themeSwitchCheckbox:checked + .slider .circle .shine {
57
- transform: translate(0%, 0%) !important;
55
+ /* Moon */
56
+ .moon {
57
+ position: absolute;
58
+ left: -100%;
59
+ top: 50%;
60
+ opacity: 0;
61
+ background-color: var(--color-surface-container);
62
+ width: 1.25rem;
63
+ height: 1.25rem;
64
+ border-radius: 99999px;
65
+ transform: translateY(-50%);
66
+ transition: all 0.4s;
58
67
  }
59
68
 
60
- .themeSwitchCheckbox:checked + .slider .circle .moon {
61
- left: -10%;
62
- opacity: 1;
63
- transform: translateY(-60%);
69
+ /* Shine */
70
+ .shine {
71
+ position: absolute;
72
+ top: 50%;
73
+ left: 50%;
74
+ width: 0.25rem;
75
+ height: 0.25rem;
76
+ background-color: var(--color-primary);
77
+ border-radius: 1rem;
78
+ transition: all 0.4s;
64
79
  }
65
80
 
66
- .moon {
67
- position: absolute;
68
- left: -100%;
69
- top: 50%;
70
- opacity: 0;
71
- background: var(--color-background);
72
- width: 1rem;
73
- height: 1rem;
74
- border-radius: 99999px;
75
- transform: translateY(-50%);
76
- transition: all 0.4s;
81
+ /* Individual shine positions */
82
+ .shine1 {
83
+ transform: translate(-50%, -375%);
77
84
  }
78
85
 
79
- .shine {
80
- display: block;
81
- position: absolute;
82
- top: 50%;
83
- left: 50%;
84
- width: 0.25rem;
85
- height: 0.25rem;
86
- background-color: var(--color-primary);
87
- border-radius: 4rem;
88
- transition: all 0.4s;
89
- }
90
-
91
- /* 個別のshine配置 */
92
- .shine-1 { transform: translate(-50%, -375%); }
93
- .shine-2 { transform: translate(175%, -275%); }
94
- .shine-3 { transform: translate(275%, -50%); }
95
- .shine-4 { transform: translate(175%, 175%); }
96
- .shine-5 { transform: translate(-50%, 275%); }
97
- .shine-6 { transform: translate(-275%, 175%); }
98
- .shine-7 { transform: translate(-375%, -50%); }
99
- .shine-8 { transform: translate(-275%, -275%); }
86
+ .shine2 {
87
+ transform: translate(175%, -275%);
88
+ }
89
+
90
+ .shine3 {
91
+ transform: translate(275%, -50%);
92
+ }
93
+
94
+ .shine4 {
95
+ transform: translate(175%, 175%);
96
+ }
97
+
98
+ .shine5 {
99
+ transform: translate(-50%, 275%);
100
+ }
101
+
102
+ .shine6 {
103
+ transform: translate(-275%, 175%);
104
+ }
105
+
106
+ .shine7 {
107
+ transform: translate(-375%, -50%);
108
+ }
109
+
110
+ .shine8 {
111
+ transform: translate(-275%, -275%);
112
+ }
@@ -1,7 +1,8 @@
1
1
  import type { Theme } from "./types";
2
- type ThemeProviderProps = {
3
- theme?: Theme;
2
+ type Props = {
3
+ defaultTheme?: Theme;
4
+ persist?: boolean;
4
5
  children: React.ReactNode;
5
6
  };
6
- export declare function ThemeProvider({ theme, children, }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function ThemeProvider({ defaultTheme, persist, children, }: Props): import("react/jsx-runtime").JSX.Element;
7
8
  export {};
@@ -1,5 +1,26 @@
1
+ "use client";
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import styles from "./ThemeProvider.module.css";
3
- export function ThemeProvider({ theme = "light", children, }) {
4
- return (_jsx("div", { className: `${styles.theme} ${theme === "dark" ? styles.dark : styles.light}`, children: children }));
3
+ import { useEffect, useState, useCallback } from "react";
4
+ import { ThemeContext } from "./context";
5
+ export function ThemeProvider({ defaultTheme = "light", persist = false, children, }) {
6
+ const [theme, setTheme] = useState(defaultTheme);
7
+ // 初期化
8
+ useEffect(() => {
9
+ if (!persist)
10
+ return;
11
+ const stored = localStorage.getItem("theme");
12
+ if (stored)
13
+ setTheme(stored);
14
+ }, [persist]);
15
+ // html[data-theme] 反映 + 永続化
16
+ useEffect(() => {
17
+ document.documentElement.dataset.theme = theme;
18
+ if (persist) {
19
+ localStorage.setItem("theme", theme);
20
+ }
21
+ }, [theme, persist]);
22
+ const toggleTheme = useCallback(() => {
23
+ setTheme((prev) => (prev === "dark" ? "light" : "dark"));
24
+ }, []);
25
+ return (_jsx(ThemeContext.Provider, { value: { theme, setTheme, toggleTheme }, children: children }));
5
26
  }
@@ -0,0 +1,7 @@
1
+ import type { Theme } from "./types";
2
+ export type ThemeContextValue = {
3
+ theme: Theme;
4
+ setTheme: (theme: Theme) => void;
5
+ toggleTheme: () => void;
6
+ };
7
+ export declare const ThemeContext: import("react").Context<ThemeContextValue | null>;
@@ -0,0 +1,3 @@
1
+ "use client";
2
+ import { createContext } from "react";
3
+ export const ThemeContext = createContext(null);
@@ -1,2 +1,3 @@
1
1
  export { ThemeProvider } from "./ThemeProvider";
2
+ export { useTheme } from "./useTheme";
2
3
  export type { Theme } from "./types";
@@ -1 +1,2 @@
1
1
  export { ThemeProvider } from "./ThemeProvider";
2
+ export { useTheme } from "./useTheme";
@@ -0,0 +1 @@
1
+ export declare function useTheme(): import("./context").ThemeContextValue;
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ import { useContext } from "react";
3
+ import { ThemeContext } from "./context";
4
+ export function useTheme() {
5
+ const ctx = useContext(ThemeContext);
6
+ if (!ctx) {
7
+ throw new Error("useTheme must be used within ThemeProvider");
8
+ }
9
+ return ctx;
10
+ }
@@ -0,0 +1,2 @@
1
+ import type { Theme } from "./types";
2
+ export declare function useThemeStorage(theme: Theme): void;
@@ -0,0 +1,7 @@
1
+ "use client";
2
+ import { useEffect } from "react";
3
+ export function useThemeStorage(theme) {
4
+ useEffect(() => {
5
+ localStorage.setItem("theme", theme);
6
+ }, [theme]);
7
+ }
@@ -2,11 +2,33 @@
2
2
  Reset / Base
3
3
  ------------------------------ */
4
4
 
5
+
6
+
7
+ html.theme-switching *,
8
+ html.theme-switching *::before,
9
+ html.theme-switching *::after {
10
+ transition: none !important;
11
+ }
12
+
13
+
14
+ /* html / body 基本設定 */
15
+ html {
16
+ -webkit-text-size-adjust: 100%;
17
+ text-size-adjust: 100%;
18
+ min-height: 100%;
19
+ font-family: var(--font),
20
+ system-ui,
21
+ -apple-system,
22
+ sans-serif;
23
+ background: var(--color-background);
24
+ }
25
+
26
+
5
27
  /* box-sizing を全要素に適用 */
6
28
  *,
7
29
  *::before,
8
30
  *::after {
9
- box-sizing: border-box;
31
+ box-sizing: border-box;
10
32
  }
11
33
 
12
34
  /* マージン・パディングをリセット */
@@ -23,33 +45,23 @@ figure,
23
45
  blockquote,
24
46
  dl,
25
47
  dd {
26
- margin: 0;
27
- padding: 0;
28
- }
29
-
30
- /* html / body 基本設定 */
31
- html {
32
- -webkit-text-size-adjust: 100%;
33
- text-size-adjust: 100%;
48
+ margin: 0;
49
+ padding: 0;
34
50
  }
35
51
 
36
52
 
37
- main {
38
- min-height: 100vh;
39
- }
40
-
41
53
  /* リスト */
42
54
  ul,
43
55
  ol {
44
- list-style: none;
45
- margin: 0;
46
- padding: 0;
56
+ list-style: none;
57
+ margin: 0;
58
+ padding: 0;
47
59
  }
48
60
 
49
61
  /* リンク */
50
62
  a {
51
- color: inherit;
52
- text-decoration: none;
63
+ color: inherit;
64
+ text-decoration: none;
53
65
  }
54
66
 
55
67
  /* 画像・メディア */
@@ -58,8 +70,8 @@ picture,
58
70
  video,
59
71
  canvas,
60
72
  svg {
61
- display: block;
62
- max-width: 100%;
73
+ display: block;
74
+ max-width: 100%;
63
75
  }
64
76
 
65
77
  /* フォーム要素 */
@@ -67,38 +79,38 @@ button,
67
79
  input,
68
80
  select,
69
81
  textarea {
70
- font: inherit;
71
- color: inherit;
72
- background: none;
73
- border: none;
74
- outline: none;
82
+ font: inherit;
83
+ color: inherit;
84
+ background: none;
85
+ border: none;
86
+ outline: none;
75
87
  }
76
88
 
77
89
  /* button */
78
90
  button {
79
- cursor: pointer;
91
+ cursor: pointer;
80
92
  }
81
93
 
82
94
  /* textarea */
83
95
  textarea {
84
- resize: vertical;
96
+ resize: vertical;
85
97
  }
86
98
 
87
99
  input::placeholder,
88
100
  textarea::placeholder {
89
- color: var(--color-on-surface-variant, #161D1D80);
90
- opacity: 1;
101
+ color: var(--color-on-surface-variant, #161D1D80);
102
+ opacity: 1;
91
103
  }
92
104
 
93
105
 
94
106
  /* テーブル */
95
107
  table {
96
- border-collapse: collapse;
97
- border-spacing: 0;
108
+ border-collapse: collapse;
109
+ border-spacing: 0;
98
110
  }
99
111
 
100
112
  /* アクセシビリティ */
101
113
  :focus-visible {
102
- outline: 2px solid var(--color-primary, #00a1a1);
103
- outline-offset: 2px;
104
- }
114
+ outline: 2px solid var(--color-primary, #00a1a1);
115
+ outline-offset: 2px;
116
+ }
@@ -1,23 +1,28 @@
1
1
  @font-face {
2
- font-family: "Noto Sans JP";
3
- src: url("../fonts/NotoSansJP-Regular.woff2") format("woff2");
4
- font-weight: 400;
5
- font-style: normal;
6
- font-display: swap;
2
+ font-family: "Noto Sans JP";
3
+ src: url("../fonts/NotoSansJP-Regular.woff2") format("woff2");
4
+ font-weight: 400;
5
+ font-style: normal;
6
+ font-display: swap;
7
7
  }
8
8
 
9
9
  @font-face {
10
- font-family: "Noto Sans JP";
11
- src: url("../fonts/NotoSansJP-Medium.woff2") format("woff2");
12
- font-weight: 500;
13
- font-style: normal;
14
- font-display: swap;
10
+ font-family: "Noto Sans JP";
11
+ src: url("../fonts/NotoSansJP-Medium.woff2") format("woff2");
12
+ font-weight: 500;
13
+ font-style: normal;
14
+ font-display: swap;
15
15
  }
16
16
 
17
17
  @font-face {
18
- font-family: "Noto Sans JP";
19
- src: url("../fonts/NotoSansJP-Bold.woff2") format("woff2");
20
- font-weight: 600;
21
- font-style: normal;
22
- font-display: swap;
18
+ font-family: "Noto Sans JP";
19
+ src: url("../fonts/NotoSansJP-Bold.woff2") format("woff2");
20
+ font-weight: 600;
21
+ font-style: normal;
22
+ font-display: swap;
23
23
  }
24
+
25
+
26
+ :root{
27
+ --font: "Noto Sans JP", sans-serif;
28
+ }
@@ -0,0 +1,10 @@
1
+ /* styles.css */
2
+
3
+ /* 1. tokens */
4
+ @import "./tokens.css";
5
+
6
+ /* 2. base */
7
+ @import "./base.css";
8
+
9
+ /* 3. font */
10
+ @import "./font.css";
@@ -0,0 +1,133 @@
1
+ :root {
2
+ /* font */
3
+ --font-size-12: 12px;
4
+ --font-size-14: 14px;
5
+ --font-size-16: 16px;
6
+ --font-size-18: 18px;
7
+ --font-size-20: 20px;
8
+ --font-size-24: 24px;
9
+ --font-size-28: 28px;
10
+ --font-size-40: 40px;
11
+ --font-size-48: 48px;
12
+
13
+ --line-tight: 1.2;
14
+ --line-snug: 1.35;
15
+ --line-normal: 1.6;
16
+
17
+ --font-weight-regular: 400;
18
+ --font-weight-medium: 500;
19
+ --font-weight-bold: 600;
20
+ --font-weight-black: 700;
21
+
22
+ /* ===== Radius ===== */
23
+ --radius-xs: 4px;
24
+ --radius-sm: 8px;
25
+ --radius-md: 16px;
26
+ --radius-max: 999px;
27
+
28
+ /* ===== Space ===== */
29
+ --space-xs: 4px;
30
+ --space-sm: 8px;
31
+ --space-md: 12px;
32
+ --space-lg: 16px;
33
+ --space-xl: 24px;
34
+ --space-2xl: 32px;
35
+ --space-3xl: 48px;
36
+ --space-4xl: 64px;
37
+ --space-5xl: 96px;
38
+ --gap-sm: 16px;
39
+ --gap-md: 32px;
40
+ --gap-lg: 64px;
41
+
42
+ /* ===== Layout ===== */
43
+ --container: 1120px;
44
+
45
+ /* ===== Color ===== */
46
+ --color-primary: #00a1a1;
47
+ --color-on-primary: #ffffff;
48
+ --color-primary-container: #9cf1f0;
49
+ --color-on-primary-container: #004f4f;
50
+
51
+ --color-secondary: #7b9695;
52
+ --color-on-secondary: #ffffff;
53
+ --color-secondary-container: #cce8e7;
54
+ --color-on-secondary-container: #324b4b;
55
+
56
+ --color-error: #ff5449;
57
+ --color-on-error: #ffffff;
58
+ --color-error-container: #ffdad6;
59
+ --color-on-error-container: #93000a;
60
+
61
+ --color-background: #f4fbfa;
62
+ --color-on-background: #161d1d;
63
+
64
+ --color-surface: #f4fbfabf;
65
+ --color-on-surface: #161d1d;
66
+ --color-on-surface-variant: #161d1d80;
67
+ --color-surface-container: #e9efeebf;
68
+
69
+ --color-border: #bbcccc;
70
+ --color-scrim: rgba(129, 129, 129, 0.25);
71
+
72
+ --color-shadow-l: rgba(255, 255, 255, 1);
73
+ --color-shadow-d: rgba(0, 0, 0, 0.2);
74
+
75
+ /* ===== Effect ===== */
76
+ --blur: blur(8px);
77
+
78
+ --shadow-sm:
79
+ -2px -2px 2px 1px var(--color-shadow-l),
80
+ 2px 2px 2px 1px var(--color-shadow-d);
81
+
82
+ --shadow-sm-in:
83
+ inset -2px -2px 2px var(--color-shadow-l),
84
+ inset 2px 2px 2px var(--color-shadow-d);
85
+
86
+ --shadow-md:
87
+ -2px -2px 4px 2px var(--color-shadow-l),
88
+ 2px 2px 4px 2px var(--color-shadow-d);
89
+
90
+ --shadow-md-in:
91
+ inset -4px -4px 4px var(--color-shadow-l),
92
+ inset 4px 4px 4px var(--color-shadow-d);
93
+
94
+ --shadow-lg:
95
+ -4px -4px 16px 8px var(--color-shadow-l),
96
+ 4px 4px 16px 8px var(--color-shadow-d);
97
+
98
+ --shadow-surface-lg:
99
+ 0px 4px 16px 8px var(--color-shadow-d);
100
+ }
101
+
102
+
103
+
104
+ html[data-theme="dark"] {
105
+ --color-primary: #80d5d4;
106
+ --color-on-primary: #003737;
107
+ --color-primary-container: #004f4f;
108
+ --color-on-primary-container: #9cf1f0;
109
+
110
+ --color-secondary: #b0cccb;
111
+ --color-on-secondary: #1b3534;
112
+ --color-secondary-container: #324b4b;
113
+ --color-on-secondary-container: #cce8e7;
114
+
115
+ --color-error: #ffb4ab;
116
+ --color-on-error: #690005;
117
+ --color-error-container: #93000a;
118
+ --color-on-error-container: #ffdad6;
119
+
120
+ --color-background: #2b3a38;
121
+ --color-on-background: #dde4e3;
122
+
123
+ --color-surface: #2b3a38bf;
124
+ --color-on-surface: #dde4e3;
125
+ --color-on-surface-variant: #dde4e380;
126
+ --color-surface-container: #262d2d;
127
+
128
+ --color-border: #889392;
129
+ --color-scrim: rgba(0, 0, 0, 0.25);
130
+
131
+ --color-shadow-l: rgba(255, 255, 255, 0.25);
132
+ --color-shadow-d: rgba(0, 0, 0, 1);
133
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lucentia-ui",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "description": "React UI design token and component system based on neumorphism, featuring two color themes: light and dark.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",