@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,53 +1,53 @@
1
- import { forwardRef, useId } from "react";
2
- import type { FormEvent, HTMLAttributes, ReactNode } from "react";
3
- import { cx } from "../utils";
4
-
5
- export interface BlockAuthProps extends Omit<HTMLAttributes<HTMLDivElement>, "title" | "onSubmit"> {
6
- title?: ReactNode;
7
- subtitle?: ReactNode;
8
- submitLabel?: ReactNode;
9
- /** Label of the secondary (social) button; omit to hide it together with the divider. */
10
- socialLabel?: ReactNode;
11
- dividerLabel?: ReactNode;
12
- /** Text under the card, e.g. `Don't have an account? <a href="#">Sign up</a>`. */
13
- footer?: ReactNode;
14
- onSubmit?: (values: { email: string; password: string }) => void;
15
- onSocial?: () => void;
16
- }
17
-
18
- /** `.oc-block-auth`: sign-in card with e-mail, password and an optional social button. */
19
- export const BlockAuth = forwardRef<HTMLDivElement, BlockAuthProps>(function BlockAuth(
20
- { title = "Sign in", subtitle, submitLabel = "Sign in", socialLabel, dividerLabel = "or continue with", footer, onSubmit, onSocial, className, ...rest },
21
- ref,
22
- ) {
23
- const id = useId().replace(/:/g, "");
24
- const submit = (e: FormEvent<HTMLFormElement>) => {
25
- e.preventDefault();
26
- const data = new FormData(e.currentTarget);
27
- onSubmit?.({ email: String(data.get("email") ?? ""), password: String(data.get("password") ?? "") });
28
- };
29
- return (
30
- <div ref={ref} className={cx("oc-block-auth", className)} {...rest}>
31
- <form className="oc-block-auth-card" onSubmit={submit}>
32
- <h2>{title}</h2>
33
- {subtitle != null && <p className="oc-block-subtitle">{subtitle}</p>}
34
- <div className="oc-form-group">
35
- <label className="oc-label" htmlFor={`${id}-email`}>Email</label>
36
- <input className="oc-input" id={`${id}-email`} name="email" type="email" placeholder="name@company.com" />
37
- </div>
38
- <div className="oc-form-group">
39
- <label className="oc-label" htmlFor={`${id}-password`}>Password</label>
40
- <input className="oc-input" id={`${id}-password`} name="password" type="password" placeholder="********" />
41
- </div>
42
- <button className="oc-btn" type="submit" style={{ width: "100%" }}>{submitLabel}</button>
43
- {socialLabel != null && (
44
- <>
45
- <div className="oc-block-auth-divider">{dividerLabel}</div>
46
- <button className="oc-btn oc-btn--outline" type="button" style={{ width: "100%" }} onClick={onSocial}>{socialLabel}</button>
47
- </>
48
- )}
49
- </form>
50
- {footer != null && <div className="oc-block-auth-footer">{footer}</div>}
51
- </div>
52
- );
53
- });
1
+ import { forwardRef, useId } from "react";
2
+ import type { FormEvent, HTMLAttributes, ReactNode } from "react";
3
+ import { cx } from "../utils";
4
+
5
+ export interface BlockAuthProps extends Omit<HTMLAttributes<HTMLDivElement>, "title" | "onSubmit"> {
6
+ title?: ReactNode;
7
+ subtitle?: ReactNode;
8
+ submitLabel?: ReactNode;
9
+ /** Label of the secondary (social) button; omit to hide it together with the divider. */
10
+ socialLabel?: ReactNode;
11
+ dividerLabel?: ReactNode;
12
+ /** Text under the card, e.g. `Don't have an account? <a href="#">Sign up</a>`. */
13
+ footer?: ReactNode;
14
+ onSubmit?: (values: { email: string; password: string }) => void;
15
+ onSocial?: () => void;
16
+ }
17
+
18
+ /** `.oc-block-auth`: sign-in card with e-mail, password and an optional social button. */
19
+ export const BlockAuth = forwardRef<HTMLDivElement, BlockAuthProps>(function BlockAuth(
20
+ { title = "Sign in", subtitle, submitLabel = "Sign in", socialLabel, dividerLabel = "or continue with", footer, onSubmit, onSocial, className, ...rest },
21
+ ref,
22
+ ) {
23
+ const id = useId().replace(/:/g, "");
24
+ const submit = (e: FormEvent<HTMLFormElement>) => {
25
+ e.preventDefault();
26
+ const data = new FormData(e.currentTarget);
27
+ onSubmit?.({ email: String(data.get("email") ?? ""), password: String(data.get("password") ?? "") });
28
+ };
29
+ return (
30
+ <div ref={ref} className={cx("oc-block-auth", className)} {...rest}>
31
+ <form className="oc-block-auth-card" onSubmit={submit}>
32
+ <h2>{title}</h2>
33
+ {subtitle != null && <p className="oc-block-subtitle">{subtitle}</p>}
34
+ <div className="oc-form-group">
35
+ <label className="oc-label" htmlFor={`${id}-email`}>Email</label>
36
+ <input className="oc-input" id={`${id}-email`} name="email" type="email" placeholder="name@company.com" />
37
+ </div>
38
+ <div className="oc-form-group">
39
+ <label className="oc-label" htmlFor={`${id}-password`}>Password</label>
40
+ <input className="oc-input" id={`${id}-password`} name="password" type="password" placeholder="********" />
41
+ </div>
42
+ <button className="oc-btn" type="submit" style={{ width: "100%" }}>{submitLabel}</button>
43
+ {socialLabel != null && (
44
+ <>
45
+ <div className="oc-block-auth-divider">{dividerLabel}</div>
46
+ <button className="oc-btn oc-btn--outline" type="button" style={{ width: "100%" }} onClick={onSocial}>{socialLabel}</button>
47
+ </>
48
+ )}
49
+ </form>
50
+ {footer != null && <div className="oc-block-auth-footer">{footer}</div>}
51
+ </div>
52
+ );
53
+ });
@@ -1,84 +1,84 @@
1
- import { forwardRef, useId } from "react";
2
- import type { FormEvent, HTMLAttributes, ReactNode } from "react";
3
- import { cx } from "../utils";
4
- import { PasswordInput } from "../components/PasswordInput";
5
-
6
- export interface BlockAuthSplitProps extends Omit<HTMLAttributes<HTMLDivElement>, "title" | "onSubmit"> {
7
- /** Brand link content (icon + name). */
8
- logo?: ReactNode;
9
- logoHref?: string;
10
- quote?: ReactNode;
11
- quoteAuthor?: ReactNode;
12
- title?: ReactNode;
13
- subtitle?: ReactNode;
14
- submitLabel?: ReactNode;
15
- forgotHref?: string;
16
- forgotLabel?: ReactNode;
17
- rememberLabel?: ReactNode;
18
- onSubmit?: (values: { email: string; password: string; remember: boolean }) => void;
19
- }
20
-
21
- /** `.oc-block-authsplit`: brand panel (logo, quote) next to the sign-in form. */
22
- export const BlockAuthSplit = forwardRef<HTMLDivElement, BlockAuthSplitProps>(function BlockAuthSplit(
23
- {
24
- logo,
25
- logoHref = "#",
26
- quote,
27
- quoteAuthor,
28
- title = "Welcome back",
29
- subtitle,
30
- submitLabel = "Sign in",
31
- forgotHref = "#",
32
- forgotLabel = "Forgot password?",
33
- rememberLabel = "Remember me",
34
- onSubmit,
35
- className,
36
- ...rest
37
- },
38
- ref,
39
- ) {
40
- const id = useId().replace(/:/g, "");
41
- const submit = (e: FormEvent<HTMLFormElement>) => {
42
- e.preventDefault();
43
- const data = new FormData(e.currentTarget);
44
- onSubmit?.({
45
- email: String(data.get("email") ?? ""),
46
- password: String(data.get("password") ?? ""),
47
- remember: data.get("remember") != null,
48
- });
49
- };
50
- return (
51
- <div ref={ref} className={cx("oc-block-authsplit", className)} {...rest}>
52
- <aside className="oc-block-authsplit-brand">
53
- {logo != null && <a className="oc-block-authsplit-logo" href={logoHref}>{logo}</a>}
54
- {quote != null && (
55
- <blockquote className="oc-block-authsplit-quote">
56
- <p>{quote}</p>
57
- {quoteAuthor != null && <footer>{quoteAuthor}</footer>}
58
- </blockquote>
59
- )}
60
- </aside>
61
- <div className="oc-block-authsplit-form">
62
- <form className="oc-block-authsplit-card" onSubmit={submit}>
63
- <h1 className="oc-block-authsplit-title">{title}</h1>
64
- {subtitle != null && <p className="oc-block-authsplit-subtitle">{subtitle}</p>}
65
- <div className="oc-block-authsplit-fields">
66
- <div className="oc-field">
67
- <label className="oc-field-label" htmlFor={`${id}-email`}>E-mail</label>
68
- <input className="oc-input" id={`${id}-email`} name="email" type="email" autoComplete="username" placeholder="name@company.com" required />
69
- </div>
70
- <div className="oc-field">
71
- <label className="oc-field-label" htmlFor={`${id}-password`}>Password</label>
72
- <PasswordInput id={`${id}-password`} name="password" required />
73
- </div>
74
- <div className="oc-block-authsplit-row">
75
- <label><input className="oc-checkbox" type="checkbox" name="remember" /> {rememberLabel}</label>
76
- <a className="oc-link" href={forgotHref}>{forgotLabel}</a>
77
- </div>
78
- <button className="oc-btn" type="submit" style={{ width: "100%" }}>{submitLabel}</button>
79
- </div>
80
- </form>
81
- </div>
82
- </div>
83
- );
84
- });
1
+ import { forwardRef, useId } from "react";
2
+ import type { FormEvent, HTMLAttributes, ReactNode } from "react";
3
+ import { cx } from "../utils";
4
+ import { PasswordInput } from "../components/PasswordInput";
5
+
6
+ export interface BlockAuthSplitProps extends Omit<HTMLAttributes<HTMLDivElement>, "title" | "onSubmit"> {
7
+ /** Brand link content (icon + name). */
8
+ logo?: ReactNode;
9
+ logoHref?: string;
10
+ quote?: ReactNode;
11
+ quoteAuthor?: ReactNode;
12
+ title?: ReactNode;
13
+ subtitle?: ReactNode;
14
+ submitLabel?: ReactNode;
15
+ forgotHref?: string;
16
+ forgotLabel?: ReactNode;
17
+ rememberLabel?: ReactNode;
18
+ onSubmit?: (values: { email: string; password: string; remember: boolean }) => void;
19
+ }
20
+
21
+ /** `.oc-block-authsplit`: brand panel (logo, quote) next to the sign-in form. */
22
+ export const BlockAuthSplit = forwardRef<HTMLDivElement, BlockAuthSplitProps>(function BlockAuthSplit(
23
+ {
24
+ logo,
25
+ logoHref = "#",
26
+ quote,
27
+ quoteAuthor,
28
+ title = "Welcome back",
29
+ subtitle,
30
+ submitLabel = "Sign in",
31
+ forgotHref = "#",
32
+ forgotLabel = "Forgot password?",
33
+ rememberLabel = "Remember me",
34
+ onSubmit,
35
+ className,
36
+ ...rest
37
+ },
38
+ ref,
39
+ ) {
40
+ const id = useId().replace(/:/g, "");
41
+ const submit = (e: FormEvent<HTMLFormElement>) => {
42
+ e.preventDefault();
43
+ const data = new FormData(e.currentTarget);
44
+ onSubmit?.({
45
+ email: String(data.get("email") ?? ""),
46
+ password: String(data.get("password") ?? ""),
47
+ remember: data.get("remember") != null,
48
+ });
49
+ };
50
+ return (
51
+ <div ref={ref} className={cx("oc-block-authsplit", className)} {...rest}>
52
+ <aside className="oc-block-authsplit-brand">
53
+ {logo != null && <a className="oc-block-authsplit-logo" href={logoHref}>{logo}</a>}
54
+ {quote != null && (
55
+ <blockquote className="oc-block-authsplit-quote">
56
+ <p>{quote}</p>
57
+ {quoteAuthor != null && <footer>{quoteAuthor}</footer>}
58
+ </blockquote>
59
+ )}
60
+ </aside>
61
+ <div className="oc-block-authsplit-form">
62
+ <form className="oc-block-authsplit-card" onSubmit={submit}>
63
+ <h1 className="oc-block-authsplit-title">{title}</h1>
64
+ {subtitle != null && <p className="oc-block-authsplit-subtitle">{subtitle}</p>}
65
+ <div className="oc-block-authsplit-fields">
66
+ <div className="oc-field">
67
+ <label className="oc-field-label" htmlFor={`${id}-email`}>E-mail</label>
68
+ <input className="oc-input" id={`${id}-email`} name="email" type="email" autoComplete="username" placeholder="name@company.com" required />
69
+ </div>
70
+ <div className="oc-field">
71
+ <label className="oc-field-label" htmlFor={`${id}-password`}>Password</label>
72
+ <PasswordInput id={`${id}-password`} name="password" required />
73
+ </div>
74
+ <div className="oc-block-authsplit-row">
75
+ <label><input className="oc-checkbox" type="checkbox" name="remember" /> {rememberLabel}</label>
76
+ <a className="oc-link" href={forgotHref}>{forgotLabel}</a>
77
+ </div>
78
+ <button className="oc-btn" type="submit" style={{ width: "100%" }}>{submitLabel}</button>
79
+ </div>
80
+ </form>
81
+ </div>
82
+ </div>
83
+ );
84
+ });
@@ -1,71 +1,71 @@
1
- // File: packages/react/src/blocks/BlockContactGrid.tsx | Map: types -> BlockContactGrid (head -> grid of cards (mark, title, lines))
2
- import { forwardRef, useId } from "react";
3
- import type { HTMLAttributes, ReactNode } from "react";
4
- import { cx } from "../utils";
5
-
6
- export interface ContactGridLine {
7
- text: ReactNode;
8
- /** Makes the line a link (mailto:, tel:, https:). */
9
- href?: string;
10
- }
11
-
12
- export interface ContactGridCard {
13
- title: ReactNode;
14
- lines: ContactGridLine[];
15
- /** Glyph in a soft product mark. */
16
- icon?: ReactNode;
17
- /** Accent preset of the mark (green, violet, ...). */
18
- accent?: string;
19
- /** Renders the card as <address>. */
20
- address?: boolean;
21
- }
22
-
23
- export interface BlockContactGridProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
24
- eyebrow?: ReactNode;
25
- title?: ReactNode;
26
- lead?: ReactNode;
27
- cards: ContactGridCard[];
28
- /** No card frames. */
29
- plain?: boolean;
30
- }
31
-
32
- /** `.oc-block-contact`: contact info cards (e-mail, phone, address, hours). */
33
- export const BlockContactGrid = forwardRef<HTMLElement, BlockContactGridProps>(function BlockContactGrid(
34
- { eyebrow, title, lead, cards, plain = false, className, ...rest },
35
- ref,
36
- ) {
37
- const titleId = `oc-contact-title-${useId().replace(/[^\w-]/g, "")}`;
38
- return (
39
- <section ref={ref} className={cx("oc-block-contact", plain && "oc-block-contact--plain", className)} aria-labelledby={title != null ? titleId : undefined} {...rest}>
40
- {(eyebrow != null || title != null || lead != null) && (
41
- <div className="oc-block-contact-head">
42
- {eyebrow != null && <p className="oc-eyebrow">{eyebrow}</p>}
43
- {title != null && <h2 className="oc-block-contact-heading" id={titleId}>{title}</h2>}
44
- {lead != null && <p className="oc-block-contact-lead">{lead}</p>}
45
- </div>
46
- )}
47
- <div className="oc-block-contact-grid">
48
- {cards.map((card, i) => {
49
- const Card = card.address ? "address" : "article";
50
- const Title = card.address ? "span" : "h3";
51
- const Line = card.address ? "span" : "p";
52
- return (
53
- <Card className="oc-block-contact-card" key={i}>
54
- {card.icon != null && (
55
- <span className={cx("oc-product-mark oc-product-mark--sm oc-product-mark--soft", card.accent && `oc-accent--${card.accent}`)} aria-hidden="true">{card.icon}</span>
56
- )}
57
- <Title className="oc-block-contact-title">{card.title}</Title>
58
- {card.lines.map((line, j) =>
59
- line.href ? (
60
- <a className="oc-block-contact-line" href={line.href} key={j}>{line.text}</a>
61
- ) : (
62
- <Line className="oc-block-contact-line" key={j}>{line.text}</Line>
63
- ),
64
- )}
65
- </Card>
66
- );
67
- })}
68
- </div>
69
- </section>
70
- );
71
- });
1
+ // File: packages/react/src/blocks/BlockContactGrid.tsx | Map: types -> BlockContactGrid (head -> grid of cards (mark, title, lines))
2
+ import { forwardRef, useId } from "react";
3
+ import type { HTMLAttributes, ReactNode } from "react";
4
+ import { cx } from "../utils";
5
+
6
+ export interface ContactGridLine {
7
+ text: ReactNode;
8
+ /** Makes the line a link (mailto:, tel:, https:). */
9
+ href?: string;
10
+ }
11
+
12
+ export interface ContactGridCard {
13
+ title: ReactNode;
14
+ lines: ContactGridLine[];
15
+ /** Glyph in a soft product mark. */
16
+ icon?: ReactNode;
17
+ /** Accent preset of the mark (green, violet, ...). */
18
+ accent?: string;
19
+ /** Renders the card as <address>. */
20
+ address?: boolean;
21
+ }
22
+
23
+ export interface BlockContactGridProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
24
+ eyebrow?: ReactNode;
25
+ title?: ReactNode;
26
+ lead?: ReactNode;
27
+ cards: ContactGridCard[];
28
+ /** No card frames. */
29
+ plain?: boolean;
30
+ }
31
+
32
+ /** `.oc-block-contact`: contact info cards (e-mail, phone, address, hours). */
33
+ export const BlockContactGrid = forwardRef<HTMLElement, BlockContactGridProps>(function BlockContactGrid(
34
+ { eyebrow, title, lead, cards, plain = false, className, ...rest },
35
+ ref,
36
+ ) {
37
+ const titleId = `oc-contact-title-${useId().replace(/[^\w-]/g, "")}`;
38
+ return (
39
+ <section ref={ref} className={cx("oc-block-contact", plain && "oc-block-contact--plain", className)} aria-labelledby={title != null ? titleId : undefined} {...rest}>
40
+ {(eyebrow != null || title != null || lead != null) && (
41
+ <div className="oc-block-contact-head">
42
+ {eyebrow != null && <p className="oc-eyebrow">{eyebrow}</p>}
43
+ {title != null && <h2 className="oc-block-contact-heading" id={titleId}>{title}</h2>}
44
+ {lead != null && <p className="oc-block-contact-lead">{lead}</p>}
45
+ </div>
46
+ )}
47
+ <div className="oc-block-contact-grid">
48
+ {cards.map((card, i) => {
49
+ const Card = card.address ? "address" : "article";
50
+ const Title = card.address ? "span" : "h3";
51
+ const Line = card.address ? "span" : "p";
52
+ return (
53
+ <Card className="oc-block-contact-card" key={i}>
54
+ {card.icon != null && (
55
+ <span className={cx("oc-product-mark oc-product-mark--sm oc-product-mark--soft", card.accent && `oc-accent--${card.accent}`)} aria-hidden="true">{card.icon}</span>
56
+ )}
57
+ <Title className="oc-block-contact-title">{card.title}</Title>
58
+ {card.lines.map((line, j) =>
59
+ line.href ? (
60
+ <a className="oc-block-contact-line" href={line.href} key={j}>{line.text}</a>
61
+ ) : (
62
+ <Line className="oc-block-contact-line" key={j}>{line.text}</Line>
63
+ ),
64
+ )}
65
+ </Card>
66
+ );
67
+ })}
68
+ </div>
69
+ </section>
70
+ );
71
+ });
@@ -1,23 +1,23 @@
1
- import { forwardRef } from "react";
2
- import type { HTMLAttributes, ReactNode } from "react";
3
- import { cx } from "../utils";
4
-
5
- export interface BlockCtaProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
6
- title: ReactNode;
7
- description?: ReactNode;
8
- actions?: ReactNode;
9
- }
10
-
11
- /** `.oc-block-cta`: call to action banner. */
12
- export const BlockCta = forwardRef<HTMLDivElement, BlockCtaProps>(function BlockCta(
13
- { title, description, actions, className, ...rest },
14
- ref,
15
- ) {
16
- return (
17
- <div ref={ref} className={cx("oc-block-cta", className)} {...rest}>
18
- <h2>{title}</h2>
19
- {description != null && <p>{description}</p>}
20
- {actions != null && <div className="oc-block-hero-actions">{actions}</div>}
21
- </div>
22
- );
23
- });
1
+ import { forwardRef } from "react";
2
+ import type { HTMLAttributes, ReactNode } from "react";
3
+ import { cx } from "../utils";
4
+
5
+ export interface BlockCtaProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
6
+ title: ReactNode;
7
+ description?: ReactNode;
8
+ actions?: ReactNode;
9
+ }
10
+
11
+ /** `.oc-block-cta`: call to action banner. */
12
+ export const BlockCta = forwardRef<HTMLDivElement, BlockCtaProps>(function BlockCta(
13
+ { title, description, actions, className, ...rest },
14
+ ref,
15
+ ) {
16
+ return (
17
+ <div ref={ref} className={cx("oc-block-cta", className)} {...rest}>
18
+ <h2>{title}</h2>
19
+ {description != null && <p>{description}</p>}
20
+ {actions != null && <div className="oc-block-hero-actions">{actions}</div>}
21
+ </div>
22
+ );
23
+ });
@@ -1,57 +1,57 @@
1
- import { forwardRef } from "react";
2
- import type { AnchorHTMLAttributes, HTMLAttributes, ReactNode } from "react";
3
- import { cx } from "../utils";
4
-
5
- export interface BlockDataTableProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
6
- title?: ReactNode;
7
- description?: ReactNode;
8
- /** Header buttons (export, new...). */
9
- actions?: ReactNode;
10
- /** Search + filters row (`oc-table-toolbar`). Use `BlockDataTableSpacer` to push items right. */
11
- toolbar?: ReactNode;
12
- /** Selection bar, shown by you when rows are selected (`oc-block-datatable-bulk`). */
13
- bulk?: ReactNode;
14
- /** Entries text, page-size select and pagination (`oc-table-footer`). */
15
- footer?: ReactNode;
16
- /** The `<Table>`. */
17
- children?: ReactNode;
18
- }
19
-
20
- /** `.oc-card.oc-block-datatable`: card with header, toolbar, table, bulk bar and footer. */
21
- export const BlockDataTable = forwardRef<HTMLElement, BlockDataTableProps>(function BlockDataTable(
22
- { title, description, actions, toolbar, bulk, footer, className, children, ...rest },
23
- ref,
24
- ) {
25
- return (
26
- <section ref={ref} className={cx("oc-card oc-block-datatable", className)} {...rest}>
27
- <header className="oc-block-datatable-head">
28
- <div>
29
- {title != null && <h2 className="oc-block-datatable-title">{title}</h2>}
30
- {description != null && <p className="oc-block-datatable-description">{description}</p>}
31
- </div>
32
- {actions != null && <div>{actions}</div>}
33
- </header>
34
- <div className="oc-block-datatable-body">
35
- {toolbar != null && <div className="oc-table-toolbar">{toolbar}</div>}
36
- {bulk != null && <div className="oc-block-datatable-bulk">{bulk}</div>}
37
- {children}
38
- {footer != null && <div className="oc-table-footer">{footer}</div>}
39
- </div>
40
- </section>
41
- );
42
- });
43
-
44
- /** `.oc-table-toolbar-spacer`: pushes the following toolbar items to the right. */
45
- export function BlockDataTableSpacer() {
46
- return <span className="oc-table-toolbar-spacer" />;
47
- }
48
-
49
- /** `.oc-filter-pills` group. */
50
- export function FilterPills({ className, ...rest }: HTMLAttributes<HTMLDivElement>) {
51
- return <div className={cx("oc-filter-pills", className)} role="group" {...rest} />;
52
- }
53
-
54
- /** `a.oc-filter-pill` (set `current` for the active filter). */
55
- export function FilterPill({ current, className, ...rest }: AnchorHTMLAttributes<HTMLAnchorElement> & { current?: boolean }) {
56
- return <a className={cx("oc-filter-pill", className)} aria-current={current ? "true" : undefined} {...rest} />;
57
- }
1
+ import { forwardRef } from "react";
2
+ import type { AnchorHTMLAttributes, HTMLAttributes, ReactNode } from "react";
3
+ import { cx } from "../utils";
4
+
5
+ export interface BlockDataTableProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
6
+ title?: ReactNode;
7
+ description?: ReactNode;
8
+ /** Header buttons (export, new...). */
9
+ actions?: ReactNode;
10
+ /** Search + filters row (`oc-table-toolbar`). Use `BlockDataTableSpacer` to push items right. */
11
+ toolbar?: ReactNode;
12
+ /** Selection bar, shown by you when rows are selected (`oc-block-datatable-bulk`). */
13
+ bulk?: ReactNode;
14
+ /** Entries text, page-size select and pagination (`oc-table-footer`). */
15
+ footer?: ReactNode;
16
+ /** The `<Table>`. */
17
+ children?: ReactNode;
18
+ }
19
+
20
+ /** `.oc-card.oc-block-datatable`: card with header, toolbar, table, bulk bar and footer. */
21
+ export const BlockDataTable = forwardRef<HTMLElement, BlockDataTableProps>(function BlockDataTable(
22
+ { title, description, actions, toolbar, bulk, footer, className, children, ...rest },
23
+ ref,
24
+ ) {
25
+ return (
26
+ <section ref={ref} className={cx("oc-card oc-block-datatable", className)} {...rest}>
27
+ <header className="oc-block-datatable-head">
28
+ <div>
29
+ {title != null && <h2 className="oc-block-datatable-title">{title}</h2>}
30
+ {description != null && <p className="oc-block-datatable-description">{description}</p>}
31
+ </div>
32
+ {actions != null && <div>{actions}</div>}
33
+ </header>
34
+ <div className="oc-block-datatable-body">
35
+ {toolbar != null && <div className="oc-table-toolbar">{toolbar}</div>}
36
+ {bulk != null && <div className="oc-block-datatable-bulk">{bulk}</div>}
37
+ {children}
38
+ {footer != null && <div className="oc-table-footer">{footer}</div>}
39
+ </div>
40
+ </section>
41
+ );
42
+ });
43
+
44
+ /** `.oc-table-toolbar-spacer`: pushes the following toolbar items to the right. */
45
+ export function BlockDataTableSpacer() {
46
+ return <span className="oc-table-toolbar-spacer" />;
47
+ }
48
+
49
+ /** `.oc-filter-pills` group. */
50
+ export function FilterPills({ className, ...rest }: HTMLAttributes<HTMLDivElement>) {
51
+ return <div className={cx("oc-filter-pills", className)} role="group" {...rest} />;
52
+ }
53
+
54
+ /** `a.oc-filter-pill` (set `current` for the active filter). */
55
+ export function FilterPill({ current, className, ...rest }: AnchorHTMLAttributes<HTMLAnchorElement> & { current?: boolean }) {
56
+ return <a className={cx("oc-filter-pill", className)} aria-current={current ? "true" : undefined} {...rest} />;
57
+ }