@birdycom/omniaui-react 0.11.0 → 0.11.2

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 (143) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +21 -21
  3. package/package.json +71 -71
  4. package/src/blocks/BlockAboutTimeline.tsx +46 -46
  5. package/src/blocks/BlockAppHeader.tsx +52 -52
  6. package/src/blocks/BlockAppShell.tsx +56 -56
  7. package/src/blocks/BlockAuth.tsx +53 -53
  8. package/src/blocks/BlockAuthSplit.tsx +84 -84
  9. package/src/blocks/BlockContactGrid.tsx +71 -71
  10. package/src/blocks/BlockCta.tsx +23 -23
  11. package/src/blocks/BlockDataTable.tsx +57 -57
  12. package/src/blocks/BlockFaq.tsx +55 -55
  13. package/src/blocks/BlockFeatures.tsx +38 -38
  14. package/src/blocks/BlockFooter.tsx +50 -50
  15. package/src/blocks/BlockFormPage.tsx +26 -26
  16. package/src/blocks/BlockHero.tsx +27 -27
  17. package/src/blocks/BlockHeroCode.tsx +63 -63
  18. package/src/blocks/BlockHeroOrbit.tsx +73 -73
  19. package/src/blocks/BlockHeroSplit.tsx +28 -28
  20. package/src/blocks/BlockModuleHeader.tsx +34 -34
  21. package/src/blocks/BlockPageHero.tsx +25 -25
  22. package/src/blocks/BlockPortfolioGrid.tsx +50 -50
  23. package/src/blocks/BlockPricing.tsx +62 -62
  24. package/src/blocks/BlockProcess.tsx +50 -50
  25. package/src/blocks/BlockProductGrid.tsx +54 -54
  26. package/src/blocks/BlockProfileHeader.tsx +57 -57
  27. package/src/blocks/BlockShowcase.tsx +58 -58
  28. package/src/blocks/BlockSiteFooter.tsx +60 -60
  29. package/src/blocks/BlockSiteHeader.tsx +42 -42
  30. package/src/blocks/BlockSpotlight.tsx +58 -58
  31. package/src/blocks/BlockStats.tsx +26 -26
  32. package/src/blocks/BlockStatsCounter.tsx +31 -31
  33. package/src/blocks/BlockTeamList.tsx +48 -48
  34. package/src/blocks/BlockTwoFactor.tsx +57 -57
  35. package/src/blocks/BlockWizardDialog.tsx +101 -101
  36. package/src/components/Accordion.tsx +31 -31
  37. package/src/components/AccordionAccent.tsx +39 -39
  38. package/src/components/AffixInput.tsx +47 -47
  39. package/src/components/Alert.tsx +24 -24
  40. package/src/components/AlertDialog.tsx +60 -60
  41. package/src/components/AlphaTabs.tsx +63 -63
  42. package/src/components/Attachment.tsx +140 -140
  43. package/src/components/Avatar.tsx +32 -32
  44. package/src/components/BackLink.tsx +11 -11
  45. package/src/components/Badge.tsx +14 -14
  46. package/src/components/BarcodeInput.tsx +64 -64
  47. package/src/components/Breadcrumb.tsx +32 -32
  48. package/src/components/BrowserFrame.tsx +48 -48
  49. package/src/components/BusyOverlay.tsx +52 -52
  50. package/src/components/Button.tsx +49 -49
  51. package/src/components/Card.tsx +27 -27
  52. package/src/components/ChartLine.tsx +119 -119
  53. package/src/components/ChatComposer.tsx +73 -73
  54. package/src/components/Checkbox.tsx +14 -14
  55. package/src/components/Checklist.tsx +45 -45
  56. package/src/components/ChipFilter.tsx +60 -60
  57. package/src/components/Collapsible.tsx +43 -43
  58. package/src/components/ColorField.tsx +48 -48
  59. package/src/components/ColorSelect.tsx +49 -49
  60. package/src/components/ColumnMenu.tsx +66 -66
  61. package/src/components/Command.tsx +113 -113
  62. package/src/components/ConfirmDialog.tsx +95 -95
  63. package/src/components/CookieConsent.tsx +137 -137
  64. package/src/components/CopyField.tsx +44 -44
  65. package/src/components/Counter.tsx +21 -21
  66. package/src/components/DataTable.tsx +201 -201
  67. package/src/components/DecimalInput.tsx +87 -87
  68. package/src/components/DefinitionList.tsx +38 -38
  69. package/src/components/DocViewer.tsx +110 -110
  70. package/src/components/Dropdown.tsx +52 -52
  71. package/src/components/Dropzone.tsx +46 -46
  72. package/src/components/EmojiPicker.tsx +72 -72
  73. package/src/components/EmptyState.tsx +27 -27
  74. package/src/components/EntityCard.tsx +56 -56
  75. package/src/components/FactList.tsx +43 -43
  76. package/src/components/FilterCounts.tsx +50 -50
  77. package/src/components/FlipCard.tsx +55 -55
  78. package/src/components/FormField.tsx +54 -54
  79. package/src/components/Greeting.tsx +63 -63
  80. package/src/components/Heading.tsx +27 -27
  81. package/src/components/Icon.tsx +25 -25
  82. package/src/components/ImageZoom.tsx +51 -51
  83. package/src/components/Input.tsx +21 -21
  84. package/src/components/InputGroup.tsx +24 -24
  85. package/src/components/LanguageSelect.tsx +64 -64
  86. package/src/components/LauncherCard.tsx +50 -50
  87. package/src/components/Lightbox.tsx +83 -83
  88. package/src/components/LinkArrow.tsx +27 -27
  89. package/src/components/ListToolbar.tsx +67 -67
  90. package/src/components/Marquee.tsx +38 -38
  91. package/src/components/MediaCard.tsx +47 -47
  92. package/src/components/MediaRow.tsx +53 -53
  93. package/src/components/MediaThumb.tsx +49 -49
  94. package/src/components/Message.tsx +171 -171
  95. package/src/components/MessageThread.tsx +177 -177
  96. package/src/components/Modal.tsx +81 -81
  97. package/src/components/Navmenu.tsx +62 -62
  98. package/src/components/NextTeaser.tsx +29 -29
  99. package/src/components/NumberInput.tsx +64 -64
  100. package/src/components/OtpInput.tsx +48 -48
  101. package/src/components/PageHeader.tsx +43 -43
  102. package/src/components/Pagination.tsx +42 -42
  103. package/src/components/PasswordInput.tsx +36 -36
  104. package/src/components/PasswordRequirements.tsx +46 -46
  105. package/src/components/PhoneInput.tsx +93 -93
  106. package/src/components/Popover.tsx +34 -34
  107. package/src/components/PriceCard.tsx +83 -83
  108. package/src/components/ProductMark.tsx +35 -35
  109. package/src/components/Progress.tsx +32 -32
  110. package/src/components/ProjectCard.tsx +46 -46
  111. package/src/components/Prose.tsx +79 -79
  112. package/src/components/RecoveryCodes.tsx +65 -65
  113. package/src/components/Segmented.tsx +47 -47
  114. package/src/components/Select.tsx +27 -27
  115. package/src/components/SeverityCard.tsx +53 -53
  116. package/src/components/Sheet.tsx +62 -62
  117. package/src/components/Skeleton.tsx +12 -12
  118. package/src/components/SortableRow.tsx +46 -46
  119. package/src/components/StackCard.tsx +40 -40
  120. package/src/components/StackList.tsx +42 -42
  121. package/src/components/StatCard.tsx +49 -49
  122. package/src/components/Stepper.tsx +54 -54
  123. package/src/components/Switch.tsx +18 -18
  124. package/src/components/Table.tsx +75 -75
  125. package/src/components/TableActions.tsx +54 -54
  126. package/src/components/TableCompact.tsx +61 -61
  127. package/src/components/Tabs.tsx +45 -45
  128. package/src/components/TagInput.tsx +73 -73
  129. package/src/components/TerminalLog.tsx +63 -63
  130. package/src/components/Textarea.tsx +19 -19
  131. package/src/components/ThemeSwitch.tsx +45 -45
  132. package/src/components/TimeInput.tsx +76 -76
  133. package/src/components/Timeline.tsx +45 -45
  134. package/src/components/Toast.tsx +138 -138
  135. package/src/components/ToggleField.tsx +39 -39
  136. package/src/components/Tooltip.tsx +14 -14
  137. package/src/components/TopProgress.tsx +54 -54
  138. package/src/components/Tree.tsx +97 -97
  139. package/src/components/UpgradeBanner.tsx +62 -62
  140. package/src/components/User.tsx +31 -31
  141. package/src/components/VersionBanner.tsx +41 -41
  142. package/src/hooks.tsx +141 -141
  143. package/src/utils.ts +25 -25
