lucentia-ui 0.2.8 → 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.
Files changed (33) hide show
  1. package/README.md +6 -0
  2. package/dist/components/Button/types.d.ts +5 -2
  3. package/dist/components/Extra/ThemeSwitch/ThemeSwitch.d.ts +1 -3
  4. package/dist/components/Extra/ThemeSwitch/ThemeSwitch.js +7 -15
  5. package/dist/components/Extra/ThemeSwitch/ThemeSwitch.module.css +91 -77
  6. package/dist/components/IconButton/IconButton.d.ts +3 -0
  7. package/dist/components/IconButton/IconButton.js +18 -0
  8. package/dist/components/IconButton/IconButton.module.css +154 -0
  9. package/dist/components/IconButton/IconButton.stories.d.ts +9 -0
  10. package/dist/components/IconButton/IconButton.stories.js +60 -0
  11. package/dist/components/IconButton/index.d.ts +2 -0
  12. package/dist/components/IconButton/index.js +2 -0
  13. package/dist/components/IconButton/types.d.ts +13 -0
  14. package/dist/components/IconButton/types.js +1 -0
  15. package/dist/components/ThemeProvider/ThemeProvider.d.ts +4 -3
  16. package/dist/components/ThemeProvider/ThemeProvider.js +24 -3
  17. package/dist/components/ThemeProvider/context.d.ts +7 -0
  18. package/dist/components/ThemeProvider/context.js +3 -0
  19. package/dist/components/ThemeProvider/index.d.ts +1 -0
  20. package/dist/components/ThemeProvider/index.js +1 -0
  21. package/dist/components/ThemeProvider/useTheme.d.ts +1 -0
  22. package/dist/components/ThemeProvider/useTheme.js +10 -0
  23. package/dist/components/ThemeProvider/useThemeStorage.d.ts +2 -0
  24. package/dist/components/ThemeProvider/useThemeStorage.js +7 -0
  25. package/dist/icons/Home.d.ts +8 -0
  26. package/dist/icons/Home.js +5 -0
  27. package/dist/index.d.ts +1 -0
  28. package/dist/index.js +1 -0
  29. package/dist/styles/base.css +46 -34
  30. package/dist/styles/font.css +20 -15
  31. package/dist/styles/styles.css +10 -0
  32. package/dist/styles/tokens.css +133 -0
  33. package/package.json +1 -1
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,5 +1,8 @@
1
+ import type React from "react";
2
+ export type ButtonVariant = "ghost" | "primary" | "secondary" | "danger";
3
+ export type ButtonSize = "sm" | "md";
1
4
  export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
2
- variant?: "ghost" | "primary" | "secondary" | "danger";
3
- size?: "sm" | "md";
5
+ variant?: ButtonVariant;
6
+ size?: ButtonSize;
4
7
  fullWidth?: boolean;
5
8
  }
@@ -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,98 +1,112 @@
1
- .themeSwitch {
2
- font-size: 18px;
3
- position: relative;
4
- display: inline-block;
5
- width: 3.5em;
6
- height: 2em;
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-color: #333;
24
- transition: 0.4s;
25
- border-radius: 30px;
26
- 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;
27
27
  }
28
28
 
29
29
  .circle {
30
- position: absolute;
31
- height: 1.4em;
32
- width: 1.4em;
33
- border-radius: 20px;
34
- left: 0.3em;
35
- bottom: 0.3em;
36
- background-color: #fff000;
37
- 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;
38
38
  }
39
39
 
40
- /* ThemeSwitch.module.css */
40
+ /* checked state */
41
+ .switch input:checked+.slider .circle {
42
+ transform: translateX(1.5em);
43
+ }
41
44
 
