@imdz/react 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/OFL.txt +189 -0
- package/README.md +85 -2
- package/dist/accordion.d.ts +13 -0
- package/dist/accordion.js +12 -0
- package/dist/alert.d.ts +7 -0
- package/dist/alert.js +11 -0
- package/dist/avatar.d.ts +8 -0
- package/dist/avatar.js +21 -0
- package/dist/badge.d.ts +6 -0
- package/dist/badge.js +11 -0
- package/dist/button.d.ts +8 -0
- package/dist/button.js +11 -0
- package/dist/card.d.ts +6 -0
- package/dist/card.js +11 -0
- package/dist/checkbox.d.ts +3 -0
- package/dist/checkbox.js +12 -0
- package/dist/cx.d.ts +1 -0
- package/dist/cx.js +3 -0
- package/dist/dark.css +13 -0
- package/dist/dialog.d.ts +12 -0
- package/dist/dialog.js +24 -0
- package/dist/fonts/inter-latin-400-normal.woff2 +0 -0
- package/dist/fonts/inter-latin-600-normal.woff2 +0 -0
- package/dist/fonts/jetbrains-mono-latin-600-normal.woff2 +0 -0
- package/dist/fonts.css +24 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +17 -0
- package/dist/input.d.ts +7 -0
- package/dist/input.js +17 -0
- package/dist/progress.d.ts +8 -0
- package/dist/progress.js +13 -0
- package/dist/select.d.ts +7 -0
- package/dist/select.js +19 -0
- package/dist/skeleton.d.ts +7 -0
- package/dist/skeleton.js +12 -0
- package/dist/spinner.d.ts +7 -0
- package/dist/spinner.js +11 -0
- package/dist/style.d.ts +5 -0
- package/dist/style.js +26 -0
- package/dist/styles/accordion.d.ts +1 -0
- package/dist/styles/accordion.js +1 -0
- package/dist/styles/alert.d.ts +1 -0
- package/dist/styles/alert.js +1 -0
- package/dist/styles/avatar.d.ts +1 -0
- package/dist/styles/avatar.js +1 -0
- package/dist/styles/badge.d.ts +1 -0
- package/dist/styles/badge.js +1 -0
- package/dist/styles/button.d.ts +1 -0
- package/dist/styles/button.js +1 -0
- package/dist/styles/card.d.ts +1 -0
- package/dist/styles/card.js +1 -0
- package/dist/styles/checkbox.d.ts +1 -0
- package/dist/styles/checkbox.js +1 -0
- package/dist/styles/dialog.d.ts +1 -0
- package/dist/styles/dialog.js +1 -0
- package/dist/styles/field.d.ts +1 -0
- package/dist/styles/field.js +1 -0
- package/dist/styles/fonts.d.ts +1 -0
- package/dist/styles/fonts.js +1 -0
- package/dist/styles/progress.d.ts +1 -0
- package/dist/styles/progress.js +1 -0
- package/dist/styles/select.d.ts +1 -0
- package/dist/styles/select.js +1 -0
- package/dist/styles/skeleton.d.ts +1 -0
- package/dist/styles/skeleton.js +1 -0
- package/dist/styles/spinner.d.ts +1 -0
- package/dist/styles/spinner.js +1 -0
- package/dist/styles/switch.d.ts +1 -0
- package/dist/styles/switch.js +1 -0
- package/dist/styles/tabs.d.ts +1 -0
- package/dist/styles/tabs.js +1 -0
- package/dist/styles/textarea.d.ts +1 -0
- package/dist/styles/textarea.js +1 -0
- package/dist/styles/tooltip.d.ts +1 -0
- package/dist/styles/tooltip.js +1 -0
- package/dist/styles.css +872 -0
- package/dist/switch.d.ts +3 -0
- package/dist/switch.js +12 -0
- package/dist/tabs.d.ts +12 -0
- package/dist/tabs.js +36 -0
- package/dist/textarea.d.ts +7 -0
- package/dist/textarea.js +18 -0
- package/dist/tokens.css +49 -0
- package/dist/tooltip.d.ts +7 -0
- package/dist/tooltip.js +12 -0
- package/package.json +54 -4
- package/src/accordion.tsx +35 -0
- package/src/alert.tsx +33 -0
- package/src/avatar.tsx +37 -0
- package/src/badge.tsx +31 -0
- package/src/button.tsx +32 -0
- package/src/card.tsx +24 -0
- package/src/checkbox.tsx +18 -0
- package/src/cx.ts +3 -0
- package/src/dialog.tsx +61 -0
- package/src/index.ts +26 -0
- package/src/input.tsx +50 -0
- package/src/progress.tsx +45 -0
- package/src/select.tsx +55 -0
- package/src/skeleton.tsx +20 -0
- package/src/spinner.tsx +23 -0
- package/src/switch.tsx +23 -0
- package/src/tabs.tsx +91 -0
- package/src/textarea.tsx +53 -0
- package/src/tooltip.tsx +21 -0
package/src/select.tsx
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useId, type SelectHTMLAttributes } from "react";
|
|
4
|
+
import { cx } from "./cx";
|
|
5
|
+
import field from "@imdz/styles/field.module.css";
|
|
6
|
+
import styles from "@imdz/styles/select.module.css";
|
|
7
|
+
|
|
8
|
+
export type SelectProps = SelectHTMLAttributes<HTMLSelectElement> & {
|
|
9
|
+
label?: string;
|
|
10
|
+
hint?: string;
|
|
11
|
+
error?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** Options go as children: regular option elements. */
|
|
15
|
+
export function Select({
|
|
16
|
+
label,
|
|
17
|
+
hint,
|
|
18
|
+
error,
|
|
19
|
+
id,
|
|
20
|
+
className,
|
|
21
|
+
children,
|
|
22
|
+
...props
|
|
23
|
+
}: SelectProps) {
|
|
24
|
+
const generatedId = useId();
|
|
25
|
+
const inputId = id ?? generatedId;
|
|
26
|
+
const message = error || hint;
|
|
27
|
+
const messageId = `${inputId}-message`;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className={field.field}>
|
|
31
|
+
{label && (
|
|
32
|
+
<label className={field.label} htmlFor={inputId}>
|
|
33
|
+
{label}
|
|
34
|
+
</label>
|
|
35
|
+
)}
|
|
36
|
+
<select
|
|
37
|
+
id={inputId}
|
|
38
|
+
className={cx(field.input, styles.select, className)}
|
|
39
|
+
aria-invalid={error ? true : undefined}
|
|
40
|
+
aria-describedby={message ? messageId : undefined}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
</select>
|
|
45
|
+
{message && (
|
|
46
|
+
<p
|
|
47
|
+
id={messageId}
|
|
48
|
+
className={error ? field.error : field.hint}
|
|
49
|
+
>
|
|
50
|
+
{message}
|
|
51
|
+
</p>
|
|
52
|
+
)}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
package/src/skeleton.tsx
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
import { cx } from "./cx";
|
|
3
|
+
import styles from "@imdz/styles/skeleton.module.css";
|
|
4
|
+
|
|
5
|
+
export type SkeletonProps = {
|
|
6
|
+
width?: CSSProperties["width"];
|
|
7
|
+
height?: CSSProperties["height"];
|
|
8
|
+
className?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** Loading placeholder. */
|
|
12
|
+
export function Skeleton({ width, height, className }: SkeletonProps) {
|
|
13
|
+
return (
|
|
14
|
+
<span
|
|
15
|
+
aria-hidden="true"
|
|
16
|
+
className={cx(styles.skeleton, className)}
|
|
17
|
+
style={{ width, height }}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
package/src/spinner.tsx
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { cx } from "./cx";
|
|
2
|
+
import styles from "@imdz/styles/spinner.module.css";
|
|
3
|
+
|
|
4
|
+
export type SpinnerProps = {
|
|
5
|
+
/** Text for screen readers. */
|
|
6
|
+
label?: string;
|
|
7
|
+
size?: "md" | "lg";
|
|
8
|
+
className?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function Spinner({
|
|
12
|
+
label = "Loading",
|
|
13
|
+
size = "md",
|
|
14
|
+
className,
|
|
15
|
+
}: SpinnerProps) {
|
|
16
|
+
return (
|
|
17
|
+
<span
|
|
18
|
+
role="status"
|
|
19
|
+
aria-label={label}
|
|
20
|
+
className={cx(styles.spinner, size === "lg" && styles.lg, className)}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
package/src/switch.tsx
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { InputHTMLAttributes } from "react";
|
|
2
|
+
import { cx } from "./cx";
|
|
3
|
+
import styles from "@imdz/styles/switch.module.css";
|
|
4
|
+
|
|
5
|
+
export type SwitchProps = Omit<
|
|
6
|
+
InputHTMLAttributes<HTMLInputElement>,
|
|
7
|
+
"type" | "role"
|
|
8
|
+
>;
|
|
9
|
+
|
|
10
|
+
/** The switch label goes as children. */
|
|
11
|
+
export function Switch({ className, children, ...props }: SwitchProps) {
|
|
12
|
+
return (
|
|
13
|
+
<label className={cx(styles.switch, className)}>
|
|
14
|
+
<input
|
|
15
|
+
type="checkbox"
|
|
16
|
+
role="switch"
|
|
17
|
+
className={styles.input}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
<span>{children}</span>
|
|
21
|
+
</label>
|
|
22
|
+
);
|
|
23
|
+
}
|
package/src/tabs.tsx
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
useId,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
type KeyboardEvent,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { cx } from "./cx";
|
|
11
|
+
import styles from "@imdz/styles/tabs.module.css";
|
|
12
|
+
|
|
13
|
+
export type TabItem = {
|
|
14
|
+
label: string;
|
|
15
|
+
content: ReactNode;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type TabsProps = {
|
|
19
|
+
items: TabItem[];
|
|
20
|
+
defaultIndex?: number;
|
|
21
|
+
onChange?: (index: number) => void;
|
|
22
|
+
className?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function Tabs({
|
|
26
|
+
items,
|
|
27
|
+
defaultIndex = 0,
|
|
28
|
+
onChange,
|
|
29
|
+
className,
|
|
30
|
+
}: TabsProps) {
|
|
31
|
+
const [selected, setSelected] = useState(defaultIndex);
|
|
32
|
+
const tabRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
|
33
|
+
const baseId = useId();
|
|
34
|
+
|
|
35
|
+
function select(index: number) {
|
|
36
|
+
setSelected(index);
|
|
37
|
+
onChange?.(index);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
41
|
+
const last = items.length - 1;
|
|
42
|
+
const next = {
|
|
43
|
+
ArrowRight: selected === last ? 0 : selected + 1,
|
|
44
|
+
ArrowLeft: selected === 0 ? last : selected - 1,
|
|
45
|
+
Home: 0,
|
|
46
|
+
End: last,
|
|
47
|
+
}[event.key];
|
|
48
|
+
|
|
49
|
+
if (next === undefined) return;
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
select(next);
|
|
52
|
+
tabRefs.current[next]?.focus();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className={cx(styles.tabs, className)}>
|
|
57
|
+
<div className={styles.list} role="tablist" onKeyDown={handleKeyDown}>
|
|
58
|
+
{items.map((item, index) => (
|
|
59
|
+
<button
|
|
60
|
+
key={item.label}
|
|
61
|
+
ref={(element) => {
|
|
62
|
+
tabRefs.current[index] = element;
|
|
63
|
+
}}
|
|
64
|
+
type="button"
|
|
65
|
+
role="tab"
|
|
66
|
+
id={`${baseId}-tab-${index}`}
|
|
67
|
+
className={styles.tab}
|
|
68
|
+
aria-selected={index === selected}
|
|
69
|
+
aria-controls={`${baseId}-panel-${index}`}
|
|
70
|
+
tabIndex={index === selected ? 0 : -1}
|
|
71
|
+
onClick={() => select(index)}
|
|
72
|
+
>
|
|
73
|
+
{item.label}
|
|
74
|
+
</button>
|
|
75
|
+
))}
|
|
76
|
+
</div>
|
|
77
|
+
{items.map((item, index) => (
|
|
78
|
+
<div
|
|
79
|
+
key={item.label}
|
|
80
|
+
role="tabpanel"
|
|
81
|
+
id={`${baseId}-panel-${index}`}
|
|
82
|
+
className={styles.panel}
|
|
83
|
+
aria-labelledby={`${baseId}-tab-${index}`}
|
|
84
|
+
hidden={index !== selected}
|
|
85
|
+
>
|
|
86
|
+
{item.content}
|
|
87
|
+
</div>
|
|
88
|
+
))}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
package/src/textarea.tsx
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useId, type TextareaHTMLAttributes } from "react";
|
|
4
|
+
import { cx } from "./cx";
|
|
5
|
+
import field from "@imdz/styles/field.module.css";
|
|
6
|
+
import styles from "@imdz/styles/textarea.module.css";
|
|
7
|
+
|
|
8
|
+
export type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
|
9
|
+
label?: string;
|
|
10
|
+
hint?: string;
|
|
11
|
+
error?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function Textarea({
|
|
15
|
+
label,
|
|
16
|
+
hint,
|
|
17
|
+
error,
|
|
18
|
+
id,
|
|
19
|
+
rows = 4,
|
|
20
|
+
className,
|
|
21
|
+
...props
|
|
22
|
+
}: TextareaProps) {
|
|
23
|
+
const generatedId = useId();
|
|
24
|
+
const inputId = id ?? generatedId;
|
|
25
|
+
const message = error || hint;
|
|
26
|
+
const messageId = `${inputId}-message`;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className={field.field}>
|
|
30
|
+
{label && (
|
|
31
|
+
<label className={field.label} htmlFor={inputId}>
|
|
32
|
+
{label}
|
|
33
|
+
</label>
|
|
34
|
+
)}
|
|
35
|
+
<textarea
|
|
36
|
+
id={inputId}
|
|
37
|
+
rows={rows}
|
|
38
|
+
className={cx(field.input, styles.textarea, className)}
|
|
39
|
+
aria-invalid={error ? true : undefined}
|
|
40
|
+
aria-describedby={message ? messageId : undefined}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
{message && (
|
|
44
|
+
<p
|
|
45
|
+
id={messageId}
|
|
46
|
+
className={error ? field.error : field.hint}
|
|
47
|
+
>
|
|
48
|
+
{message}
|
|
49
|
+
</p>
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
package/src/tooltip.tsx
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { cx } from "./cx";
|
|
3
|
+
import styles from "@imdz/styles/tooltip.module.css";
|
|
4
|
+
|
|
5
|
+
export type TooltipProps = {
|
|
6
|
+
text: string;
|
|
7
|
+
className?: string;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** Wraps an element and shows a short text on hover or focus. */
|
|
12
|
+
export function Tooltip({ text, className, children }: TooltipProps) {
|
|
13
|
+
return (
|
|
14
|
+
<span className={cx(styles.tooltip, className)}>
|
|
15
|
+
{children}
|
|
16
|
+
<span className={styles.bubble} role="tooltip">
|
|
17
|
+
{text}
|
|
18
|
+
</span>
|
|
19
|
+
</span>
|
|
20
|
+
);
|
|
21
|
+
}
|