@birdycom/omniaui-react 0.11.0 → 0.11.1

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,45 +1,45 @@
1
- "use client";
2
-
3
- import { forwardRef, useId } from "react";
4
- import type { HTMLAttributes, ReactNode } from "react";
5
- import { cx } from "../utils";
6
- import { useControllable } from "../hooks";
7
-
8
- export interface TabItem {
9
- value: string;
10
- label: ReactNode;
11
- content: ReactNode;
12
- }
13
-
14
- export interface TabsProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> {
15
- tabs: TabItem[];
16
- value?: string;
17
- defaultValue?: string;
18
- onChange?: (value: string) => void;
19
- /** Radio group name (generated when omitted). */
20
- name?: string;
21
- }
22
-
23
- /** `.oc-tabset`: radio triggers + panels; CSS (`:has()`) shows the selected panel. */
24
- export const Tabs = forwardRef<HTMLDivElement, TabsProps>(function Tabs(
25
- { tabs, value, defaultValue, onChange, name, className, ...rest },
26
- ref,
27
- ) {
28
- const auto = useId();
29
- const group = name ?? `oc-tabs-${auto.replace(/:/g, "")}`;
30
- const [current, setCurrent] = useControllable<string>(value, defaultValue ?? tabs[0]?.value ?? "", onChange);
31
- return (
32
- <div ref={ref} className={cx("oc-tabset", className)} {...rest}>
33
- <div className="oc-tab-list" role="tablist">
34
- {tabs.map((t) => (
35
- <label className="oc-tab-trigger" key={t.value}>
36
- <input type="radio" name={group} value={t.value} checked={current === t.value} onChange={() => setCurrent(t.value)} /> {t.label}
37
- </label>
38
- ))}
39
- </div>
40
- {tabs.map((t) => (
41
- <div className="oc-tab-panel" key={t.value}>{t.content}</div>
42
- ))}
43
- </div>
44
- );
45
- });
1
+ "use client";
2
+
3
+ import { forwardRef, useId } from "react";
4
+ import type { HTMLAttributes, ReactNode } from "react";
5
+ import { cx } from "../utils";
6
+ import { useControllable } from "../hooks";
7
+
8
+ export interface TabItem {
9
+ value: string;
10
+ label: ReactNode;
11
+ content: ReactNode;
12
+ }
13
+
14
+ export interface TabsProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> {
15
+ tabs: TabItem[];
16
+ value?: string;
17
+ defaultValue?: string;
18
+ onChange?: (value: string) => void;
19
+ /** Radio group name (generated when omitted). */
20
+ name?: string;
21
+ }
22
+
23
+ /** `.oc-tabset`: radio triggers + panels; CSS (`:has()`) shows the selected panel. */
24
+ export const Tabs = forwardRef<HTMLDivElement, TabsProps>(function Tabs(
25
+ { tabs, value, defaultValue, onChange, name, className, ...rest },
26
+ ref,
27
+ ) {
28
+ const auto = useId();
29
+ const group = name ?? `oc-tabs-${auto.replace(/:/g, "")}`;
30
+ const [current, setCurrent] = useControllable<string>(value, defaultValue ?? tabs[0]?.value ?? "", onChange);
31
+ return (
32
+ <div ref={ref} className={cx("oc-tabset", className)} {...rest}>
33
+ <div className="oc-tab-list" role="tablist">
34
+ {tabs.map((t) => (
35
+ <label className="oc-tab-trigger" key={t.value}>
36
+ <input type="radio" name={group} value={t.value} checked={current === t.value} onChange={() => setCurrent(t.value)} /> {t.label}
37
+ </label>
38
+ ))}
39
+ </div>
40
+ {tabs.map((t) => (
41
+ <div className="oc-tab-panel" key={t.value}>{t.content}</div>
42
+ ))}
43
+ </div>
44
+ );
45
+ });
@@ -1,73 +1,73 @@
1
- "use client";
2
-
3
- import { forwardRef, useState } from "react";
4
- import type { HTMLAttributes, KeyboardEvent } from "react";
5
- import { cx } from "../utils";
6
- import { useControllable } from "../hooks";
7
-
8
- export interface TagInputProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> {
9
- value?: string[];
10
- defaultValue?: string[];
11
- onChange?: (tags: string[]) => void;
12
- /** Suggestion buttons below the control (already used tags are hidden). */
13
- suggestions?: string[];
14
- placeholder?: string;
15
- /** Accessible name of the text input. */
16
- label?: string;
17
- disabled?: boolean;
18
- }
19
-
20
- /** `.oc-tag-input`: chips with remove buttons and a free-text input (Enter or comma adds, Backspace removes). */
21
- export const TagInput = forwardRef<HTMLDivElement, TagInputProps>(function TagInput(
22
- { value, defaultValue = [], onChange, suggestions, placeholder = "Add tag...", label = "Add tag", disabled, className, ...rest },
23
- ref,
24
- ) {
25
- const [tags, setTags] = useControllable<string[]>(value, defaultValue, onChange);
26
- const [draft, setDraft] = useState("");
27
-
28
- const add = (raw: string) => {
29
- const tag = raw.trim();
30
- if (tag && !tags.includes(tag)) setTags([...tags, tag]);
31
- setDraft("");
32
- };
33
- const remove = (tag: string) => setTags(tags.filter((t) => t !== tag));
34
- const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
35
- if (e.key === "Enter" || e.key === ",") {
36
- e.preventDefault();
37
- add(draft);
38
- } else if (e.key === "Backspace" && !draft && tags.length) {
39
- remove(tags[tags.length - 1]);
40
- }
41
- };
42
- const open = suggestions?.filter((s) => !tags.includes(s)) ?? [];
43
-
44
- return (
45
- <>
46
- <div ref={ref} className={cx("oc-tag-input", className)} {...rest}>
47
- {tags.map((tag) => (
48
- <span className="oc-tag" key={tag}>
49
- {tag}{" "}
50
- <button className="oc-tag-remove" type="button" aria-label={`Remove ${tag}`} disabled={disabled} onClick={() => remove(tag)}>&times;</button>
51
- </span>
52
- ))}
53
- <input
54
- type="text"
55
- placeholder={placeholder}
56
- aria-label={label}
57
- value={draft}
58
- disabled={disabled}
59
- onChange={(e) => setDraft(e.target.value)}
60
- onKeyDown={onKeyDown}
61
- onBlur={() => draft && add(draft)}
62
- />
63
- </div>
64
- {open.length > 0 && (
65
- <div className="oc-tag-suggestions">
66
- {open.map((s) => (
67
- <button type="button" key={s} disabled={disabled} onClick={() => add(s)}>{s}</button>
68
- ))}
69
- </div>
70
- )}
71
- </>
72
- );
73
- });
1
+ "use client";
2
+
3
+ import { forwardRef, useState } from "react";
4
+ import type { HTMLAttributes, KeyboardEvent } from "react";
5
+ import { cx } from "../utils";
6
+ import { useControllable } from "../hooks";
7
+
8
+ export interface TagInputProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> {
9
+ value?: string[];
10
+ defaultValue?: string[];
11
+ onChange?: (tags: string[]) => void;
12
+ /** Suggestion buttons below the control (already used tags are hidden). */
13
+ suggestions?: string[];
14
+ placeholder?: string;
15
+ /** Accessible name of the text input. */
16
+ label?: string;
17
+ disabled?: boolean;
18
+ }
19
+
20
+ /** `.oc-tag-input`: chips with remove buttons and a free-text input (Enter or comma adds, Backspace removes). */
21
+ export const TagInput = forwardRef<HTMLDivElement, TagInputProps>(function TagInput(
22
+ { value, defaultValue = [], onChange, suggestions, placeholder = "Add tag...", label = "Add tag", disabled, className, ...rest },
23
+ ref,
24
+ ) {
25
+ const [tags, setTags] = useControllable<string[]>(value, defaultValue, onChange);
26
+ const [draft, setDraft] = useState("");
27
+
28
+ const add = (raw: string) => {
29
+ const tag = raw.trim();
30
+ if (tag && !tags.includes(tag)) setTags([...tags, tag]);
31
+ setDraft("");
32
+ };
33
+ const remove = (tag: string) => setTags(tags.filter((t) => t !== tag));
34
+ const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
35
+ if (e.key === "Enter" || e.key === ",") {
36
+ e.preventDefault();
37
+ add(draft);
38
+ } else if (e.key === "Backspace" && !draft && tags.length) {
39
+ remove(tags[tags.length - 1]);
40
+ }
41
+ };
42
+ const open = suggestions?.filter((s) => !tags.includes(s)) ?? [];
43
+
44
+ return (
45
+ <>
46
+ <div ref={ref} className={cx("oc-tag-input", className)} {...rest}>
47
+ {tags.map((tag) => (
48
+ <span className="oc-tag" key={tag}>
49
+ {tag}{" "}
50
+ <button className="oc-tag-remove" type="button" aria-label={`Remove ${tag}`} disabled={disabled} onClick={() => remove(tag)}>&times;</button>
51
+ </span>
52
+ ))}
53
+ <input
54
+ type="text"
55
+ placeholder={placeholder}
56
+ aria-label={label}
57
+ value={draft}
58
+ disabled={disabled}
59
+ onChange={(e) => setDraft(e.target.value)}
60
+ onKeyDown={onKeyDown}
61
+ onBlur={() => draft && add(draft)}
62
+ />
63
+ </div>
64
+ {open.length > 0 && (
65
+ <div className="oc-tag-suggestions">
66
+ {open.map((s) => (
67
+ <button type="button" key={s} disabled={disabled} onClick={() => add(s)}>{s}</button>
68
+ ))}
69
+ </div>
70
+ )}
71
+ </>
72
+ );
73
+ });
@@ -1,63 +1,63 @@
1
- // File: packages/react/src/components/TerminalLog.tsx
2
- // Map: types -> TerminalLog (bar: dots, title, status -> body role=log: lines + prompt) -> auto-scroll effect
3
- // Same markup as packages/ui/snippets/terminal-log.html. New lines scroll into view (autoScroll).
4
- "use client";
5
-
6
- import { forwardRef, useEffect, useRef } from "react";
7
- import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
8
- import { cx } from "../utils";
9
-
10
- export type TerminalLogLevel = "info" | "success" | "warn" | "error" | "debug";
11
-
12
- export interface TerminalLogLine {
13
- level: TerminalLogLevel;
14
- message: ReactNode;
15
- /** "09:41:02". Leave out for a two-column line. */
16
- time?: string;
17
- /** Text in the level column (defaults to the level name; "ok" for success). */
18
- label?: string;
19
- }
20
-
21
- export interface TerminalLogProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
22
- lines: TerminalLogLine[];
23
- title?: ReactNode;
24
- /** Text next to the pulsing dot ("Live"). No dot when empty. */
25
- status?: ReactNode;
26
- /** Prompt line with a blinking cursor. `true` = empty prompt. */
27
- prompt?: ReactNode | true;
28
- /** Max height of the body, e.g. "12rem". */
29
- height?: string;
30
- /** Keep the newest line in view. Default true. */
31
- autoScroll?: boolean;
32
- }
33
-
34
- /** `.oc-terminal-log`: dark console window. */
35
- export const TerminalLog = forwardRef<HTMLDivElement, TerminalLogProps>(function TerminalLog(
36
- { lines, title, status, prompt, height, autoScroll = true, className, style, "aria-label": label = "Log output", ...rest },
37
- ref,
38
- ) {
39
- const body = useRef<HTMLDivElement>(null);
40
- useEffect(() => {
41
- if (autoScroll && body.current) body.current.scrollTop = body.current.scrollHeight;
42
- }, [lines.length, autoScroll]);
43
- const vars = height ? ({ "--oc-terminal-log-height": height } as CSSProperties) : undefined;
44
- return (
45
- <div ref={ref} className={cx("oc-terminal-log", className)} style={{ ...vars, ...style }} {...rest}>
46
- <div className="oc-terminal-log__bar">
47
- <i /><i /><i />
48
- {title != null && <span className="oc-terminal-log__title">{title}</span>}
49
- {status != null && <span className="oc-terminal-log__status">{status}</span>}
50
- </div>
51
- <div ref={body} className="oc-terminal-log__body" role="log" aria-live="polite" aria-label={label} tabIndex={0}>
52
- {lines.map((l, i) => (
53
- <p key={i} className={cx("oc-terminal-log__line", `oc-terminal-log__line--${l.level}`)}>
54
- {l.time && <time className="oc-terminal-log__time">{l.time}</time>}
55
- <span className="oc-terminal-log__level">{l.label ?? (l.level === "success" ? "ok" : l.level)}</span>
56
- <span className="oc-terminal-log__msg">{l.message}</span>
57
- </p>
58
- ))}
59
- {prompt != null && prompt !== false && <p className="oc-terminal-log__prompt">{prompt === true ? null : prompt}</p>}
60
- </div>
61
- </div>
62
- );
63
- });
1
+ // File: packages/react/src/components/TerminalLog.tsx
2
+ // Map: types -> TerminalLog (bar: dots, title, status -> body role=log: lines + prompt) -> auto-scroll effect
3
+ // Same markup as packages/ui/snippets/terminal-log.html. New lines scroll into view (autoScroll).
4
+ "use client";
5
+
6
+ import { forwardRef, useEffect, useRef } from "react";
7
+ import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
8
+ import { cx } from "../utils";
9
+
10
+ export type TerminalLogLevel = "info" | "success" | "warn" | "error" | "debug";
11
+
12
+ export interface TerminalLogLine {
13
+ level: TerminalLogLevel;
14
+ message: ReactNode;
15
+ /** "09:41:02". Leave out for a two-column line. */
16
+ time?: string;
17
+ /** Text in the level column (defaults to the level name; "ok" for success). */
18
+ label?: string;
19
+ }
20
+
21
+ export interface TerminalLogProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
22
+ lines: TerminalLogLine[];
23
+ title?: ReactNode;
24
+ /** Text next to the pulsing dot ("Live"). No dot when empty. */
25
+ status?: ReactNode;
26
+ /** Prompt line with a blinking cursor. `true` = empty prompt. */
27
+ prompt?: ReactNode | true;
28
+ /** Max height of the body, e.g. "12rem". */
29
+ height?: string;
30
+ /** Keep the newest line in view. Default true. */
31
+ autoScroll?: boolean;
32
+ }
33
+
34
+ /** `.oc-terminal-log`: dark console window. */
35
+ export const TerminalLog = forwardRef<HTMLDivElement, TerminalLogProps>(function TerminalLog(
36
+ { lines, title, status, prompt, height, autoScroll = true, className, style, "aria-label": label = "Log output", ...rest },
37
+ ref,
38
+ ) {
39
+ const body = useRef<HTMLDivElement>(null);
40
+ useEffect(() => {
41
+ if (autoScroll && body.current) body.current.scrollTop = body.current.scrollHeight;
42
+ }, [lines.length, autoScroll]);
43
+ const vars = height ? ({ "--oc-terminal-log-height": height } as CSSProperties) : undefined;
44
+ return (
45
+ <div ref={ref} className={cx("oc-terminal-log", className)} style={{ ...vars, ...style }} {...rest}>
46
+ <div className="oc-terminal-log__bar">
47
+ <i /><i /><i />
48
+ {title != null && <span className="oc-terminal-log__title">{title}</span>}
49
+ {status != null && <span className="oc-terminal-log__status">{status}</span>}
50
+ </div>
51
+ <div ref={body} className="oc-terminal-log__body" role="log" aria-live="polite" aria-label={label} tabIndex={0}>
52
+ {lines.map((l, i) => (
53
+ <p key={i} className={cx("oc-terminal-log__line", `oc-terminal-log__line--${l.level}`)}>
54
+ {l.time && <time className="oc-terminal-log__time">{l.time}</time>}
55
+ <span className="oc-terminal-log__level">{l.label ?? (l.level === "success" ? "ok" : l.level)}</span>
56
+ <span className="oc-terminal-log__msg">{l.message}</span>
57
+ </p>
58
+ ))}
59
+ {prompt != null && prompt !== false && <p className="oc-terminal-log__prompt">{prompt === true ? null : prompt}</p>}
60
+ </div>
61
+ </div>
62
+ );
63
+ });
@@ -1,19 +1,19 @@
1
- import { forwardRef } from "react";
2
- import type { TextareaHTMLAttributes } from "react";
3
- import { cx } from "../utils";
4
-
5
- export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
6
- error?: boolean;
7
- }
8
-
9
- /** `<textarea class="oc-input">` */
10
- export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea({ error, className, ...rest }, ref) {
11
- return (
12
- <textarea
13
- ref={ref}
14
- className={cx("oc-input", error && "oc-input--error", className)}
15
- aria-invalid={error ? true : rest["aria-invalid"]}
16
- {...rest}
17
- />
18
- );
19
- });
1
+ import { forwardRef } from "react";
2
+ import type { TextareaHTMLAttributes } from "react";
3
+ import { cx } from "../utils";
4
+
5
+ export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
6
+ error?: boolean;
7
+ }
8
+
9
+ /** `<textarea class="oc-input">` */
10
+ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea({ error, className, ...rest }, ref) {
11
+ return (
12
+ <textarea
13
+ ref={ref}
14
+ className={cx("oc-input", error && "oc-input--error", className)}
15
+ aria-invalid={error ? true : rest["aria-invalid"]}
16
+ {...rest}
17
+ />
18
+ );
19
+ });
@@ -1,45 +1,45 @@
1
- "use client";
2
-
3
- import { forwardRef, useId } from "react";
4
- import type { HTMLAttributes } from "react";
5
- import { cx } from "../utils";
6
- import { useControllable } from "../hooks";
7
-
8
- export type ThemeMode = "system" | "light" | "dark";
9
-
10
- const LABELS: Record<ThemeMode, string> = { system: "System", light: "Light", dark: "Dark" };
11
-
12
- export interface ThemeSwitchProps extends Omit<HTMLAttributes<HTMLElement>, "onChange" | "defaultValue"> {
13
- value?: ThemeMode;
14
- defaultValue?: ThemeMode;
15
- onChange?: (mode: ThemeMode) => void;
16
- /** Which modes to offer (default all three). */
17
- modes?: ThemeMode[];
18
- /** Radio group name (generated when omitted). */
19
- name?: string;
20
- /** Render a `div role="radiogroup"` (compact app bars) instead of a `fieldset`. */
21
- as?: "fieldset" | "div";
22
- }
23
-
24
- /**
25
- * `.oc-theme-switch` radio group. The CSS applies `color-scheme` through `:has()`,
26
- * so no state is needed; `onChange` lets you persist the choice.
27
- */
28
- export const ThemeSwitch = forwardRef<HTMLElement, ThemeSwitchProps>(function ThemeSwitch(
29
- { value, defaultValue = "system", onChange, modes = ["system", "light", "dark"], name, as = "fieldset", className, "aria-label": ariaLabel = "Color theme", ...rest },
30
- ref,
31
- ) {
32
- const auto = useId();
33
- const group = name ?? `oc-theme-${auto.replace(/:/g, "")}`;
34
- const [current, setCurrent] = useControllable<ThemeMode>(value, defaultValue, onChange);
35
- const items = modes.map((m) => (
36
- <label key={m}>
37
- <input type="radio" name={group} value={m} checked={current === m} onChange={() => setCurrent(m)} /> {LABELS[m]}
38
- </label>
39
- ));
40
- return as === "div" ? (
41
- <div ref={ref as React.Ref<HTMLDivElement>} className={cx("oc-theme-switch", className)} role="radiogroup" aria-label={ariaLabel} {...rest}>{items}</div>
42
- ) : (
43
- <fieldset ref={ref as React.Ref<HTMLFieldSetElement>} className={cx("oc-theme-switch", className)} aria-label={ariaLabel} {...rest}>{items}</fieldset>
44
- );
45
- });
1
+ "use client";
2
+
3
+ import { forwardRef, useId } from "react";
4
+ import type { HTMLAttributes } from "react";
5
+ import { cx } from "../utils";
6
+ import { useControllable } from "../hooks";
7
+
8
+ export type ThemeMode = "system" | "light" | "dark";
9
+
10
+ const LABELS: Record<ThemeMode, string> = { system: "System", light: "Light", dark: "Dark" };
11
+
12
+ export interface ThemeSwitchProps extends Omit<HTMLAttributes<HTMLElement>, "onChange" | "defaultValue"> {
13
+ value?: ThemeMode;
14
+ defaultValue?: ThemeMode;
15
+ onChange?: (mode: ThemeMode) => void;
16
+ /** Which modes to offer (default all three). */
17
+ modes?: ThemeMode[];
18
+ /** Radio group name (generated when omitted). */
19
+ name?: string;
20
+ /** Render a `div role="radiogroup"` (compact app bars) instead of a `fieldset`. */
21
+ as?: "fieldset" | "div";
22
+ }
23
+
24
+ /**
25
+ * `.oc-theme-switch` radio group. The CSS applies `color-scheme` through `:has()`,
26
+ * so no state is needed; `onChange` lets you persist the choice.
27
+ */
28
+ export const ThemeSwitch = forwardRef<HTMLElement, ThemeSwitchProps>(function ThemeSwitch(
29
+ { value, defaultValue = "system", onChange, modes = ["system", "light", "dark"], name, as = "fieldset", className, "aria-label": ariaLabel = "Color theme", ...rest },
30
+ ref,
31
+ ) {
32
+ const auto = useId();
33
+ const group = name ?? `oc-theme-${auto.replace(/:/g, "")}`;
34
+ const [current, setCurrent] = useControllable<ThemeMode>(value, defaultValue, onChange);
35
+ const items = modes.map((m) => (
36
+ <label key={m}>
37
+ <input type="radio" name={group} value={m} checked={current === m} onChange={() => setCurrent(m)} /> {LABELS[m]}
38
+ </label>
39
+ ));
40
+ return as === "div" ? (
41
+ <div ref={ref as React.Ref<HTMLDivElement>} className={cx("oc-theme-switch", className)} role="radiogroup" aria-label={ariaLabel} {...rest}>{items}</div>
42
+ ) : (
43
+ <fieldset ref={ref as React.Ref<HTMLFieldSetElement>} className={cx("oc-theme-switch", className)} aria-label={ariaLabel} {...rest}>{items}</fieldset>
44
+ );
45
+ });
@@ -1,76 +1,76 @@
1
- // File: packages/react/src/components/TimeInput.tsx
2
- // Map: helper (digits -> HH:mm) -> component (text field + datalist of slots)
3
- // Same markup as packages/ui/snippets/time-input.html. JS only adds the colon while typing ("0930" -> "09:30").
4
- "use client";
5
-
6
- import { forwardRef, useId } from "react";
7
- import type { InputHTMLAttributes } from "react";
8
- import { cx } from "../utils";
9
- import { useControllable } from "../hooks";
10
-
11
- export interface TimeInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "type" | "list"> {
12
- /** "HH:mm" or "" (empty). */
13
- value?: string;
14
- defaultValue?: string;
15
- onChange?: (value: string) => void;
16
- /** Suggested times shown by the browser while typing (datalist). */
17
- slots?: string[];
18
- error?: boolean;
19
- }
20
-
21
- /** Keep up to 4 digits and put a colon after the first two: "0930" -> "09:30". Short input is completed on blur. */
22
- export function formatTime24h(raw: string): string {
23
- const digits = raw.replace(/\D/g, "").slice(0, 4);
24
- return digits.length <= 2 ? digits : `${digits.slice(0, 2)}:${digits.slice(2)}`;
25
- }
26
-
27
- /** On blur: "9" -> "09:00", "930" -> "09:30". Leaves other text alone (pattern marks it invalid). */
28
- function completeTime(text: string): string {
29
- const digits = text.replace(/\D/g, "");
30
- if (digits.length === 1 || digits.length === 2) return `${digits.padStart(2, "0")}:00`;
31
- if (digits.length === 3) return `0${digits[0]}:${digits.slice(1)}`;
32
- return text;
33
- }
34
-
35
- /** `.oc-time-input`: 24h text field (pattern HH:mm) with optional datalist suggestions. */
36
- export const TimeInput = forwardRef<HTMLInputElement, TimeInputProps>(function TimeInput(
37
- { value, defaultValue, onChange, slots, error, className, onBlur, placeholder = "hh:mm", ...rest },
38
- ref,
39
- ) {
40
- const listId = `oc-time-${useId().replace(/:/g, "")}`;
41
- const [current, setCurrent] = useControllable<string>(value, defaultValue ?? "", onChange);
42
- return (
43
- <>
44
- <input
45
- ref={ref}
46
- className={cx("oc-input", "oc-time-input", className)}
47
- type="text"
48
- inputMode="numeric"
49
- maxLength={5}
50
- pattern="([01][0-9]|2[0-3]):[0-5][0-9]"
51
- placeholder={placeholder}
52
- list={slots?.length ? listId : undefined}
53
- aria-invalid={error ? true : undefined}
54
- value={current}
55
- onChange={(e) => {
56
- // A picked datalist option already has the colon; typed digits get one.
57
- const v = e.target.value;
58
- setCurrent(/^\d{2}:\d{2}$/.test(v) ? v : formatTime24h(v));
59
- }}
60
- onBlur={(e) => {
61
- const done = completeTime(current);
62
- if (done !== current) setCurrent(done);
63
- onBlur?.(e);
64
- }}
65
- {...rest}
66
- />
67
- {slots?.length ? (
68
- <datalist id={listId}>
69
- {slots.map((s) => (
70
- <option key={s} value={s} />
71
- ))}
72
- </datalist>
73
- ) : null}
74
- </>
75
- );
76
- });
1
+ // File: packages/react/src/components/TimeInput.tsx
2
+ // Map: helper (digits -> HH:mm) -> component (text field + datalist of slots)
3
+ // Same markup as packages/ui/snippets/time-input.html. JS only adds the colon while typing ("0930" -> "09:30").
4
+ "use client";
5
+
6
+ import { forwardRef, useId } from "react";
7
+ import type { InputHTMLAttributes } from "react";
8
+ import { cx } from "../utils";
9
+ import { useControllable } from "../hooks";
10
+
11
+ export interface TimeInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "type" | "list"> {
12
+ /** "HH:mm" or "" (empty). */
13
+ value?: string;
14
+ defaultValue?: string;
15
+ onChange?: (value: string) => void;
16
+ /** Suggested times shown by the browser while typing (datalist). */
17
+ slots?: string[];
18
+ error?: boolean;
19
+ }
20
+
21
+ /** Keep up to 4 digits and put a colon after the first two: "0930" -> "09:30". Short input is completed on blur. */
22
+ export function formatTime24h(raw: string): string {
23
+ const digits = raw.replace(/\D/g, "").slice(0, 4);
24
+ return digits.length <= 2 ? digits : `${digits.slice(0, 2)}:${digits.slice(2)}`;
25
+ }
26
+
27
+ /** On blur: "9" -> "09:00", "930" -> "09:30". Leaves other text alone (pattern marks it invalid). */
28
+ function completeTime(text: string): string {
29
+ const digits = text.replace(/\D/g, "");
30
+ if (digits.length === 1 || digits.length === 2) return `${digits.padStart(2, "0")}:00`;
31
+ if (digits.length === 3) return `0${digits[0]}:${digits.slice(1)}`;
32
+ return text;
33
+ }
34
+
35
+ /** `.oc-time-input`: 24h text field (pattern HH:mm) with optional datalist suggestions. */
36
+ export const TimeInput = forwardRef<HTMLInputElement, TimeInputProps>(function TimeInput(
37
+ { value, defaultValue, onChange, slots, error, className, onBlur, placeholder = "hh:mm", ...rest },
38
+ ref,
39
+ ) {
40
+ const listId = `oc-time-${useId().replace(/:/g, "")}`;
41
+ const [current, setCurrent] = useControllable<string>(value, defaultValue ?? "", onChange);
42
+ return (
43
+ <>
44
+ <input
45
+ ref={ref}
46
+ className={cx("oc-input", "oc-time-input", className)}
47
+ type="text"
48
+ inputMode="numeric"
49
+ maxLength={5}
50
+ pattern="([01][0-9]|2[0-3]):[0-5][0-9]"
51
+ placeholder={placeholder}
52
+ list={slots?.length ? listId : undefined}
53
+ aria-invalid={error ? true : undefined}
54
+ value={current}
55
+ onChange={(e) => {
56
+ // A picked datalist option already has the colon; typed digits get one.
57
+ const v = e.target.value;
58
+ setCurrent(/^\d{2}:\d{2}$/.test(v) ? v : formatTime24h(v));
59
+ }}
60
+ onBlur={(e) => {
61
+ const done = completeTime(current);
62
+ if (done !== current) setCurrent(done);
63
+ onBlur?.(e);
64
+ }}
65
+ {...rest}
66
+ />
67
+ {slots?.length ? (
68
+ <datalist id={listId}>
69
+ {slots.map((s) => (
70
+ <option key={s} value={s} />
71
+ ))}
72
+ </datalist>
73
+ ) : null}
74
+ </>
75
+ );
76
+ });