42
- /* inputのクラス名をTSXと合わせる */
43
- .themeSwitchCheckbox {
44
- opacity: 0;
45
- width: 0;
46
- height: 0;
47
- position: absolute;
45
+ .switch input:checked+.slider .circle .shine {
46
+ transform: translate(0%, 0%);
48
47
  }
49
48
 
50
- /* チェック時の隣接セレクタを調整 */
51
- .themeSwitchCheckbox:checked + .slider .circle {
52
- transform: rotate(0deg) translateX(1.5em) !important;
49
+ .switch input:checked+.slider .circle .moon {
50
+ left: -10%;
51
+ opacity: 1;
52
+ transform: translateY(-60%);
53
53
  }
54
54
 
55
- .themeSwitchCheckbox:checked + .slider .circle .shine {
56
- 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;
57
67
  }
58
68
 
59
- .themeSwitchCheckbox:checked + .slider .circle .moon {
60
- left: -10%;
61
- opacity: 1;
62
- 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;
63
79
  }
64
80
 
65
- .moon {
66
- position: absolute;
67
- left: -100%;
68
- top: 50%;
69
- opacity: 0;
70
- background-color: #333;
71
- width: 1.25rem;
72
- height: 1.25rem;
73
- border-radius: 99999px;
74
- transform: translateY(-50%);
75
- transition: all 0.4s;
81
+ /* Individual shine positions */
82
+ .shine1 {
83
+ transform: translate(-50%, -375%);
76
84
  }
77
85
 
78
- .shine {
79
- display: block;
80
- position: absolute;
81
- top: 50%;
82
- left: 50%;
83
- width: 0.25rem;
84
- height: 0.25rem;
85
- background-color: #fff000;
86
- border-radius: 1rem;
87
- transition: all 0.4s;
88
- }
89
-
90
- /* 個別のshine配置 */
91
- .shine-1 { transform: translate(-50%, -375%); }
92
- .shine-2 { transform: translate(175%, -275%); }
93
- .shine-3 { transform: translate(275%, -50%); }
94
- .shine-4 { transform: translate(175%, 175%); }
95
- .shine-5 { transform: translate(-50%, 275%); }
96
- .shine-6 { transform: translate(-275%, 175%); }
97
- .shine-7 { transform: translate(-375%, -50%); }
98
- .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
+ }
@@ -0,0 +1,3 @@
1
+ import React from "react";
2
+ import type { IconButtonProps } from "./types";
3
+ export declare const IconButton: React.ForwardRefExoticComponent<IconButtonProps & React.RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import clsx from "clsx";
4
+ import styles from "./IconButton.module.css";
5
+ export const IconButton = React.forwardRef((props, ref) => {
6
+ const { icon, children, variant = "ghost", size = "md", className, ...rest } = props;
7
+ // a11y guard (dev only)
8
+ if (process.env.NODE_ENV !== "production" &&
9
+ !children &&
10
+ !rest["aria-label"]) {
11
+ console.warn("IconButton: icon-only usage requires `aria-label`.");
12
+ }
13
+ return (_jsxs("button", { ref: ref, className: clsx(styles.iconButton, styles[variant], styles[size], {
14
+ [styles.iconOnly]: !children,
15
+ [styles.withText]: !!children,
16
+ }, className), ...rest, children: [_jsx("span", { className: styles.icon, children: icon }), children && _jsx("span", { className: styles.text, children: children })] }));
17
+ });
18
+ IconButton.displayName = "IconButton";
@@ -0,0 +1,154 @@
1
+ .iconButton {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ gap: 0.5rem;
6
+ width: fit-content;
7
+ height: fit-content;
8
+ font-family: var(--font);
9
+ font-weight: var(--font-weight-medium);
10
+ border: 2px solid transparent;
11
+ cursor: pointer;
12
+
13
+ border-radius: var(--radius-sm);
14
+
15
+ transition:
16
+ box-shadow 0.2s ease,
17
+ opacity 0.2s ease,
18
+ background 0.25s ease-in-out,
19
+ color 0.25s ease;
20
+ }
21
+
22
+
23
+
24
+ /* ===== Variant ===== */
25
+ .ghost {
26
+ background: var(--color-surface);
27
+ color: var(--color-on-surface);
28
+ }
29
+ .ghost:hover:not(:disabled) {
30
+ background: var(--color-surface-container);
31
+ }
32
+
33
+
34
+ .primary {
35
+ background: var(--color-primary-container);
36
+ color: var(--color-on-primary-container);
37
+ }
38
+
39
+ .sm.primary:hover:not(:disabled) {
40
+ border: 2px solid var(--color-primary);
41
+ box-shadow:
42
+ 0 0 2px var(--color-primary),
43
+ 0 0 4px var(--color-primary);
44
+ }
45
+
46
+ .md.primary:hover:not(:disabled) {
47
+ border: 2px solid var(--color-primary);
48
+ box-shadow:
49
+ 0 0 4px var(--color-primary),
50
+ 0 0 8px var(--color-primary);
51
+ }
52
+
53
+ .md.primary:hover:not(:disabled) {
54
+ border: 2px solid var(--color-primary);
55
+ box-shadow:
56
+ 0 0 4px var(--color-primary),
57
+ 0 0 8px var(--color-primary);
58
+ }
59
+
60
+ .secondary {
61
+ background: var(--color-secondary-container);
62
+ color: var(--color-on-secondary-container);
63
+ }
64
+
65
+ .sm.secondary:hover:not(:disabled) {
66
+ border: 2px solid var(--color-secondary);
67
+ box-shadow:
68
+ 0 0 2px var(--color-secondary),
69
+ 0 0 4px var(--color-secondary);
70
+ }
71
+
72
+ .md.secondary:hover:not(:disabled) {
73
+ border: 2px solid var(--color-secondary);
74
+ box-shadow:
75
+ 0 0 4px var(--color-secondary),
76
+ 0 0 8px var(--color-secondary);
77
+ }
78
+
79
+
80
+ .danger {
81
+ background: var(--color-error-container);
82
+ color: var(--color-on-error-container);
83
+ }
84
+
85
+ .sm.danger:hover:not(:disabled) {
86
+ border: 2px solid var(--color-error);
87
+ box-shadow:
88
+ 0 0 2px var(--color-error),
89
+ 0 0 4px var(--color-error);
90
+ }
91
+
92
+ .md.danger:hover:not(:disabled) {
93
+ border: 2px solid var(--color-error);
94
+ box-shadow:
95
+ 0 0 4px var(--color-error),
96
+ 0 0 8px var(--color-error);
97
+ }
98
+
99
+ .md.danger:active:not(:disabled) {
100
+ box-shadow: none;
101
+ }
102
+
103
+
104
+ /* ===== Size ===== */
105
+
106
+ .sm {
107
+ padding: var(--space-xs) var(--space-lg);
108
+ font-size: var(--font-size-14);
109
+ box-shadow: var(--shadow-sm);
110
+ }
111
+
112
+ .md {
113
+ padding: var(--space-sm) var(--space-2xl);
114
+ font-size: var(--font-size-16);
115
+ box-shadow: var(--shadow-md);
116
+ }
117
+
118
+ /* icon-only */
119
+ .sm.iconOnly {
120
+ padding: var(--space-xs) var(--space-md);
121
+ }
122
+
123
+ .md.iconOnly {
124
+ padding: var(--space-sm) var(--space-md);
125
+ }
126
+
127
+
128
+
129
+ .icon {
130
+ display: inline-flex;
131
+ }
132
+
133
+ .text {
134
+ white-space: nowrap;
135
+ }
136
+
137
+
138
+
139
+ /* ===== State ===== */
140
+
141
+ .sm.iconButton:active:not(:disabled),
142
+ .md.iconButton:active:not(:disabled),
143
+ .iconButton[data-state="pressed"] {
144
+ box-shadow: none;
145
+ }
146
+
147
+ .iconButton:focus-visible {
148
+ outline: none;
149
+ }
150
+
151
+ .iconButton:disabled {
152
+ opacity: 0.5;
153
+ cursor: not-allowed;
154
+ }
@@ -0,0 +1,9 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { IconButton } from "./IconButton";
3
+ declare const meta: Meta<typeof IconButton>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof IconButton>;
6
+ export declare const Default: Story;
7
+ export declare const State: Story;
8
+ export declare const Variants: Story;
9
+ export declare const Sizes: Story;
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { IconButton } from "./IconButton";
3
+ // Storybook only
4
+ import { HomeIcon } from "../../icons/Home";
5
+ const meta = {
6
+ title: "Components/IconButton",
7
+ component: IconButton,
8
+ args: {
9
+ children: "IconButton",
10
+ variant: "ghost",
11
+ size: "md",
12
+ },
13
+ argTypes: {
14
+ variant: {
15
+ control: "radio",
16
+ options: ["ghost", "primary", "secondary", "danger"],
17
+ description: "Buttonの表示バリエーション",
18
+ },
19
+ size: {
20
+ control: "radio",
21
+ options: ["sm", "md"],
22
+ description: "Buttonのサイズ",
23
+ },
24
+ disabled: {
25
+ control: "boolean",
26
+ description: "無効状態",
27
+ },
28
+ onClick: {
29
+ action: "clicked",
30
+ },
31
+ },
32
+ };
33
+ export default meta;
34
+ export const Default = {
35
+ render: () => _jsx(IconButton, { icon: _jsx(HomeIcon, {}), variant: "ghost" }),
36
+ };
37
+ export const State = {
38
+ render: () => (_jsxs("div", { style: {
39
+ display: "flex",
40
+ flexDirection: "column",
41
+ alignItems: "flex-start",
42
+ gap: 32,
43
+ }, children: [_jsx(IconButton, { icon: _jsx(HomeIcon, {}), variant: "ghost", children: "Default" }), _jsx(IconButton, { icon: _jsx(HomeIcon, {}), variant: "ghost", style: { background: "var(--color-surface-container)" }, "data-state": "pressed", children: "Pressed" }), _jsx(IconButton, { icon: _jsx(HomeIcon, {}), variant: "ghost", disabled: true, children: "Disabled" })] })),
44
+ };
45
+ export const Variants = {
46
+ render: () => (_jsxs("div", { style: {
47
+ display: "flex",
48
+ flexDirection: "column",
49
+ alignItems: "flex-start",
50
+ gap: 32,
51
+ }, children: [_jsx(IconButton, { icon: _jsx(HomeIcon, {}), variant: "ghost", children: "Ghost" }), _jsx(IconButton, { icon: _jsx(HomeIcon, {}), variant: "primary", children: "Primary" }), _jsx(IconButton, { icon: _jsx(HomeIcon, {}), variant: "secondary", children: "Secondary" }), _jsx(IconButton, { icon: _jsx(HomeIcon, {}), variant: "danger", children: "Danger" })] })),
52
+ };
53
+ export const Sizes = {
54
+ render: () => (_jsxs("div", { style: {
55
+ display: "flex",
56
+ flexDirection: "column",
57
+ alignItems: "flex-start",
58
+ gap: 32,
59
+ }, children: [_jsx(IconButton, { icon: _jsx(HomeIcon, { size: 20 }), size: "sm", children: "Small" }), _jsx(IconButton, { icon: _jsx(HomeIcon, {}), size: "md", children: "Medium" })] })),
60
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./IconButton";
2
+ export * from "./types";
@@ -0,0 +1,2 @@
1
+ export * from "./IconButton";
2
+ export * from "./types";
@@ -0,0 +1,13 @@
1
+ import type React from "react";
2
+ import type { ButtonVariant, ButtonSize } from "../Button/types";
3
+ export interface IconButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
4
+ /** Icon element (required) */
5
+ icon: React.ReactNode;
6
+ /** Optional text */
7
+ children?: React.ReactNode;
8
+ /** Inherit from Button */
9
+ variant?: ButtonVariant;
10
+ size?: ButtonSize;
11
+ /** icon-only の場合に必須 */
12
+ "aria-label"?: string;
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -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
+ }
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ interface IconProps {
3
+ size?: number;
4
+ color?: string;
5
+ className?: string;
6
+ }
7
+ export declare const HomeIcon: React.FC<IconProps>;
8
+ export default HomeIcon;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export const HomeIcon = ({ size = 24, color = "currentColor", className = "", }) => {
3
+ return (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", className: className, children: _jsx("path", { d: "M13.5 14H10.5C9.94772 14 9.5 14.4477 9.5 15V20.5C9.5 21.0523 9.05228 21.5 8.5 21.5H4C3.44772 21.5 3 21.0523 3 20.5V10.931C3 10.6559 3.11335 10.3929 3.31338 10.204L11.3134 2.64848C11.6988 2.28449 12.3012 2.28449 12.6866 2.64848L20.6866 10.204C20.8866 10.3929 21 10.6559 21 10.931V20.5C21 21.0523 20.5523 21.5 20 21.5H15.5C14.9477 21.5 14.5 21.0523 14.5 20.5V15C14.5 14.4477 14.0523 14 13.5 14Z", fill: "currentColor" }) }));
4
+ };
5
+ export default HomeIcon;
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export * from "./components/Checkbox";
5
5
  export * from "./components/Divider";
6
6
  export * from "./components/Radio";
7
7
  export * from "./components/Input";
8
+ export * from "./components/IconButton";
8
9
  export * from "./components/Textarea";
9
10
  export * from "./components/Typography";
10
11
  export * from "./components/Select";
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ export * from "./components/Checkbox";
6
6
  export * from "./components/Divider";
7
7
  export * from "./components/Radio";
8
8
  export * from "./components/Input";
9
+ export * from "./components/IconButton";
9
10
  export * from "./components/Textarea";
10
11
  export * from "./components/Typography";
11
12
  export * from "./components/Select";
@@ -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.8",
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",