@@ -1,119 +1,119 @@
1
- // File: packages/react/src/components/ChartLine.tsx
2
- // Map: types + demo data -> geometry (smooth path, area, percentages) -> ChartLine (head -> y axis -> plot: svg series + hover columns -> x axis -> legend)
3
- // Same markup as packages/ui/snippets/chart-line.html. The paths are computed here; tooltips open on :hover / :focus-visible (CSS only).
4
- import { forwardRef } from "react";
5
- import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
6
- import { cx } from "../utils";
7
-
8
- export interface ChartLineSeries {
9
- name: string;
10
- values: number[];
11
- dashed?: boolean;
12
- }
13
-
14
- export interface ChartLineProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
15
- title?: ReactNode;
16
- sub?: ReactNode;
17
- /** Big number in the head (`oc-chart-line__value`). */
18
- value?: ReactNode;
19
- series?: ChartLineSeries[];
20
- /** One label per point, used in the tooltips (e.g. "January"). */
21
- labels?: string[];
22
- /** Short x axis labels (default: labels; "Jan"… for the demo months). */
23
- xLabels?: string[];
24
- /** Top of the y axis (default: a round number above the highest value). */
25
- max?: number;
26
- /** Gridline rows (default 4). */
27
- rows?: number;
28
- /** "area" fills the series, "spark" hides axes and grid. */
29
- variant?: "line" | "area" | "spark";
30
- /** Dots always visible. */
31
- dots?: boolean;
32
- /** aria-label of the point columns. */
33
- label?: string;
34
- format?: (value: number) => string;
35
- }
36
-
37
- const DEMO: ChartLineSeries[] = [
38
- { name: "Desktop", values: [186, 305, 237, 73, 209, 214] },
39
- { name: "Mobile", values: [80, 200, 120, 190, 130, 140] },
40
- ];
41
- const MONTHS = ["January", "February", "March", "April", "May", "June"];
42
- const SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"];
43
-
44
- /** Number with at most 2 decimals and no trailing zeros ("75", "37.5", "4.69"). */
45
- const n2 = (n: number) => String(parseFloat(n.toFixed(2)));
46
- /** Round axis maximum: step = max / rows rounded up to 1, 1.5, 2, 2.5, 3, 4, 5, 6, 8 or 10 x 10^k. */
47
- export function niceMax(value: number, rows = 4) {
48
- if (value <= 0) return rows;
49
- const raw = value / rows, pow = 10 ** Math.floor(Math.log10(raw));
50
- return ([1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10].find((s) => s * pow >= raw) ?? 10) * pow * rows;
51
- }
52
- /** Smooth path through the points (x 0-100, y = 100 - value / max * 100); control points stay between their two ends. */
53
- export function chartLinePath(values: number[], max: number) {
54
- const pts = values.map((v, i) => [values.length > 1 ? (i / (values.length - 1)) * 100 : 50, 100 - (v / max) * 100]);
55
- if (!pts.length) return "";
56
- const clamp = (v: number, a: number, b: number) => Math.min(Math.max(v, Math.min(a, b)), Math.max(a, b));
57
- let d = `M${n2(pts[0][0])} ${n2(pts[0][1])}`;
58
- for (let i = 0; i < pts.length - 1; i++) {
59
- const p0 = pts[i - 1] ?? pts[i], p1 = pts[i], p2 = pts[i + 1], p3 = pts[i + 2] ?? p2;
60
- const c1 = [p1[0] + (p2[0] - p0[0]) / 6, clamp(p1[1] + (p2[1] - p0[1]) / 6, p1[1], p2[1])];
61
- const c2 = [p2[0] - (p3[0] - p1[0]) / 6, clamp(p2[1] - (p3[1] - p1[1]) / 6, p1[1], p2[1])];
62
- d += ` C${n2(c1[0])} ${n2(c1[1])} ${n2(c2[0])} ${n2(c2[1])} ${n2(p2[0])} ${n2(p2[1])}`;
63
- }
64
- return d;
65
- }
66
-
67
- /** `figure.oc-chart-line`: line / area chart in inline SVG with axes, legend and tooltips. */
68
- export const ChartLine = forwardRef<HTMLElement, ChartLineProps>(function ChartLine(
69
- { title = "Visitors", sub = "January – June 2026", value, series = DEMO, labels: labelsProp, xLabels, max, rows = 4, variant = "line", dots = true,
70
- label = "Visitors per month", format = String, className, ...rest },
71
- ref,
72
- ) {
73
- const top = max ?? niceMax(Math.max(0, ...series.flatMap((s) => s.values)), rows);
74
- const count = Math.max(0, ...series.map((s) => s.values.length));
75
- const labels = labelsProp ?? MONTHS;
76
- const axis = xLabels ?? labelsProp ?? SHORT;
77
- return (
78
- <figure ref={ref} className={cx("oc-chart-line", variant !== "line" && `oc-chart-line--${variant}`, dots && "oc-chart-line--dots", className)} {...rest}>
79
- {(title != null || sub != null || value != null) && (
80
- <figcaption className="oc-chart-line__head">
81
- {title != null && <span className="oc-chart-line__title">{title}</span>}
82
- {sub != null && <span className="oc-chart-line__sub">{sub}</span>}
83
- {value != null && <span className="oc-chart-line__value">{value}</span>}
84
- </figcaption>
85
- )}
86
- <div className="oc-chart-line__body">
87
- <ul className="oc-chart-line__y" aria-hidden="true">
88
- {Array.from({ length: rows + 1 }, (_, i) => <li key={i}>{format(top - (top / rows) * i)}</li>)}
89
- </ul>
90
- <div className="oc-chart-line__plot" style={rows !== 4 ? ({ "--oc-chart-line-rows": rows } as CSSProperties) : undefined}>
91
- <svg className="oc-chart-line__svg" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
92
- {series.map((s, i) => {
93
- const d = chartLinePath(s.values, top);
94
- return (
95
- <g key={i} className="oc-chart-line__series">
96
- <path className="oc-chart-line__area" d={`${d} L100 100 L0 100 Z`} />
97
- <path className={cx("oc-chart-line__path", s.dashed && "oc-chart-line__path--dashed")} d={d} />
98
- </g>
99
- );
100
- })}
101
- </svg>
102
- <ul className="oc-chart-line__cols" style={{ "--n": count } as CSSProperties} aria-label={label}>
103
- {Array.from({ length: count }, (_, i) => (
104
- <li key={i} className="oc-chart-line__col" style={{ "--x": i } as CSSProperties} tabIndex={0} aria-label={`${labels[i] ?? i + 1}: ${series.map((s) => `${s.name} ${format(s.values[i])}`).join(", ")}`}>
105
- {series.map((s, j) => <i key={j} className="oc-chart-line__dot" style={{ "--y": `${n2((s.values[i] / top) * 100)}%` } as CSSProperties} />)}
106
- <span className="oc-chart-line__tip" aria-hidden="true">
107
- <strong>{labels[i] ?? i + 1}</strong>
108
- {series.map((s, j) => <span key={j} className="oc-chart-line__row">{s.name}<b>{format(s.values[i])}</b></span>)}
109
- </span>
110
- </li>
111
- ))}
112
- </ul>
113
- </div>
114
- <ol className="oc-chart-line__x" aria-hidden="true">{axis.map((l, i) => <li key={i}>{l}</li>)}</ol>
115
- </div>
116
- <ul className="oc-chart-legend">{series.map((s, i) => <li key={i}><span className="oc-chart-swatch" />{s.name}</li>)}</ul>
117
- </figure>
118
- );
119
- });
1
+ // File: packages/react/src/components/ChartLine.tsx
2
+ // Map: types + demo data -> geometry (smooth path, area, percentages) -> ChartLine (head -> y axis -> plot: svg series + hover columns -> x axis -> legend)
3
+ // Same markup as packages/ui/snippets/chart-line.html. The paths are computed here; tooltips open on :hover / :focus-visible (CSS only).
4
+ import { forwardRef } from "react";
5
+ import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
6
+ import { cx } from "../utils";
7
+
8
+ export interface ChartLineSeries {
9
+ name: string;
10
+ values: number[];
11
+ dashed?: boolean;
12
+ }
13
+
14
+ export interface ChartLineProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
15
+ title?: ReactNode;
16
+ sub?: ReactNode;
17
+ /** Big number in the head (`oc-chart-line__value`). */
18
+ value?: ReactNode;
19
+ series?: ChartLineSeries[];
20
+ /** One label per point, used in the tooltips (e.g. "January"). */
21
+ labels?: string[];
22
+ /** Short x axis labels (default: labels; "Jan"… for the demo months). */
23
+ xLabels?: string[];
24
+ /** Top of the y axis (default: a round number above the highest value). */
25
+ max?: number;
26
+ /** Gridline rows (default 4). */
27
+ rows?: number;
28
+ /** "area" fills the series, "spark" hides axes and grid. */
29
+ variant?: "line" | "area" | "spark";
30
+ /** Dots always visible. */
31
+ dots?: boolean;
32
+ /** aria-label of the point columns. */
33
+ label?: string;
34
+ format?: (value: number) => string;
35
+ }
36
+
37
+ const DEMO: ChartLineSeries[] = [
38
+ { name: "Desktop", values: [186, 305, 237, 73, 209, 214] },
39
+ { name: "Mobile", values: [80, 200, 120, 190, 130, 140] },
40
+ ];
41
+ const MONTHS = ["January", "February", "March", "April", "May", "June"];
42
+ const SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"];
43
+
44
+ /** Number with at most 2 decimals and no trailing zeros ("75", "37.5", "4.69"). */
45
+ const n2 = (n: number) => String(parseFloat(n.toFixed(2)));
46
+ /** Round axis maximum: step = max / rows rounded up to 1, 1.5, 2, 2.5, 3, 4, 5, 6, 8 or 10 x 10^k. */
47
+ export function niceMax(value: number, rows = 4) {
48
+ if (value <= 0) return rows;
49
+ const raw = value / rows, pow = 10 ** Math.floor(Math.log10(raw));
50
+ return ([1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10].find((s) => s * pow >= raw) ?? 10) * pow * rows;
51
+ }
52
+ /** Smooth path through the points (x 0-100, y = 100 - value / max * 100); control points stay between their two ends. */
53
+ export function chartLinePath(values: number[], max: number) {
54
+ const pts = values.map((v, i) => [values.length > 1 ? (i / (values.length - 1)) * 100 : 50, 100 - (v / max) * 100]);
55
+ if (!pts.length) return "";
56
+ const clamp = (v: number, a: number, b: number) => Math.min(Math.max(v, Math.min(a, b)), Math.max(a, b));
57
+ let d = `M${n2(pts[0][0])} ${n2(pts[0][1])}`;
58
+ for (let i = 0; i < pts.length - 1; i++) {
59
+ const p0 = pts[i - 1] ?? pts[i], p1 = pts[i], p2 = pts[i + 1], p3 = pts[i + 2] ?? p2;
60
+ const c1 = [p1[0] + (p2[0] - p0[0]) / 6, clamp(p1[1] + (p2[1] - p0[1]) / 6, p1[1], p2[1])];
61
+ const c2 = [p2[0] - (p3[0] - p1[0]) / 6, clamp(p2[1] - (p3[1] - p1[1]) / 6, p1[1], p2[1])];
62
+ d += ` C${n2(c1[0])} ${n2(c1[1])} ${n2(c2[0])} ${n2(c2[1])} ${n2(p2[0])} ${n2(p2[1])}`;
63
+ }
64
+ return d;
65
+ }
66
+
67
+ /** `figure.oc-chart-line`: line / area chart in inline SVG with axes, legend and tooltips. */
68
+ export const ChartLine = forwardRef<HTMLElement, ChartLineProps>(function ChartLine(
69
+ { title = "Visitors", sub = "January – June 2026", value, series = DEMO, labels: labelsProp, xLabels, max, rows = 4, variant = "line", dots = true,
70
+ label = "Visitors per month", format = String, className, ...rest },
71
+ ref,
72
+ ) {
73
+ const top = max ?? niceMax(Math.max(0, ...series.flatMap((s) => s.values)), rows);
74
+ const count = Math.max(0, ...series.map((s) => s.values.length));
75
+ const labels = labelsProp ?? MONTHS;
76
+ const axis = xLabels ?? labelsProp ?? SHORT;
77
+ return (
78
+ <figure ref={ref} className={cx("oc-chart-line", variant !== "line" && `oc-chart-line--${variant}`, dots && "oc-chart-line--dots", className)} {...rest}>
79
+ {(title != null || sub != null || value != null) && (
80
+ <figcaption className="oc-chart-line__head">
81
+ {title != null && <span className="oc-chart-line__title">{title}</span>}
82
+ {sub != null && <span className="oc-chart-line__sub">{sub}</span>}
83
+ {value != null && <span className="oc-chart-line__value">{value}</span>}
84
+ </figcaption>
85
+ )}
86
+ <div className="oc-chart-line__body">
87
+ <ul className="oc-chart-line__y" aria-hidden="true">
88
+ {Array.from({ length: rows + 1 }, (_, i) => <li key={i}>{format(top - (top / rows) * i)}</li>)}
89
+ </ul>
90
+ <div className="oc-chart-line__plot" style={rows !== 4 ? ({ "--oc-chart-line-rows": rows } as CSSProperties) : undefined}>
91
+ <svg className="oc-chart-line__svg" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
92
+ {series.map((s, i) => {
93
+ const d = chartLinePath(s.values, top);
94
+ return (
95
+ <g key={i} className="oc-chart-line__series">
96
+ <path className="oc-chart-line__area" d={`${d} L100 100 L0 100 Z`} />
97
+ <path className={cx("oc-chart-line__path", s.dashed && "oc-chart-line__path--dashed")} d={d} />
98
+ </g>
99
+ );
100
+ })}
101
+ </svg>
102
+ <ul className="oc-chart-line__cols" style={{ "--n": count } as CSSProperties} aria-label={label}>
103
+ {Array.from({ length: count }, (_, i) => (
104
+ <li key={i} className="oc-chart-line__col" style={{ "--x": i } as CSSProperties} tabIndex={0} aria-label={`${labels[i] ?? i + 1}: ${series.map((s) => `${s.name} ${format(s.values[i])}`).join(", ")}`}>
105
+ {series.map((s, j) => <i key={j} className="oc-chart-line__dot" style={{ "--y": `${n2((s.values[i] / top) * 100)}%` } as CSSProperties} />)}
106
+ <span className="oc-chart-line__tip" aria-hidden="true">
107
+ <strong>{labels[i] ?? i + 1}</strong>
108
+ {series.map((s, j) => <span key={j} className="oc-chart-line__row">{s.name}<b>{format(s.values[i])}</b></span>)}
109
+ </span>
110
+ </li>
111
+ ))}
112
+ </ul>
113
+ </div>
114
+ <ol className="oc-chart-line__x" aria-hidden="true">{axis.map((l, i) => <li key={i}>{l}</li>)}</ol>
115
+ </div>
116
+ <ul className="oc-chart-legend">{series.map((s, i) => <li key={i}><span className="oc-chart-swatch" />{s.name}</li>)}</ul>
117
+ </figure>
118
+ );
119
+ });
@@ -1,73 +1,73 @@
1
- // File: packages/react/src/components/ChatComposer.tsx
2
- // Map: props -> ChatComposer (form -> label + textarea -> bar: tools, hint, send) -> submit / Enter handling
3
- // Same markup as packages/ui/snippets/chat-composer.html. React adds onSend and (optional) Enter to send.
4
- "use client";
5
-
6
- import { forwardRef, useId } from "react";
7
- import type { FormHTMLAttributes, KeyboardEvent, ReactNode } from "react";
8
- import { cx } from "../utils";
9
- import { useControllable } from "../hooks";
10
-
11
- /** The event type React gives to a form's onSubmit (differs between React 18 and 19 typings). */
12
- type SubmitEvent = Parameters<NonNullable<FormHTMLAttributes<HTMLFormElement>["onSubmit"]>>[0];
13
-
14
- export interface ChatComposerProps extends Omit<FormHTMLAttributes<HTMLFormElement>, "onChange"> {
15
- value?: string;
16
- defaultValue?: string;
17
- onValueChange?: (value: string) => void;
18
- /** Called with the trimmed text on submit; the field is cleared afterwards. */
19
- onSend?: (text: string) => void;
20
- placeholder?: string;
21
- /** Accessible label of the field (visually hidden). */
22
- label?: string;
23
- /** Tool buttons (`.oc-chat-composer__tool`), e.g. attach or an EmojiPicker. */
24
- tools?: ReactNode;
25
- hint?: ReactNode;
26
- inline?: boolean;
27
- /** Enter sends, Shift+Enter adds a new line. Default true. */
28
- sendOnEnter?: boolean;
29
- disabled?: boolean;
30
- }
31
-
32
- /** `form.oc-chat-composer`: growing textarea + toolbar + send button. */
33
- export const ChatComposer = forwardRef<HTMLFormElement, ChatComposerProps>(function ChatComposer(
34
- { value, defaultValue = "", onValueChange, onSend, placeholder = "Write a message…", label = "Message", tools, hint, inline, sendOnEnter = true, disabled, className, onSubmit, ...rest },
35
- ref,
36
- ) {
37
- const id = `oc-chat-${useId().replace(/:/g, "")}`;
38
- const [text, setText] = useControllable(value, defaultValue, onValueChange);
39
- const submit = (e: SubmitEvent) => {
40
- e.preventDefault();
41
- onSubmit?.(e);
42
- if (!text.trim() || disabled) return;
43
- onSend?.(text.trim());
44
- setText("");
45
- };
46
- const onKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
47
- if (sendOnEnter && e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
48
- e.preventDefault();
49
- e.currentTarget.form?.requestSubmit();
50
- }
51
- };
52
- return (
53
- <form ref={ref} className={cx("oc-chat-composer", inline && "oc-chat-composer--inline", className)} onSubmit={submit} {...rest}>
54
- <label className="oc-visually-hidden" htmlFor={id}>{label}</label>
55
- <textarea
56
- className="oc-chat-composer__input"
57
- id={id}
58
- rows={1}
59
- placeholder={placeholder}
60
- required
61
- disabled={disabled}
62
- value={text}
63
- onChange={(e) => setText(e.target.value)}
64
- onKeyDown={onKeyDown}
65
- />
66
- <div className="oc-chat-composer__bar">
67
- {tools != null && <div className="oc-chat-composer__tools">{tools}</div>}
68
- {hint != null && <span className="oc-chat-composer__hint">{hint}</span>}
69
- <button type="submit" className="oc-chat-composer__send" aria-label="Send" disabled={disabled}>&#10148;</button>
70
- </div>
71
- </form>
72
- );
73
- });
1
+ // File: packages/react/src/components/ChatComposer.tsx
2
+ // Map: props -> ChatComposer (form -> label + textarea -> bar: tools, hint, send) -> submit / Enter handling
3
+ // Same markup as packages/ui/snippets/chat-composer.html. React adds onSend and (optional) Enter to send.
4
+ "use client";
5
+
6
+ import { forwardRef, useId } from "react";
7
+ import type { FormHTMLAttributes, KeyboardEvent, ReactNode } from "react";
8
+ import { cx } from "../utils";
9
+ import { useControllable } from "../hooks";
10
+
11
+ /** The event type React gives to a form's onSubmit (differs between React 18 and 19 typings). */
12
+ type SubmitEvent = Parameters<NonNullable<FormHTMLAttributes<HTMLFormElement>["onSubmit"]>>[0];
13
+
14
+ export interface ChatComposerProps extends Omit<FormHTMLAttributes<HTMLFormElement>, "onChange"> {
15
+ value?: string;
16
+ defaultValue?: string;
17
+ onValueChange?: (value: string) => void;
18
+ /** Called with the trimmed text on submit; the field is cleared afterwards. */
19
+ onSend?: (text: string) => void;
20
+ placeholder?: string;
21
+ /** Accessible label of the field (visually hidden). */
22
+ label?: string;
23
+ /** Tool buttons (`.oc-chat-composer__tool`), e.g. attach or an EmojiPicker. */
24
+ tools?: ReactNode;
25
+ hint?: ReactNode;
26
+ inline?: boolean;
27
+ /** Enter sends, Shift+Enter adds a new line. Default true. */
28
+ sendOnEnter?: boolean;
29
+ disabled?: boolean;
30
+ }
31
+
32
+ /** `form.oc-chat-composer`: growing textarea + toolbar + send button. */
33
+ export const ChatComposer = forwardRef<HTMLFormElement, ChatComposerProps>(function ChatComposer(
34
+ { value, defaultValue = "", onValueChange, onSend, placeholder = "Write a message…", label = "Message", tools, hint, inline, sendOnEnter = true, disabled, className, onSubmit, ...rest },
35
+ ref,
36
+ ) {
37
+ const id = `oc-chat-${useId().replace(/:/g, "")}`;
38
+ const [text, setText] = useControllable(value, defaultValue, onValueChange);
39
+ const submit = (e: SubmitEvent) => {
40
+ e.preventDefault();
41
+ onSubmit?.(e);
42
+ if (!text.trim() || disabled) return;
43
+ onSend?.(text.trim());
44
+ setText("");
45
+ };
46
+ const onKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
47
+ if (sendOnEnter && e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
48
+ e.preventDefault();
49
+ e.currentTarget.form?.requestSubmit();
50
+ }
51
+ };
52
+ return (
53
+ <form ref={ref} className={cx("oc-chat-composer", inline && "oc-chat-composer--inline", className)} onSubmit={submit} {...rest}>
54
+ <label className="oc-visually-hidden" htmlFor={id}>{label}</label>
55
+ <textarea
56
+ className="oc-chat-composer__input"
57
+ id={id}
58
+ rows={1}
59
+ placeholder={placeholder}
60
+ required
61
+ disabled={disabled}
62
+ value={text}
63
+ onChange={(e) => setText(e.target.value)}
64
+ onKeyDown={onKeyDown}
65
+ />
66
+ <div className="oc-chat-composer__bar">
67
+ {tools != null && <div className="oc-chat-composer__tools">{tools}</div>}
68
+ {hint != null && <span className="oc-chat-composer__hint">{hint}</span>}
69
+ <button type="submit" className="oc-chat-composer__send" aria-label="Send" disabled={disabled}>&#10148;</button>
70
+ </div>
71
+ </form>
72
+ );
73
+ });
@@ -1,14 +1,14 @@
1
- import { forwardRef } from "react";
2
- import type { InputHTMLAttributes, ReactNode } from "react";
3
- import { cx } from "../utils";
4
-
5
- export interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type"> {
6
- /** When set the input is wrapped in a <label> with this text. */
7
- label?: ReactNode;
8
- }
9
-
10
- /** `<input class="oc-checkbox" type="checkbox">`, wrapped in a label when `label` is given. */
11
- export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(function Checkbox({ label, className, ...rest }, ref) {
12
- const input = <input ref={ref} type="checkbox" className={cx("oc-checkbox", className)} {...rest} />;
13
- return label != null ? <label>{input} {label}</label> : input;
14
- });
1
+ import { forwardRef } from "react";
2
+ import type { InputHTMLAttributes, ReactNode } from "react";
3
+ import { cx } from "../utils";
4
+
5
+ export interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type"> {
6
+ /** When set the input is wrapped in a <label> with this text. */
7
+ label?: ReactNode;
8
+ }
9
+
10
+ /** `<input class="oc-checkbox" type="checkbox">`, wrapped in a label when `label` is given. */
11
+ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(function Checkbox({ label, className, ...rest }, ref) {
12
+ const input = <input ref={ref} type="checkbox" className={cx("oc-checkbox", className)} {...rest} />;
13
+ return label != null ? <label>{input} {label}</label> : input;
14
+ });
@@ -1,45 +1,45 @@
1
- import { forwardRef } from "react";
2
- import type { HTMLAttributes, LiHTMLAttributes, ReactNode } from "react";
3
- import { cx } from "../utils";
4
-
5
- export interface ChecklistProps extends HTMLAttributes<HTMLUListElement> {
6
- /** Bordered rows with an action slot. */
7
- card?: boolean;
8
- }
9
-
10
- /** `<ul class="oc-checklist">` */
11
- export const Checklist = forwardRef<HTMLUListElement, ChecklistProps>(function Checklist({ card, className, ...rest }, ref) {
12
- return <ul ref={ref} className={cx("oc-checklist", card && "oc-checklist--card", className)} {...rest} />;
13
- });
14
-
15
- export interface ChecklistItemProps extends Omit<LiHTMLAttributes<HTMLLIElement>, "title"> {
16
- /** Rule is met / step is done (`is-done`). */
17
- done?: boolean;
18
- /** Card variant: title + hint text column. Without it `children` are the row content. */
19
- title?: ReactNode;
20
- hint?: ReactNode;
21
- /** Card variant: trailing action (usually a button or link). */
22
- action?: ReactNode;
23
- }
24
-
25
- /** `<li class="oc-checklist-item is-done">` */
26
- export const ChecklistItem = forwardRef<HTMLLIElement, ChecklistItemProps>(function ChecklistItem(
27
- { done, title, hint, action, className, children, ...rest },
28
- ref,
29
- ) {
30
- return (
31
- <li ref={ref} className={cx("oc-checklist-item", done && "is-done", className)} {...rest}>
32
- {title != null ? (
33
- <>
34
- <span className="oc-checklist-text">
35
- <span className="oc-checklist-title">{title}</span>
36
- {hint != null && <span className="oc-checklist-hint">{hint}</span>}
37
- </span>
38
- {action}
39
- </>
40
- ) : (
41
- children
42
- )}
43
- </li>
44
- );
45
- });
1
+ import { forwardRef } from "react";
2
+ import type { HTMLAttributes, LiHTMLAttributes, ReactNode } from "react";
3
+ import { cx } from "../utils";
4
+
5
+ export interface ChecklistProps extends HTMLAttributes<HTMLUListElement> {
6
+ /** Bordered rows with an action slot. */
7
+ card?: boolean;
8
+ }
9
+
10
+ /** `<ul class="oc-checklist">` */
11
+ export const Checklist = forwardRef<HTMLUListElement, ChecklistProps>(function Checklist({ card, className, ...rest }, ref) {
12
+ return <ul ref={ref} className={cx("oc-checklist", card && "oc-checklist--card", className)} {...rest} />;
13
+ });
14
+
15
+ export interface ChecklistItemProps extends Omit<LiHTMLAttributes<HTMLLIElement>, "title"> {
16
+ /** Rule is met / step is done (`is-done`). */
17
+ done?: boolean;
18
+ /** Card variant: title + hint text column. Without it `children` are the row content. */
19
+ title?: ReactNode;
20
+ hint?: ReactNode;
21
+ /** Card variant: trailing action (usually a button or link). */
22
+ action?: ReactNode;
23
+ }
24
+
25
+ /** `<li class="oc-checklist-item is-done">` */
26
+ export const ChecklistItem = forwardRef<HTMLLIElement, ChecklistItemProps>(function ChecklistItem(
27
+ { done, title, hint, action, className, children, ...rest },
28
+ ref,
29
+ ) {
30
+ return (
31
+ <li ref={ref} className={cx("oc-checklist-item", done && "is-done", className)} {...rest}>
32
+ {title != null ? (
33
+ <>
34
+ <span className="oc-checklist-text">
35
+ <span className="oc-checklist-title">{title}</span>
36
+ {hint != null && <span className="oc-checklist-hint">{hint}</span>}
37
+ </span>
38
+ {action}
39
+ </>
40
+ ) : (
41
+ children
42
+ )}
43
+ </li>
44
+ );
45
+ });
@@ -1,60 +1,60 @@
1
- // File: packages/react/src/components/ChipFilter.tsx
2
- // Map: types -> component (group -> one chip label per option: checkbox or radio + text + optional count)
3
- // Same markup as packages/ui/snippets/chip-filter.html. multiple = checkboxes (array value), else radios (string).
4
- "use client";
5
-
6
- import { forwardRef, useId } from "react";
7
- import type { HTMLAttributes, ReactNode } from "react";
8
- import { cx } from "../utils";
9
- import { useControllable } from "../hooks";
10
-
11
- export interface ChipFilterOption {
12
- value: string;
13
- label: ReactNode;
14
- count?: number;
15
- disabled?: boolean;
16
- }
17
-
18
- export interface ChipFilterProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> {
19
- options: ChipFilterOption[];
20
- /** Several chips at once (checkboxes). */
21
- multiple?: boolean;
22
- /** string for single choice, string[] with `multiple`. */
23
- value?: string | string[];
24
- defaultValue?: string | string[];
25
- onChange?: (value: string | string[]) => void;
26
- name?: string;
27
- size?: "sm" | "md";
28
- /** One line that scrolls sideways. */
29
- scroll?: boolean;
30
- }
31
-
32
- /** `.oc-chip-filter`: rounded filter chips. */
33
- export const ChipFilter = forwardRef<HTMLDivElement, ChipFilterProps>(function ChipFilter(
34
- { options, multiple, value, defaultValue, onChange, name, size = "md", scroll, className, ...rest },
35
- ref,
36
- ) {
37
- const group = name ?? `oc-chip-${useId().replace(/:/g, "")}`;
38
- const [current, setCurrent] = useControllable<string | string[]>(value, defaultValue ?? (multiple ? [] : ""), onChange);
39
- const selected = Array.isArray(current) ? current : current ? [current] : [];
40
-
41
- // Checkbox: add or remove the value. Radio: replace it.
42
- const toggle = (v: string) => setCurrent(multiple ? (selected.includes(v) ? selected.filter((s) => s !== v) : [...selected, v]) : v);
43
-
44
- return (
45
- <div
46
- ref={ref}
47
- className={cx("oc-chip-filter", size === "sm" && "oc-chip-filter--sm", scroll && "oc-chip-filter--scroll", className)}
48
- role={multiple ? "group" : "radiogroup"}
49
- {...rest}
50
- >
51
- {options.map((o) => (
52
- <label key={o.value} className="oc-chip-filter-item">
53
- <input type={multiple ? "checkbox" : "radio"} name={group} value={o.value} disabled={o.disabled} checked={selected.includes(o.value)} onChange={() => toggle(o.value)} />
54
- {" "}{o.label}
55
- {o.count != null && <span className="oc-chip-filter-count">{o.count}</span>}
56
- </label>
57
- ))}
58
- </div>
59
- );
60
- });
1
+ // File: packages/react/src/components/ChipFilter.tsx
2
+ // Map: types -> component (group -> one chip label per option: checkbox or radio + text + optional count)
3
+ // Same markup as packages/ui/snippets/chip-filter.html. multiple = checkboxes (array value), else radios (string).
4
+ "use client";
5
+
6
+ import { forwardRef, useId } from "react";
7
+ import type { HTMLAttributes, ReactNode } from "react";
8
+ import { cx } from "../utils";
9
+ import { useControllable } from "../hooks";
10
+
11
+ export interface ChipFilterOption {
12
+ value: string;
13
+ label: ReactNode;
14
+ count?: number;
15
+ disabled?: boolean;
16
+ }
17
+
18
+ export interface ChipFilterProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> {
19
+ options: ChipFilterOption[];
20
+ /** Several chips at once (checkboxes). */
21
+ multiple?: boolean;
22
+ /** string for single choice, string[] with `multiple`. */
23
+ value?: string | string[];
24
+ defaultValue?: string | string[];
25
+ onChange?: (value: string | string[]) => void;
26
+ name?: string;
27
+ size?: "sm" | "md";
28
+ /** One line that scrolls sideways. */
29
+ scroll?: boolean;
30
+ }
31
+
32
+ /** `.oc-chip-filter`: rounded filter chips. */
33
+ export const ChipFilter = forwardRef<HTMLDivElement, ChipFilterProps>(function ChipFilter(
34
+ { options, multiple, value, defaultValue, onChange, name, size = "md", scroll, className, ...rest },
35
+ ref,
36
+ ) {
37
+ const group = name ?? `oc-chip-${useId().replace(/:/g, "")}`;
38
+ const [current, setCurrent] = useControllable<string | string[]>(value, defaultValue ?? (multiple ? [] : ""), onChange);
39
+ const selected = Array.isArray(current) ? current : current ? [current] : [];
40
+
41
+ // Checkbox: add or remove the value. Radio: replace it.
42
+ const toggle = (v: string) => setCurrent(multiple ? (selected.includes(v) ? selected.filter((s) => s !== v) : [...selected, v]) : v);
43
+
44
+ return (
45
+ <div
46
+ ref={ref}
47
+ className={cx("oc-chip-filter", size === "sm" && "oc-chip-filter--sm", scroll && "oc-chip-filter--scroll", className)}
48
+ role={multiple ? "group" : "radiogroup"}
49
+ {...rest}
50
+ >
51
+ {options.map((o) => (
52
+ <label key={o.value} className="oc-chip-filter-item">
53
+ <input type={multiple ? "checkbox" : "radio"} name={group} value={o.value} disabled={o.disabled} checked={selected.includes(o.value)} onChange={() => toggle(o.value)} />
54
+ {" "}{o.label}
55
+ {o.count != null && <span className="oc-chip-filter-count">{o.count}</span>}
56
+ </label>
57
+ ))}
58
+ </div>
59
+ );
60
+ });