@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,36 +1,36 @@
1
- "use client";
2
-
3
- import { forwardRef, useState } from "react";
4
- import type { InputHTMLAttributes } from "react";
5
- import { cx } from "../utils";
6
-
7
- export interface PasswordInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type"> {
8
- showLabel?: string;
9
- hideLabel?: string;
10
- /** Accessible name of the toggle button. */
11
- toggleLabel?: string;
12
- error?: boolean;
13
- }
14
-
15
- /** Password field with a show / hide button (`aria-pressed`), inside `.oc-input-reveal`. */
16
- export const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(function PasswordInput(
17
- { showLabel = "Show", hideLabel = "Hide", toggleLabel = "Toggle password visibility", error, className, ...rest },
18
- ref,
19
- ) {
20
- const [shown, setShown] = useState(false);
21
- return (
22
- <div className="oc-input-reveal">
23
- <input
24
- ref={ref}
25
- className={cx("oc-input", error && "oc-input--error", className)}
26
- type={shown ? "text" : "password"}
27
- autoComplete="current-password"
28
- aria-invalid={error ? true : undefined}
29
- {...rest}
30
- />
31
- <button className="oc-input-reveal-button" type="button" aria-pressed={shown} aria-label={toggleLabel} onClick={() => setShown((s) => !s)}>
32
- {shown ? hideLabel : showLabel}
33
- </button>
34
- </div>
35
- );
36
- });
1
+ "use client";
2
+
3
+ import { forwardRef, useState } from "react";
4
+ import type { InputHTMLAttributes } from "react";
5
+ import { cx } from "../utils";
6
+
7
+ export interface PasswordInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type"> {
8
+ showLabel?: string;
9
+ hideLabel?: string;
10
+ /** Accessible name of the toggle button. */
11
+ toggleLabel?: string;
12
+ error?: boolean;
13
+ }
14
+
15
+ /** Password field with a show / hide button (`aria-pressed`), inside `.oc-input-reveal`. */
16
+ export const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(function PasswordInput(
17
+ { showLabel = "Show", hideLabel = "Hide", toggleLabel = "Toggle password visibility", error, className, ...rest },
18
+ ref,
19
+ ) {
20
+ const [shown, setShown] = useState(false);
21
+ return (
22
+ <div className="oc-input-reveal">
23
+ <input
24
+ ref={ref}
25
+ className={cx("oc-input", error && "oc-input--error", className)}
26
+ type={shown ? "text" : "password"}
27
+ autoComplete="current-password"
28
+ aria-invalid={error ? true : undefined}
29
+ {...rest}
30
+ />
31
+ <button className="oc-input-reveal-button" type="button" aria-pressed={shown} aria-label={toggleLabel} onClick={() => setShown((s) => !s)}>
32
+ {shown ? hideLabel : showLabel}
33
+ </button>
34
+ </div>
35
+ );
36
+ });
@@ -1,46 +1,46 @@
1
- // File: packages/react/src/components/PasswordRequirements.tsx
2
- // Map: rule type + default rules -> component (live list, --met per rule)
3
- // Same markup as packages/ui/snippets/password-requirements.html.
4
- import { forwardRef } from "react";
5
- import type { HTMLAttributes, ReactNode } from "react";
6
- import { cx } from "../utils";
7
-
8
- export interface PasswordRule {
9
- label: ReactNode;
10
- /** True when the password meets the rule. */
11
- test: (password: string) => boolean;
12
- }
13
-
14
- /** Common rules: length, uppercase, number, symbol. Pass your own list to change them. */
15
- export const defaultPasswordRules: PasswordRule[] = [
16
- { label: "At least 8 characters", test: (p) => p.length >= 8 },
17
- { label: "One uppercase letter", test: (p) => /[A-Z]/.test(p) },
18
- { label: "One number", test: (p) => /[0-9]/.test(p) },
19
- { label: "One symbol", test: (p) => /[^A-Za-z0-9]/.test(p) },
20
- ];
21
-
22
- export interface PasswordRequirementsProps extends HTMLAttributes<HTMLUListElement> {
23
- password: string;
24
- rules?: PasswordRule[];
25
- /** Rules side by side. */
26
- inline?: boolean;
27
- /** Render nothing until the user types. */
28
- hideWhenEmpty?: boolean;
29
- }
30
-
31
- /** `.oc-password-requirements`: each rule turns green (--met) as soon as it passes. */
32
- export const PasswordRequirements = forwardRef<HTMLUListElement, PasswordRequirementsProps>(function PasswordRequirements(
33
- { password, rules = defaultPasswordRules, inline, hideWhenEmpty, className, ...rest },
34
- ref,
35
- ) {
36
- if (hideWhenEmpty && !password) return null;
37
- return (
38
- <ul ref={ref} className={cx("oc-password-requirements", inline && "oc-password-requirements--inline", className)} aria-live="polite" {...rest}>
39
- {rules.map((rule, i) => (
40
- <li key={i} className={cx("oc-password-requirement", rule.test(password) && "oc-password-requirement--met")}>
41
- {rule.label}
42
- </li>
43
- ))}
44
- </ul>
45
- );
46
- });
1
+ // File: packages/react/src/components/PasswordRequirements.tsx
2
+ // Map: rule type + default rules -> component (live list, --met per rule)
3
+ // Same markup as packages/ui/snippets/password-requirements.html.
4
+ import { forwardRef } from "react";
5
+ import type { HTMLAttributes, ReactNode } from "react";
6
+ import { cx } from "../utils";
7
+
8
+ export interface PasswordRule {
9
+ label: ReactNode;
10
+ /** True when the password meets the rule. */
11
+ test: (password: string) => boolean;
12
+ }
13
+
14
+ /** Common rules: length, uppercase, number, symbol. Pass your own list to change them. */
15
+ export const defaultPasswordRules: PasswordRule[] = [
16
+ { label: "At least 8 characters", test: (p) => p.length >= 8 },
17
+ { label: "One uppercase letter", test: (p) => /[A-Z]/.test(p) },
18
+ { label: "One number", test: (p) => /[0-9]/.test(p) },
19
+ { label: "One symbol", test: (p) => /[^A-Za-z0-9]/.test(p) },
20
+ ];
21
+
22
+ export interface PasswordRequirementsProps extends HTMLAttributes<HTMLUListElement> {
23
+ password: string;
24
+ rules?: PasswordRule[];
25
+ /** Rules side by side. */
26
+ inline?: boolean;
27
+ /** Render nothing until the user types. */
28
+ hideWhenEmpty?: boolean;
29
+ }
30
+
31
+ /** `.oc-password-requirements`: each rule turns green (--met) as soon as it passes. */
32
+ export const PasswordRequirements = forwardRef<HTMLUListElement, PasswordRequirementsProps>(function PasswordRequirements(
33
+ { password, rules = defaultPasswordRules, inline, hideWhenEmpty, className, ...rest },
34
+ ref,
35
+ ) {
36
+ if (hideWhenEmpty && !password) return null;
37
+ return (
38
+ <ul ref={ref} className={cx("oc-password-requirements", inline && "oc-password-requirements--inline", className)} aria-live="polite" {...rest}>
39
+ {rules.map((rule, i) => (
40
+ <li key={i} className={cx("oc-password-requirement", rule.test(password) && "oc-password-requirement--met")}>
41
+ {rule.label}
42
+ </li>
43
+ ))}
44
+ </ul>
45
+ );
46
+ });
@@ -1,93 +1,93 @@
1
- // File: packages/react/src/components/PhoneInput.tsx
2
- // Map: types -> helpers (split value, group digits) -> component (dial select + tel input)
3
- // Same markup as packages/ui/snippets/phone-input.html. The only JS: digit grouping and joining dial + number.
4
- "use client";
5
-
6
- import { forwardRef, useState } from "react";
7
- import type { InputHTMLAttributes } from "react";
8
- import { cx } from "../utils";
9
- import { useControllable } from "../hooks";
10
-
11
- export interface PhoneCountry {
12
- /** Dial code with plus sign, e.g. "+32". */
13
- dial: string;
14
- /** Text in the select, e.g. "BE +32". */
15
- label: string;
16
- }
17
-
18
- export interface PhoneInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "size"> {
19
- /** Full value without spaces, e.g. "+32470123456" (empty string = no number). */
20
- value?: string;
21
- defaultValue?: string;
22
- onChange?: (value: string) => void;
23
- countries?: PhoneCountry[];
24
- /** Dial code used while the value is empty. */
25
- defaultDial?: string;
26
- size?: "sm" | "md";
27
- error?: boolean;
28
- /** Accessible name of the dial code select. */
29
- dialLabel?: string;
30
- }
31
-
32
- const DEFAULT_COUNTRIES: PhoneCountry[] = [
33
- { dial: "+32", label: "BE +32" },
34
- { dial: "+31", label: "NL +31" },
35
- { dial: "+33", label: "FR +33" },
36
- { dial: "+49", label: "DE +49" },
37
- ];
38
-
39
- /** "470123456" -> "470 12 34 56": a group of 3, then pairs (readable for most European numbers). */
40
- export function formatPhoneNational(digits: string): string {
41
- const clean = digits.replace(/\D/g, "");
42
- const head = clean.slice(0, 3);
43
- const pairs = clean.slice(3).match(/.{1,2}/g) ?? [];
44
- return [head, ...pairs].filter(Boolean).join(" ");
45
- }
46
-
47
- /** Split "+32470..." into the known dial code (longest match) and the national digits. */
48
- function splitPhone(full: string, countries: PhoneCountry[]) {
49
- const match = [...countries].sort((a, b) => b.dial.length - a.dial.length).find((c) => full.startsWith(c.dial));
50
- return match ? { dial: match.dial, national: full.slice(match.dial.length) } : { dial: "", national: full.replace(/^\+/, "") };
51
- }
52
-
53
- /** `.oc-phone-input`: country dial code + national number. Emits one value: dial + digits. */
54
- export const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(function PhoneInput(
55
- { value, defaultValue, onChange, countries = DEFAULT_COUNTRIES, defaultDial, size = "md", error, dialLabel = "Country code", className, disabled, ...rest },
56
- ref,
57
- ) {
58
- const [full, setFull] = useControllable<string>(value, defaultValue ?? "", onChange);
59
- const parsed = splitPhone(full, countries);
60
- // Remember the chosen dial code, also while the number is still empty.
61
- const [dialState, setDialState] = useState(parsed.dial || defaultDial || countries[0]?.dial || "");
62
- const dial = parsed.dial || dialState;
63
-
64
- const emit = (nextDial: string, national: string) => {
65
- setDialState(nextDial);
66
- const digits = national.replace(/\D/g, "");
67
- setFull(digits ? nextDial + digits : "");
68
- };
69
-
70
- return (
71
- <div className={cx("oc-phone-input", size === "sm" && "oc-phone-input--sm", error && "oc-phone-input--error", className)}>
72
- <select className="oc-phone-input-dial" aria-label={dialLabel} value={dial} disabled={disabled} onChange={(e) => emit(e.target.value, parsed.national)}>
73
- {countries.map((c) => (
74
- <option key={c.dial} value={c.dial}>
75
- {c.label}
76
- </option>
77
- ))}
78
- </select>
79
- <input
80
- ref={ref}
81
- className="oc-phone-input-number"
82
- type="tel"
83
- inputMode="tel"
84
- autoComplete="tel-national"
85
- aria-invalid={error ? true : undefined}
86
- disabled={disabled}
87
- value={formatPhoneNational(parsed.national)}
88
- onChange={(e) => emit(dial, e.target.value)}
89
- {...rest}
90
- />
91
- </div>
92
- );
93
- });
1
+ // File: packages/react/src/components/PhoneInput.tsx
2
+ // Map: types -> helpers (split value, group digits) -> component (dial select + tel input)
3
+ // Same markup as packages/ui/snippets/phone-input.html. The only JS: digit grouping and joining dial + number.
4
+ "use client";
5
+
6
+ import { forwardRef, useState } from "react";
7
+ import type { InputHTMLAttributes } from "react";
8
+ import { cx } from "../utils";
9
+ import { useControllable } from "../hooks";
10
+
11
+ export interface PhoneCountry {
12
+ /** Dial code with plus sign, e.g. "+32". */
13
+ dial: string;
14
+ /** Text in the select, e.g. "BE +32". */
15
+ label: string;
16
+ }
17
+
18
+ export interface PhoneInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "size"> {
19
+ /** Full value without spaces, e.g. "+32470123456" (empty string = no number). */
20
+ value?: string;
21
+ defaultValue?: string;
22
+ onChange?: (value: string) => void;
23
+ countries?: PhoneCountry[];
24
+ /** Dial code used while the value is empty. */
25
+ defaultDial?: string;
26
+ size?: "sm" | "md";
27
+ error?: boolean;
28
+ /** Accessible name of the dial code select. */
29
+ dialLabel?: string;
30
+ }
31
+
32
+ const DEFAULT_COUNTRIES: PhoneCountry[] = [
33
+ { dial: "+32", label: "BE +32" },
34
+ { dial: "+31", label: "NL +31" },
35
+ { dial: "+33", label: "FR +33" },
36
+ { dial: "+49", label: "DE +49" },
37
+ ];
38
+
39
+ /** "470123456" -> "470 12 34 56": a group of 3, then pairs (readable for most European numbers). */
40
+ export function formatPhoneNational(digits: string): string {
41
+ const clean = digits.replace(/\D/g, "");
42
+ const head = clean.slice(0, 3);
43
+ const pairs = clean.slice(3).match(/.{1,2}/g) ?? [];
44
+ return [head, ...pairs].filter(Boolean).join(" ");
45
+ }
46
+
47
+ /** Split "+32470..." into the known dial code (longest match) and the national digits. */
48
+ function splitPhone(full: string, countries: PhoneCountry[]) {
49
+ const match = [...countries].sort((a, b) => b.dial.length - a.dial.length).find((c) => full.startsWith(c.dial));
50
+ return match ? { dial: match.dial, national: full.slice(match.dial.length) } : { dial: "", national: full.replace(/^\+/, "") };
51
+ }
52
+
53
+ /** `.oc-phone-input`: country dial code + national number. Emits one value: dial + digits. */
54
+ export const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(function PhoneInput(
55
+ { value, defaultValue, onChange, countries = DEFAULT_COUNTRIES, defaultDial, size = "md", error, dialLabel = "Country code", className, disabled, ...rest },
56
+ ref,
57
+ ) {
58
+ const [full, setFull] = useControllable<string>(value, defaultValue ?? "", onChange);
59
+ const parsed = splitPhone(full, countries);
60
+ // Remember the chosen dial code, also while the number is still empty.
61
+ const [dialState, setDialState] = useState(parsed.dial || defaultDial || countries[0]?.dial || "");
62
+ const dial = parsed.dial || dialState;
63
+
64
+ const emit = (nextDial: string, national: string) => {
65
+ setDialState(nextDial);
66
+ const digits = national.replace(/\D/g, "");
67
+ setFull(digits ? nextDial + digits : "");
68
+ };
69
+
70
+ return (
71
+ <div className={cx("oc-phone-input", size === "sm" && "oc-phone-input--sm", error && "oc-phone-input--error", className)}>
72
+ <select className="oc-phone-input-dial" aria-label={dialLabel} value={dial} disabled={disabled} onChange={(e) => emit(e.target.value, parsed.national)}>
73
+ {countries.map((c) => (
74
+ <option key={c.dial} value={c.dial}>
75
+ {c.label}
76
+ </option>
77
+ ))}
78
+ </select>
79
+ <input
80
+ ref={ref}
81
+ className="oc-phone-input-number"
82
+ type="tel"
83
+ inputMode="tel"
84
+ autoComplete="tel-national"
85
+ aria-invalid={error ? true : undefined}
86
+ disabled={disabled}
87
+ value={formatPhoneNational(parsed.national)}
88
+ onChange={(e) => emit(dial, e.target.value)}
89
+ {...rest}
90
+ />
91
+ </div>
92
+ );
93
+ });
@@ -1,34 +1,34 @@
1
- import { forwardRef, useId } from "react";
2
- import type { HTMLAttributes, ReactNode } from "react";
3
- import { attrs, cx } from "../utils";
4
- import { Button } from "./Button";
5
- import type { ButtonProps } from "./Button";
6
-
7
- export interface PopoverProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
8
- /** Content of the trigger button. */
9
- trigger: ReactNode;
10
- triggerProps?: ButtonProps;
11
- size?: "sm" | "md" | "lg";
12
- title?: ReactNode;
13
- /** Short body text (`oc-popover-text`). */
14
- text?: ReactNode;
15
- }
16
-
17
- /** Trigger + `<div class="oc-popover" popover>` floating panel. */
18
- export const Popover = forwardRef<HTMLDivElement, PopoverProps>(function Popover(
19
- { trigger, triggerProps, size = "md", title, text, className, id, children, ...rest },
20
- ref,
21
- ) {
22
- const auto = useId();
23
- const panelId = id ?? `oc-popover-${auto.replace(/:/g, "")}`;
24
- return (
25
- <>
26
- <Button variant="outline" type="button" {...triggerProps} {...attrs({ popovertarget: panelId })}>{trigger}</Button>
27
- <div ref={ref} className={cx("oc-popover", size !== "md" && `oc-popover--${size}`, className)} id={panelId} {...attrs({ popover: "" })} {...rest}>
28
- {title != null && <h3 className="oc-popover-title">{title}</h3>}
29
- {text != null && <p className="oc-popover-text">{text}</p>}
30
- {children}
31
- </div>
32
- </>
33
- );
34
- });
1
+ import { forwardRef, useId } from "react";
2
+ import type { HTMLAttributes, ReactNode } from "react";
3
+ import { attrs, cx } from "../utils";
4
+ import { Button } from "./Button";
5
+ import type { ButtonProps } from "./Button";
6
+
7
+ export interface PopoverProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
8
+ /** Content of the trigger button. */
9
+ trigger: ReactNode;
10
+ triggerProps?: ButtonProps;
11
+ size?: "sm" | "md" | "lg";
12
+ title?: ReactNode;
13
+ /** Short body text (`oc-popover-text`). */
14
+ text?: ReactNode;
15
+ }
16
+
17
+ /** Trigger + `<div class="oc-popover" popover>` floating panel. */
18
+ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(function Popover(
19
+ { trigger, triggerProps, size = "md", title, text, className, id, children, ...rest },
20
+ ref,
21
+ ) {
22
+ const auto = useId();
23
+ const panelId = id ?? `oc-popover-${auto.replace(/:/g, "")}`;
24
+ return (
25
+ <>
26
+ <Button variant="outline" type="button" {...triggerProps} {...attrs({ popovertarget: panelId })}>{trigger}</Button>
27
+ <div ref={ref} className={cx("oc-popover", size !== "md" && `oc-popover--${size}`, className)} id={panelId} {...attrs({ popover: "" })} {...rest}>
28
+ {title != null && <h3 className="oc-popover-title">{title}</h3>}
29
+ {text != null && <p className="oc-popover-text">{text}</p>}
30
+ {children}
31
+ </div>
32
+ </>
33
+ );
34
+ });
@@ -1,83 +1,83 @@
1
- // File: packages/react/src/components/PriceCard.tsx | Map: types -> PriceCard (head -> toggle (radios) -> prices -> features -> cta)
2
- import { forwardRef, useId } from "react";
3
- import type { HTMLAttributes, ReactNode } from "react";
4
- import { cx } from "../utils";
5
-
6
- export interface PriceCardFeature {
7
- label: ReactNode;
8
- /** Shown with a cross and muted text. */
9
- excluded?: boolean;
10
- }
11
-
12
- export interface PriceCardPrice {
13
- amount: ReactNode;
14
- period?: ReactNode;
15
- /** Old price (struck through). */
16
- was?: ReactNode;
17
- note?: ReactNode;
18
- }
19
-
20
- export interface PriceCardProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
21
- name: ReactNode;
22
- description?: ReactNode;
23
- /** Badge such as "Save 20%". */
24
- discount?: ReactNode;
25
- monthly: PriceCardPrice;
26
- /** Without a yearly price the toggle is not rendered. */
27
- yearly?: PriceCardPrice;
28
- /** Period that is selected first (uncontrolled radio). */
29
- defaultPeriod?: "monthly" | "yearly";
30
- features?: PriceCardFeature[];
31
- /** Call to action, e.g. <a className="oc-btn oc-price-card__cta">. */
32
- cta?: ReactNode;
33
- featured?: boolean;
34
- /** Labels of the toggle. */
35
- monthlyLabel?: ReactNode;
36
- yearlyLabel?: ReactNode;
37
- }
38
-
39
- function Price({ kind, price }: { kind: "monthly" | "yearly"; price: PriceCardPrice }) {
40
- return (
41
- <p className={`oc-price-card__price oc-price-card__price--${kind}`}>
42
- <span className="oc-price-card__amount">{price.amount}</span>
43
- {price.period != null && <span className="oc-price-card__period">{price.period}</span>}
44
- {price.was != null && <s className="oc-price-card__was">{price.was}</s>}
45
- {price.note != null && <span className="oc-price-card__note">{price.note}</span>}
46
- </p>
47
- );
48
- }
49
-
50
- /** `<article class="oc-price-card">`: plan card with a CSS-only monthly / yearly switch. */
51
- export const PriceCard = forwardRef<HTMLElement, PriceCardProps>(function PriceCard(
52
- { name, description, discount, monthly, yearly, defaultPeriod = "monthly", features = [], cta, featured = false,
53
- monthlyLabel = "Monthly", yearlyLabel = "Yearly", className, ...rest },
54
- ref,
55
- ) {
56
- // Every card needs its own radio group name.
57
- const group = `oc-price-${useId().replace(/[^\w-]/g, "")}`;
58
- return (
59
- <article ref={ref} className={cx("oc-price-card", featured && "oc-price-card--featured", className)} {...rest}>
60
- <div className="oc-price-card__head">
61
- <h3 className="oc-price-card__name">{name}</h3>
62
- {discount != null && <span className="oc-price-card__discount">{discount}</span>}
63
- </div>
64
- {description != null && <p className="oc-price-card__desc">{description}</p>}
65
- {yearly && (
66
- <div className="oc-segmented oc-price-card__toggle" role="radiogroup" aria-label="Billing period">
67
- <label className="oc-segmented-item"><input type="radio" name={group} value="monthly" defaultChecked={defaultPeriod === "monthly"} /> {monthlyLabel}</label>
68
- <label className="oc-segmented-item"><input type="radio" name={group} value="yearly" defaultChecked={defaultPeriod === "yearly"} /> {yearlyLabel}</label>
69
- </div>
70
- )}
71
- <Price kind="monthly" price={monthly} />
72
- {yearly && <Price kind="yearly" price={yearly} />}
73
- {features.length > 0 && (
74
- <ul className="oc-price-card__features">
75
- {features.map((f, i) => (
76
- <li key={i} className={cx("oc-price-card__feature", f.excluded && "oc-price-card__feature--excluded")}>{f.label}</li>
77
- ))}
78
- </ul>
79
- )}
80
- {cta}
81
- </article>
82
- );
83
- });
1
+ // File: packages/react/src/components/PriceCard.tsx | Map: types -> PriceCard (head -> toggle (radios) -> prices -> features -> cta)
2
+ import { forwardRef, useId } from "react";
3
+ import type { HTMLAttributes, ReactNode } from "react";
4
+ import { cx } from "../utils";
5
+
6
+ export interface PriceCardFeature {
7
+ label: ReactNode;
8
+ /** Shown with a cross and muted text. */
9
+ excluded?: boolean;
10
+ }
11
+
12
+ export interface PriceCardPrice {
13
+ amount: ReactNode;
14
+ period?: ReactNode;
15
+ /** Old price (struck through). */
16
+ was?: ReactNode;
17
+ note?: ReactNode;
18
+ }
19
+
20
+ export interface PriceCardProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
21
+ name: ReactNode;
22
+ description?: ReactNode;
23
+ /** Badge such as "Save 20%". */
24
+ discount?: ReactNode;
25
+ monthly: PriceCardPrice;
26
+ /** Without a yearly price the toggle is not rendered. */
27
+ yearly?: PriceCardPrice;
28
+ /** Period that is selected first (uncontrolled radio). */
29
+ defaultPeriod?: "monthly" | "yearly";
30
+ features?: PriceCardFeature[];
31
+ /** Call to action, e.g. <a className="oc-btn oc-price-card__cta">. */
32
+ cta?: ReactNode;
33
+ featured?: boolean;
34
+ /** Labels of the toggle. */
35
+ monthlyLabel?: ReactNode;
36
+ yearlyLabel?: ReactNode;
37
+ }
38
+
39
+ function Price({ kind, price }: { kind: "monthly" | "yearly"; price: PriceCardPrice }) {
40
+ return (
41
+ <p className={`oc-price-card__price oc-price-card__price--${kind}`}>
42
+ <span className="oc-price-card__amount">{price.amount}</span>
43
+ {price.period != null && <span className="oc-price-card__period">{price.period}</span>}
44
+ {price.was != null && <s className="oc-price-card__was">{price.was}</s>}
45
+ {price.note != null && <span className="oc-price-card__note">{price.note}</span>}
46
+ </p>
47
+ );
48
+ }
49
+
50
+ /** `<article class="oc-price-card">`: plan card with a CSS-only monthly / yearly switch. */
51
+ export const PriceCard = forwardRef<HTMLElement, PriceCardProps>(function PriceCard(
52
+ { name, description, discount, monthly, yearly, defaultPeriod = "monthly", features = [], cta, featured = false,
53
+ monthlyLabel = "Monthly", yearlyLabel = "Yearly", className, ...rest },
54
+ ref,
55
+ ) {
56
+ // Every card needs its own radio group name.
57
+ const group = `oc-price-${useId().replace(/[^\w-]/g, "")}`;
58
+ return (
59
+ <article ref={ref} className={cx("oc-price-card", featured && "oc-price-card--featured", className)} {...rest}>
60
+ <div className="oc-price-card__head">
61
+ <h3 className="oc-price-card__name">{name}</h3>
62
+ {discount != null && <span className="oc-price-card__discount">{discount}</span>}
63
+ </div>
64
+ {description != null && <p className="oc-price-card__desc">{description}</p>}
65
+ {yearly && (
66
+ <div className="oc-segmented oc-price-card__toggle" role="radiogroup" aria-label="Billing period">
67
+ <label className="oc-segmented-item"><input type="radio" name={group} value="monthly" defaultChecked={defaultPeriod === "monthly"} /> {monthlyLabel}</label>
68
+ <label className="oc-segmented-item"><input type="radio" name={group} value="yearly" defaultChecked={defaultPeriod === "yearly"} /> {yearlyLabel}</label>
69
+ </div>
70
+ )}
71
+ <Price kind="monthly" price={monthly} />
72
+ {yearly && <Price kind="yearly" price={yearly} />}
73
+ {features.length > 0 && (
74
+ <ul className="oc-price-card__features">
75
+ {features.map((f, i) => (
76
+ <li key={i} className={cx("oc-price-card__feature", f.excluded && "oc-price-card__feature--excluded")}>{f.label}</li>
77
+ ))}
78
+ </ul>
79
+ )}
80
+ {cta}
81
+ </article>
82
+ );
83
+ });