@k8o/arte-odyssey 0.0.6 → 0.1.0

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 ADDED
@@ -0,0 +1,266 @@
1
+ # @k8o/arte-odyssey
2
+
3
+ A modern React UI component library built with TypeScript, Tailwind CSS, and accessibility in mind.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @k8o/arte-odyssey
9
+ # or
10
+ pnpm add @k8o/arte-odyssey
11
+ # or
12
+ yarn add @k8o/arte-odyssey
13
+ ```
14
+
15
+ ## Peer Dependencies
16
+
17
+ Make sure you have the following peer dependencies installed:
18
+
19
+ ```bash
20
+ npm install react react-dom typescript tailwindcss
21
+ ```
22
+
23
+ Required versions:
24
+ - React ≥19.0.0
25
+ - TypeScript ≥5.9.0
26
+ - Tailwind CSS ≥4.0.0
27
+
28
+ ## Quick Start
29
+
30
+ 1. Install the package and import the CSS:
31
+
32
+ ```tsx
33
+ // In your main CSS file or component
34
+ import '@k8o/arte-odyssey/styles.css';
35
+ ```
36
+
37
+ 2. Use components in your React app:
38
+
39
+ ```tsx
40
+ import { Button, Card } from '@k8o/arte-odyssey';
41
+
42
+ function App() {
43
+ return (
44
+ <Card>
45
+ <h1>Welcome to ArteOdyssey</h1>
46
+ <Button variant="primary" onClick={() => alert('Hello!')}>
47
+ Click me
48
+ </Button>
49
+ </Card>
50
+ );
51
+ }
52
+ ```
53
+
54
+ ## Component Categories
55
+
56
+ ### Layout & Navigation
57
+ - **Accordion** - Collapsible content panels
58
+ - **Breadcrumb** - Navigation path indicator
59
+ - **Card** - Flexible content container
60
+ - **Separator** - Visual content divider
61
+ - **Tabs** - Tab-based content organization
62
+
63
+ ### Form Controls
64
+ - **Autocomplete** - Search with suggestions
65
+ - **Checkbox** - Multi-selection input
66
+ - **Form Control** - Form field wrapper with label and validation
67
+ - **Number Field** - Numeric input with controls
68
+ - **Radio** - Single selection from options
69
+ - **Range Field** - Slider input control
70
+ - **Select** - Dropdown selection
71
+ - **Text Field** - Single-line text input
72
+ - **Textarea** - Multi-line text input
73
+
74
+ ### Buttons & Links
75
+ - **Button** - Primary action button
76
+ - **Icon Button** - Button with icon only
77
+ - **Link Button** - Button styled as link
78
+ - **Anchor** - External link component
79
+ - **Icon Link** - Link with icon
80
+
81
+ ### Feedback & Status
82
+ - **Alert** - Important messages and notifications
83
+ - **Toast** - Temporary notification messages
84
+ - **Progress** - Progress indication
85
+ - **Baseline Status** - Web standard support indicator
86
+
87
+ ### Overlays & Modals
88
+ - **Dialog** - Modal dialog boxes
89
+ - **Drawer** - Slide-out panel
90
+ - **Modal** - Overlay modal component
91
+ - **Popover** - Floating content container
92
+ - **Tooltip** - Contextual help text
93
+ - **Dropdown Menu** - Action menu component
94
+
95
+ ### Data Display
96
+ - **Code** - Formatted code display
97
+ - **Heading** - Typography heading component
98
+ - **List Box** - Selectable list component
99
+ - **Text Tag** - Labeled text elements
100
+
101
+ ### Utilities
102
+ - **Error Boundary** - Error handling wrapper
103
+ - **Providers** - Context providers for the library
104
+ - **Scroll Linked** - Scroll-triggered animations
105
+ - **Icons** - Icon component collection
106
+
107
+ ## Usage Examples
108
+
109
+ ### Basic Button
110
+
111
+ ```tsx
112
+ import { Button } from '@k8o/arte-odyssey';
113
+
114
+ <Button variant="primary" size="medium">
115
+ Primary Button
116
+ </Button>
117
+ ```
118
+
119
+ ### Form with Validation
120
+
121
+ ```tsx
122
+ import { FormControl, TextField, Button } from '@k8o/arte-odyssey';
123
+
124
+ <form>
125
+ <FormControl label="Email" required>
126
+ <TextField
127
+ type="email"
128
+ placeholder="Enter your email"
129
+ required
130
+ />
131
+ </FormControl>
132
+ <Button type="submit">Submit</Button>
133
+ </form>
134
+ ```
135
+
136
+ ### Modal Dialog
137
+
138
+ ```tsx
139
+ import { Dialog, Button } from '@k8o/arte-odyssey';
140
+ import { useState } from 'react';
141
+
142
+ function MyComponent() {
143
+ const [isOpen, setIsOpen] = useState(false);
144
+
145
+ return (
146
+ <>
147
+ <Button onClick={() => setIsOpen(true)}>
148
+ Open Dialog
149
+ </Button>
150
+ <Dialog
151
+ isOpen={isOpen}
152
+ onClose={() => setIsOpen(false)}
153
+ title="Confirm Action"
154
+ >
155
+ <p>Are you sure you want to continue?</p>
156
+ <Button onClick={() => setIsOpen(false)}>
157
+ Confirm
158
+ </Button>
159
+ </Dialog>
160
+ </>
161
+ );
162
+ }
163
+ ```
164
+
165
+ ## Granular Imports
166
+
167
+ ArteOdyssey supports granular imports to optimize your bundle size:
168
+
169
+ ```tsx
170
+ // Import specific components
171
+ import { Button } from '@k8o/arte-odyssey/button';
172
+ import { Card } from '@k8o/arte-odyssey/card';
173
+
174
+ // Import specific hooks
175
+ import { useClickAway } from '@k8o/arte-odyssey/hooks/click-away';
176
+ import { useLocalStorage } from '@k8o/arte-odyssey/hooks/local-storage';
177
+ ```
178
+
179
+ ## Custom Hooks
180
+
181
+ The library includes several useful hooks:
182
+
183
+ - **useClickAway** - Detect clicks outside an element
184
+ - **useClipboard** - Clipboard operations
185
+ - **useHash** - URL hash management
186
+ - **useInterval** - Interval timer management
187
+ - **useLocalStorage** - Local storage with React state
188
+ - **useScrollDirection** - Scroll direction detection
189
+ - **useStep** - Step-based state management
190
+ - **useTimeout** - Timeout management
191
+ - **useWindowSize** - Window size tracking
192
+
193
+ ## TypeScript Support
194
+
195
+ All components are fully typed with TypeScript. The library exports comprehensive type definitions for all props and APIs.
196
+
197
+ ```tsx
198
+ import type { ButtonProps } from '@k8o/arte-odyssey';
199
+
200
+ const CustomButton: React.FC<ButtonProps> = (props) => {
201
+ return <Button {...props} />;
202
+ };
203
+ ```
204
+
205
+ ## Accessibility
206
+
207
+ All components follow WCAG accessibility guidelines:
208
+
209
+ - Semantic HTML elements
210
+ - Proper ARIA attributes
211
+ - Keyboard navigation support
212
+ - Screen reader compatibility
213
+ - Focus management
214
+ - Color contrast compliance
215
+
216
+ ## Styling & Customization
217
+
218
+ Components are built with Tailwind CSS and support customization through:
219
+
220
+ - CSS custom properties
221
+ - Tailwind utility classes
222
+ - Theme configuration
223
+ - Style overrides
224
+
225
+ ## Development
226
+
227
+ For local development and contributing:
228
+
229
+ ```bash
230
+ # Install dependencies
231
+ pnpm install
232
+
233
+ # Start Storybook for component development
234
+ pnpm storybook
235
+
236
+ # Run tests
237
+ pnpm test
238
+
239
+ # Build the library
240
+ pnpm build
241
+
242
+ # Type checking
243
+ pnpm typecheck
244
+
245
+ # Linting and formatting
246
+ pnpm check:write
247
+ ```
248
+
249
+ ## Browser Support
250
+
251
+ - Chrome (latest)
252
+ - Firefox (latest)
253
+ - Safari (latest)
254
+ - Edge (latest)
255
+
256
+ ## License
257
+
258
+ MIT License - see [LICENSE](../../LICENSE) for details.
259
+
260
+ ## Contributing
261
+
262
+ Contributions are welcome! Please see the [main repository](../../README.md) for contribution guidelines.
263
+
264
+ ---
265
+
266
+ Built with ❤️ using React, TypeScript, and Tailwind CSS.
@@ -7,7 +7,8 @@ type Props = {
7
7
  isDisabled: boolean;
8
8
  isRequired: boolean;
9
9
  options: readonly Option[];
10
- value: string[];
10
+ value?: string[];
11
+ defaultValue?: string[];
11
12
  onChange: (value: string[]) => void;
12
13
  };
13
14
  export declare const Autocomplete: FC<Props>;
@@ -11,12 +11,27 @@ const Autocomplete = ({
11
11
  isRequired,
12
12
  options,
13
13
  value,
14
+ defaultValue,
14
15
  onChange
15
16
  }) => {
17
+ const [internalValue, setInternalValue] = useState(
18
+ defaultValue || []
19
+ );
20
+ const isControlled = value !== void 0;
21
+ const currentValue = isControlled ? value : internalValue;
16
22
  const ref = useRef(null);
17
23
  const [open, setOpen] = useState(false);
18
24
  const [text, setText] = useState("");
19
25
  const [selectIndex, setSelectIndex] = useState();
26
+ const handleChange = useCallback(
27
+ (newValue) => {
28
+ if (!isControlled) {
29
+ setInternalValue(newValue);
30
+ }
31
+ onChange(newValue);
32
+ },
33
+ [isControlled, onChange]
34
+ );
20
35
  const filteredOptions = options.filter(
21
36
  (option) => option.label.includes(text)
22
37
  );
@@ -48,7 +63,7 @@ const Autocomplete = ({
48
63
  children: [
49
64
  /* @__PURE__ */ jsxs("div", { className: "flex min-h-12 items-center justify-between gap-2 px-3 py-2", children: [
50
65
  /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-wrap gap-1", children: [
51
- value.map((text2) => {
66
+ currentValue.map((text2) => {
52
67
  const label = options.find(
53
68
  (option) => option.value === text2
54
69
  )?.label;
@@ -66,7 +81,7 @@ const Autocomplete = ({
66
81
  onClick: (e) => {
67
82
  e.stopPropagation();
68
83
  reset();
69
- onChange(value.filter((v) => v !== text2));
84
+ handleChange(currentValue.filter((v) => v !== text2));
70
85
  },
71
86
  size: "sm",
72
87
  children: /* @__PURE__ */ jsx(CloseIcon, { size: "sm" })
@@ -115,7 +130,7 @@ const Autocomplete = ({
115
130
  onKeyDown: (e) => {
116
131
  if (e.key === "Backspace" && text.length === 0) {
117
132
  reset();
118
- onChange(value.slice(0, -1));
133
+ handleChange(currentValue.slice(0, -1));
119
134
  return;
120
135
  }
121
136
  if (e.key === "ArrowDown") {
@@ -143,12 +158,14 @@ const Autocomplete = ({
143
158
  if (!selected) {
144
159
  return;
145
160
  }
146
- if (value.includes(selected.value)) {
147
- onChange(value.filter((v) => v !== selected.value));
161
+ if (currentValue.includes(selected.value)) {
162
+ handleChange(
163
+ currentValue.filter((v) => v !== selected.value)
164
+ );
148
165
  reset();
149
166
  return;
150
167
  }
151
- onChange([...value, selected.value]);
168
+ handleChange([...currentValue, selected.value]);
152
169
  reset();
153
170
  return;
154
171
  }
@@ -160,13 +177,13 @@ const Autocomplete = ({
160
177
  }
161
178
  )
162
179
  ] }),
163
- value.length > 0 && /* @__PURE__ */ jsx(
180
+ currentValue.length > 0 && /* @__PURE__ */ jsx(
164
181
  IconButton,
165
182
  {
166
183
  label: "\u3059\u3079\u3066\u9589\u3058\u308B",
167
184
  onClick: (e) => {
168
185
  e.stopPropagation();
169
- onChange([]);
186
+ handleChange([]);
170
187
  },
171
188
  size: "sm",
172
189
  children: /* @__PURE__ */ jsx(CloseIcon, { size: "sm" })
@@ -181,13 +198,13 @@ const Autocomplete = ({
181
198
  children: /* @__PURE__ */ jsxs("ul", { className: "max-h-96 py-2", id: `${id}_listbox`, children: [
182
199
  filteredOptions.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-3 py-2 text-fg-mute", children: "\u8A72\u5F53\u306A\u3057" }),
183
200
  filteredOptions.map((option, idx) => {
184
- const selected = value.includes(option.value);
201
+ const selected = currentValue.includes(option.value);
185
202
  return /* @__PURE__ */ jsx(
186
203
  "li",
187
204
  {
188
205
  className: cn(
189
206
  "cursor-pointer px-3 py-2",
190
- selected && "bg-primary-bg text-fg-inverse",
207
+ selected && "bg-primary-bg",
191
208
  selectIndex === idx && !selected && "bg-bg-emphasize",
192
209
  selectIndex === idx && selected && "hover:bg-primary-bg/90"
193
210
  ),
@@ -196,10 +213,12 @@ const Autocomplete = ({
196
213
  e.stopPropagation();
197
214
  reset();
198
215
  if (selected) {
199
- onChange(value.filter((v) => v !== option.value));
216
+ handleChange(
217
+ currentValue.filter((v) => v !== option.value)
218
+ );
200
219
  return;
201
220
  }
202
- onChange([...value, option.value]);
221
+ handleChange([...currentValue, option.value]);
203
222
  },
204
223
  onKeyDown: (e) => {
205
224
  e.preventDefault();
@@ -2,6 +2,7 @@ import { type ChangeEventHandler, type FC } from 'react';
2
2
  type Props = {
3
3
  label: string;
4
4
  value: boolean;
5
+ defaultChecked?: boolean;
5
6
  onChange: ChangeEventHandler<HTMLInputElement>;
6
7
  };
7
8
  export declare const Checkbox: FC<Props>;
@@ -2,7 +2,12 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
3
  import { cn } from "./../../../helpers/cn";
4
4
  import { CheckIcon } from "../../icons";
5
- const Checkbox = ({ label, value, onChange }) => {
5
+ const Checkbox = ({
6
+ label,
7
+ value,
8
+ defaultChecked,
9
+ onChange
10
+ }) => {
6
11
  const [isFocus, setIsFocus] = useState(false);
7
12
  return /* @__PURE__ */ jsxs("label", { className: "inline-flex cursor-pointer items-center gap-2", children: [
8
13
  /* @__PURE__ */ jsx(
@@ -10,6 +15,7 @@ const Checkbox = ({ label, value, onChange }) => {
10
15
  {
11
16
  checked: value,
12
17
  className: "sr-only",
18
+ defaultChecked,
13
19
  onBlur: () => {
14
20
  setIsFocus(false);
15
21
  },
@@ -6,6 +6,7 @@ type Props = {
6
6
  isDisabled: boolean;
7
7
  isRequired: boolean;
8
8
  value: number;
9
+ defaultValue?: number;
9
10
  onChange: (value: number) => void;
10
11
  step?: number;
11
12
  precision?: number;
@@ -10,6 +10,7 @@ const NumberField = ({
10
10
  isDisabled,
11
11
  isRequired,
12
12
  value,
13
+ defaultValue,
13
14
  onChange,
14
15
  step = 1,
15
16
  precision = 0,
@@ -17,8 +18,12 @@ const NumberField = ({
17
18
  min = -9007199254740991,
18
19
  placeholder
19
20
  }) => {
20
- const [displayValue, setDisplayValue] = useState(value.toFixed(precision));
21
- const [prevValue, setPrevValue] = useState(value);
21
+ const [displayValue, setDisplayValue] = useState(
22
+ defaultValue !== void 0 ? defaultValue.toFixed(precision) : value.toFixed(precision)
23
+ );
24
+ const [prevValue, setPrevValue] = useState(
25
+ defaultValue !== void 0 ? defaultValue : value
26
+ );
22
27
  if (value !== prevValue) {
23
28
  setDisplayValue(value.toFixed(precision));
24
29
  setPrevValue(value);
@@ -4,6 +4,7 @@ type Props = {
4
4
  labelId: string;
5
5
  isDisabled: boolean;
6
6
  value: string;
7
+ defaultValue?: string;
7
8
  onChange: ChangeEventHandler<HTMLInputElement>;
8
9
  options: readonly Option[];
9
10
  };
@@ -4,6 +4,7 @@ const Radio = ({
4
4
  labelId,
5
5
  isDisabled,
6
6
  value,
7
+ defaultValue,
7
8
  onChange,
8
9
  options
9
10
  }) => {
@@ -32,6 +33,7 @@ const Radio = ({
32
33
  "cursor-pointer",
33
34
  "disabled:cursor-not-allowed disabled:bg-bg-mute"
34
35
  ),
36
+ defaultChecked: defaultValue === option.value,
35
37
  disabled: isDisabled,
36
38
  onChange,
37
39
  type: "radio",
@@ -6,6 +6,7 @@ type Props = {
6
6
  isDisabled: boolean;
7
7
  isRequired: boolean;
8
8
  value: number;
9
+ defaultValue?: number;
9
10
  onChange: (value: number) => void;
10
11
  step?: number;
11
12
  max?: number;
@@ -7,6 +7,7 @@ const RangeField = ({
7
7
  isDisabled,
8
8
  isRequired,
9
9
  value,
10
+ defaultValue,
10
11
  onChange,
11
12
  step = 1,
12
13
  max = 100,
@@ -30,6 +31,7 @@ const RangeField = ({
30
31
  "disabled:cursor-not-allowed disabled:opacity-50",
31
32
  isInvalid && "ring-2 ring-border-error"
32
33
  ),
34
+ defaultValue,
33
35
  disabled: isDisabled,
34
36
  id,
35
37
  max,
@@ -8,6 +8,7 @@ type Props = {
8
8
  isRequired: boolean;
9
9
  options: readonly Option[];
10
10
  value: string;
11
+ defaultValue?: string;
11
12
  onChange: ChangeEventHandler<HTMLSelectElement>;
12
13
  };
13
14
  export declare const Select: FC<Props>;
@@ -9,6 +9,7 @@ const Select = ({
9
9
  isRequired,
10
10
  options,
11
11
  value,
12
+ defaultValue,
12
13
  onChange
13
14
  }) => {
14
15
  return /* @__PURE__ */ jsxs("div", { className: "relative h-fit w-full", children: [
@@ -24,6 +25,7 @@ const Select = ({
24
25
  "disabled:cursor-not-allowed disabled:border-border-mute disabled:bg-bg-mute disabled:hover:bg-bg-mute",
25
26
  "focus-visible:border-transparent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info"
26
27
  ),
28
+ defaultValue,
27
29
  disabled: isDisabled,
28
30
  id,
29
31
  onChange,
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx } from "react/jsx-runtime";
2
3
  import { MotionConfig } from "motion/react";
3
4
  import { ToastProvider } from "../toast";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k8o/arte-odyssey",
3
- "version": "0.0.6",
3
+ "version": "0.1.0",
4
4
  "description": "k8o's react ui library",
5
5
  "author": "k8o <kosakanoki@gmail.com>",
6
6
  "keywords": [
@@ -52,36 +52,36 @@
52
52
  "*.css"
53
53
  ],
54
54
  "dependencies": {
55
- "@floating-ui/react": "0.27.15",
55
+ "@floating-ui/react": "0.27.16",
56
56
  "baseline-status": "1.0.11",
57
57
  "clsx": "2.1.1",
58
58
  "esbuild": "0.25.9",
59
- "lucide-react": "0.539.0",
59
+ "lucide-react": "0.542.0",
60
60
  "motion": "12.23.12",
61
61
  "react-error-boundary": "6.0.0",
62
62
  "tailwind-merge": "3.3.1"
63
63
  },
64
64
  "devDependencies": {
65
- "@chromatic-com/storybook": "4.1.0",
66
- "@storybook/addon-a11y": "9.1.2",
67
- "@storybook/addon-docs": "9.1.2",
68
- "@storybook/addon-vitest": "9.1.2",
69
- "@storybook/react-vite": "9.1.2",
65
+ "@chromatic-com/storybook": "4.1.1",
66
+ "@storybook/addon-a11y": "9.1.4",
67
+ "@storybook/addon-docs": "9.1.4",
68
+ "@storybook/addon-vitest": "9.1.4",
69
+ "@storybook/react-vite": "9.1.4",
70
70
  "@tailwindcss/postcss": "4.1.12",
71
71
  "@testing-library/dom": "10.4.1",
72
72
  "@testing-library/react": "16.3.0",
73
- "@types/react": "19.1.10",
74
- "@types/react-dom": "19.1.7",
75
- "@vitejs/plugin-react-swc": "4.0.0",
73
+ "@types/react": "19.1.12",
74
+ "@types/react-dom": "19.1.9",
75
+ "@vitejs/plugin-react-swc": "4.0.1",
76
76
  "@vitest/browser": "3.2.4",
77
77
  "@vitest/ui": "3.2.4",
78
78
  "postcss": "8.5.6",
79
79
  "react": "19.1.1",
80
80
  "react-dom": "19.1.1",
81
- "storybook": "9.1.2",
81
+ "storybook": "9.1.4",
82
82
  "storybook-addon-mock-date": "1.0.1",
83
83
  "tailwindcss": "4.1.12",
84
- "vite": "7.1.2",
84
+ "vite": "7.1.4",
85
85
  "vitest": "3.2.4"
86
86
  },
87
87
  "peerDependencies": {