@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.
Files changed (106) hide show
  1. package/LICENSE +21 -0
  2. package/OFL.txt +189 -0
  3. package/README.md +85 -2
  4. package/dist/accordion.d.ts +13 -0
  5. package/dist/accordion.js +12 -0
  6. package/dist/alert.d.ts +7 -0
  7. package/dist/alert.js +11 -0
  8. package/dist/avatar.d.ts +8 -0
  9. package/dist/avatar.js +21 -0
  10. package/dist/badge.d.ts +6 -0
  11. package/dist/badge.js +11 -0
  12. package/dist/button.d.ts +8 -0
  13. package/dist/button.js +11 -0
  14. package/dist/card.d.ts +6 -0
  15. package/dist/card.js +11 -0
  16. package/dist/checkbox.d.ts +3 -0
  17. package/dist/checkbox.js +12 -0
  18. package/dist/cx.d.ts +1 -0
  19. package/dist/cx.js +3 -0
  20. package/dist/dark.css +13 -0
  21. package/dist/dialog.d.ts +12 -0
  22. package/dist/dialog.js +24 -0
  23. package/dist/fonts/inter-latin-400-normal.woff2 +0 -0
  24. package/dist/fonts/inter-latin-600-normal.woff2 +0 -0
  25. package/dist/fonts/jetbrains-mono-latin-600-normal.woff2 +0 -0
  26. package/dist/fonts.css +24 -0
  27. package/dist/index.d.ts +17 -0
  28. package/dist/index.js +17 -0
  29. package/dist/input.d.ts +7 -0
  30. package/dist/input.js +17 -0
  31. package/dist/progress.d.ts +8 -0
  32. package/dist/progress.js +13 -0
  33. package/dist/select.d.ts +7 -0
  34. package/dist/select.js +19 -0
  35. package/dist/skeleton.d.ts +7 -0
  36. package/dist/skeleton.js +12 -0
  37. package/dist/spinner.d.ts +7 -0
  38. package/dist/spinner.js +11 -0
  39. package/dist/style.d.ts +5 -0
  40. package/dist/style.js +26 -0
  41. package/dist/styles/accordion.d.ts +1 -0
  42. package/dist/styles/accordion.js +1 -0
  43. package/dist/styles/alert.d.ts +1 -0
  44. package/dist/styles/alert.js +1 -0
  45. package/dist/styles/avatar.d.ts +1 -0
  46. package/dist/styles/avatar.js +1 -0
  47. package/dist/styles/badge.d.ts +1 -0
  48. package/dist/styles/badge.js +1 -0
  49. package/dist/styles/button.d.ts +1 -0
  50. package/dist/styles/button.js +1 -0
  51. package/dist/styles/card.d.ts +1 -0
  52. package/dist/styles/card.js +1 -0
  53. package/dist/styles/checkbox.d.ts +1 -0
  54. package/dist/styles/checkbox.js +1 -0
  55. package/dist/styles/dialog.d.ts +1 -0
  56. package/dist/styles/dialog.js +1 -0
  57. package/dist/styles/field.d.ts +1 -0
  58. package/dist/styles/field.js +1 -0
  59. package/dist/styles/fonts.d.ts +1 -0
  60. package/dist/styles/fonts.js +1 -0
  61. package/dist/styles/progress.d.ts +1 -0
  62. package/dist/styles/progress.js +1 -0
  63. package/dist/styles/select.d.ts +1 -0
  64. package/dist/styles/select.js +1 -0
  65. package/dist/styles/skeleton.d.ts +1 -0
  66. package/dist/styles/skeleton.js +1 -0
  67. package/dist/styles/spinner.d.ts +1 -0
  68. package/dist/styles/spinner.js +1 -0
  69. package/dist/styles/switch.d.ts +1 -0
  70. package/dist/styles/switch.js +1 -0
  71. package/dist/styles/tabs.d.ts +1 -0
  72. package/dist/styles/tabs.js +1 -0
  73. package/dist/styles/textarea.d.ts +1 -0
  74. package/dist/styles/textarea.js +1 -0
  75. package/dist/styles/tooltip.d.ts +1 -0
  76. package/dist/styles/tooltip.js +1 -0
  77. package/dist/styles.css +872 -0
  78. package/dist/switch.d.ts +3 -0
  79. package/dist/switch.js +12 -0
  80. package/dist/tabs.d.ts +12 -0
  81. package/dist/tabs.js +36 -0
  82. package/dist/textarea.d.ts +7 -0
  83. package/dist/textarea.js +18 -0
  84. package/dist/tokens.css +49 -0
  85. package/dist/tooltip.d.ts +7 -0
  86. package/dist/tooltip.js +12 -0
  87. package/package.json +54 -4
  88. package/src/accordion.tsx +35 -0
  89. package/src/alert.tsx +33 -0
  90. package/src/avatar.tsx +37 -0
  91. package/src/badge.tsx +31 -0
  92. package/src/button.tsx +32 -0
  93. package/src/card.tsx +24 -0
  94. package/src/checkbox.tsx +18 -0
  95. package/src/cx.ts +3 -0
  96. package/src/dialog.tsx +61 -0
  97. package/src/index.ts +26 -0
  98. package/src/input.tsx +50 -0
  99. package/src/progress.tsx +45 -0
  100. package/src/select.tsx +55 -0
  101. package/src/skeleton.tsx +20 -0
  102. package/src/spinner.tsx +23 -0
  103. package/src/switch.tsx +23 -0
  104. package/src/tabs.tsx +91 -0
  105. package/src/textarea.tsx +53 -0
  106. 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
+ }
@@ -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
+ }
@@ -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
+ }
@@ -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
+ }
@@ -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
+ }