@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,50 +1,50 @@
1
- import { forwardRef } from "react";
2
- import type { HTMLAttributes, ReactNode } from "react";
3
- import { cx } from "../utils";
4
-
5
- export interface ProcessStep {
6
- title: ReactNode;
7
- text?: ReactNode;
8
- }
9
-
10
- export interface BlockProcessProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
11
- eyebrow?: ReactNode;
12
- /** Display title; wrap a word in `<span className="oc-text-gradient">`. */
13
- title: ReactNode;
14
- steps: ProcessStep[];
15
- /** Items of the endless marquee (strings). The list is repeated once for the seamless loop. */
16
- marquee?: string[];
17
- marqueeLabel?: string;
18
- }
19
-
20
- /** Numbered steps (CSS counter) plus an optional marquee of technologies. */
21
- export const BlockProcess = forwardRef<HTMLElement, BlockProcessProps>(function BlockProcess(
22
- { eyebrow, title, steps, marquee, marqueeLabel = "Technologies", className, ...rest },
23
- ref,
24
- ) {
25
- return (
26
- <section ref={ref} className={cx("oc-section-pad", className)} {...rest}>
27
- <div className="oc-container">
28
- {eyebrow != null && <p className="oc-eyebrow">{eyebrow}</p>}
29
- <h2 className="oc-display oc-display--md">{title}</h2>
30
- <ol className="oc-steps">
31
- {steps.map((s, i) => (
32
- <li key={i}>
33
- <h3>{s.title}</h3>
34
- {s.text != null && <p>{s.text}</p>}
35
- </li>
36
- ))}
37
- </ol>
38
- {marquee && marquee.length > 0 && (
39
- <div className="oc-marquee mt-4" aria-label={marqueeLabel}>
40
- <div className="oc-marquee__track">
41
- {marquee.map((m) => <span className="oc-marquee__item" key={m}>{m}</span>)}
42
- {marquee.map((m) => <span className="oc-marquee__item" aria-hidden="true" key={`dup-${m}`}>{m}</span>)}
43
- </div>
44
- </div>
45
- )}
46
- </div>
47
- </section>
48
- );
49
- });
50
-
1
+ import { forwardRef } from "react";
2
+ import type { HTMLAttributes, ReactNode } from "react";
3
+ import { cx } from "../utils";
4
+
5
+ export interface ProcessStep {
6
+ title: ReactNode;
7
+ text?: ReactNode;
8
+ }
9
+
10
+ export interface BlockProcessProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
11
+ eyebrow?: ReactNode;
12
+ /** Display title; wrap a word in `<span className="oc-text-gradient">`. */
13
+ title: ReactNode;
14
+ steps: ProcessStep[];
15
+ /** Items of the endless marquee (strings). The list is repeated once for the seamless loop. */
16
+ marquee?: string[];
17
+ marqueeLabel?: string;
18
+ }
19
+
20
+ /** Numbered steps (CSS counter) plus an optional marquee of technologies. */
21
+ export const BlockProcess = forwardRef<HTMLElement, BlockProcessProps>(function BlockProcess(
22
+ { eyebrow, title, steps, marquee, marqueeLabel = "Technologies", className, ...rest },
23
+ ref,
24
+ ) {
25
+ return (
26
+ <section ref={ref} className={cx("oc-section-pad", className)} {...rest}>
27
+ <div className="oc-container">
28
+ {eyebrow != null && <p className="oc-eyebrow">{eyebrow}</p>}
29
+ <h2 className="oc-display oc-display--md">{title}</h2>
30
+ <ol className="oc-steps">
31
+ {steps.map((s, i) => (
32
+ <li key={i}>
33
+ <h3>{s.title}</h3>
34
+ {s.text != null && <p>{s.text}</p>}
35
+ </li>
36
+ ))}
37
+ </ol>
38
+ {marquee && marquee.length > 0 && (
39
+ <div className="oc-marquee mt-4" aria-label={marqueeLabel}>
40
+ <div className="oc-marquee__track">
41
+ {marquee.map((m) => <span className="oc-marquee__item" key={m}>{m}</span>)}
42
+ {marquee.map((m) => <span className="oc-marquee__item" aria-hidden="true" key={`dup-${m}`}>{m}</span>)}
43
+ </div>
44
+ </div>
45
+ )}
46
+ </div>
47
+ </section>
48
+ );
49
+ });
50
+
@@ -1,54 +1,54 @@
1
- import { forwardRef } from "react";
2
- import type { HTMLAttributes, ReactNode } from "react";
3
- import { cx } from "../utils";
4
-
5
- export interface Product {
6
- href: string;
7
- /** Accent name (`green`, `orange`, `violet`, `cyan`, ...), applied as `oc-accent--{accent}`. */
8
- accent?: string;
9
- /** Glyph or inline SVG in the icon tile. */
10
- icon?: ReactNode;
11
- /** Pill label (`Own product`, `Tool`...). */
12
- pill?: ReactNode;
13
- name: ReactNode;
14
- tagline?: ReactNode;
15
- summary?: ReactNode;
16
- /** "Discover ..." link text. */
17
- more?: ReactNode;
18
- }
19
-
20
- export interface BlockProductGridProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
21
- eyebrow?: ReactNode;
22
- /** Display title; wrap a word in `<span className="oc-text-gradient">`. */
23
- title: ReactNode;
24
- products: Product[];
25
- }
26
-
27
- /** Section title plus a grid of accent-colored product cards. */
28
- export const BlockProductGrid = forwardRef<HTMLElement, BlockProductGridProps>(function BlockProductGrid(
29
- { eyebrow, title, products, className, ...rest },
30
- ref,
31
- ) {
32
- return (
33
- <section ref={ref} className={cx("oc-section-pad", className)} {...rest}>
34
- <div className="oc-container">
35
- {eyebrow != null && <p className="oc-eyebrow">{eyebrow}</p>}
36
- <h2 className="oc-display oc-display--md">{title}</h2>
37
- <div className="oc-card-grid">
38
- {products.map((p, i) => (
39
- <a className={cx("oc-product-card", p.accent && `oc-accent--${p.accent}`)} href={p.href} key={i}>
40
- <div className="oc-product-card__top">
41
- {p.icon != null && <span className="oc-icon-tile" aria-hidden="true">{p.icon}</span>}
42
- {p.pill != null && <span className="oc-pill">{p.pill}</span>}
43
- </div>
44
- <h3 className="oc-product-card__name">{p.name}</h3>
45
- {p.tagline != null && <p className="oc-product-card__tagline">{p.tagline}</p>}
46
- {p.summary != null && <p className="oc-product-card__summary">{p.summary}</p>}
47
- {p.more != null && <span className="oc-product-card__more">{p.more}</span>}
48
- </a>
49
- ))}
50
- </div>
51
- </div>
52
- </section>
53
- );
54
- });
1
+ import { forwardRef } from "react";
2
+ import type { HTMLAttributes, ReactNode } from "react";
3
+ import { cx } from "../utils";
4
+
5
+ export interface Product {
6
+ href: string;
7
+ /** Accent name (`green`, `orange`, `violet`, `cyan`, ...), applied as `oc-accent--{accent}`. */
8
+ accent?: string;
9
+ /** Glyph or inline SVG in the icon tile. */
10
+ icon?: ReactNode;
11
+ /** Pill label (`Own product`, `Tool`...). */
12
+ pill?: ReactNode;
13
+ name: ReactNode;
14
+ tagline?: ReactNode;
15
+ summary?: ReactNode;
16
+ /** "Discover ..." link text. */
17
+ more?: ReactNode;
18
+ }
19
+
20
+ export interface BlockProductGridProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
21
+ eyebrow?: ReactNode;
22
+ /** Display title; wrap a word in `<span className="oc-text-gradient">`. */
23
+ title: ReactNode;
24
+ products: Product[];
25
+ }
26
+
27
+ /** Section title plus a grid of accent-colored product cards. */
28
+ export const BlockProductGrid = forwardRef<HTMLElement, BlockProductGridProps>(function BlockProductGrid(
29
+ { eyebrow, title, products, className, ...rest },
30
+ ref,
31
+ ) {
32
+ return (
33
+ <section ref={ref} className={cx("oc-section-pad", className)} {...rest}>
34
+ <div className="oc-container">
35
+ {eyebrow != null && <p className="oc-eyebrow">{eyebrow}</p>}
36
+ <h2 className="oc-display oc-display--md">{title}</h2>
37
+ <div className="oc-card-grid">
38
+ {products.map((p, i) => (
39
+ <a className={cx("oc-product-card", p.accent && `oc-accent--${p.accent}`)} href={p.href} key={i}>
40
+ <div className="oc-product-card__top">
41
+ {p.icon != null && <span className="oc-icon-tile" aria-hidden="true">{p.icon}</span>}
42
+ {p.pill != null && <span className="oc-pill">{p.pill}</span>}
43
+ </div>
44
+ <h3 className="oc-product-card__name">{p.name}</h3>
45
+ {p.tagline != null && <p className="oc-product-card__tagline">{p.tagline}</p>}
46
+ {p.summary != null && <p className="oc-product-card__summary">{p.summary}</p>}
47
+ {p.more != null && <span className="oc-product-card__more">{p.more}</span>}
48
+ </a>
49
+ ))}
50
+ </div>
51
+ </div>
52
+ </section>
53
+ );
54
+ });
@@ -1,57 +1,57 @@
1
- // File: packages/react/src/blocks/BlockProfileHeader.tsx | Map: types -> BlockProfileHeader (banner -> bar (avatar, info, actions) -> tabs)
2
- import { forwardRef, useId } from "react";
3
- import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
4
- import { cx } from "../utils";
5
-
6
- export interface ProfileHeaderTab {
7
- label: ReactNode;
8
- href: string;
9
- current?: boolean;
10
- }
11
-
12
- export interface BlockProfileHeaderProps extends HTMLAttributes<HTMLElement> {
13
- name: ReactNode;
14
- meta?: ReactNode;
15
- bio?: ReactNode;
16
- /** Initials, or an <img> for the avatar. */
17
- avatar: ReactNode;
18
- /** Banner image URL; without it the banner is an accent gradient. */
19
- banner?: string;
20
- actions?: ReactNode;
21
- tabs?: ProfileHeaderTab[];
22
- tabsLabel?: string;
23
- compact?: boolean;
24
- /** Heading level of the name (h1 on a profile page, h2 inside a page). */
25
- headingLevel?: 1 | 2;
26
- }
27
-
28
- /** `.oc-block-profile`: banner, overhanging avatar, name, meta, actions and link tabs. */
29
- export const BlockProfileHeader = forwardRef<HTMLElement, BlockProfileHeaderProps>(function BlockProfileHeader(
30
- { name, meta, bio, avatar, banner, actions, tabs = [], tabsLabel = "Profile sections", compact = false, headingLevel = 1, className, ...rest },
31
- ref,
32
- ) {
33
- const nameId = `oc-profile-name-${useId().replace(/[^\w-]/g, "")}`;
34
- const Heading = headingLevel === 1 ? "h1" : "h2";
35
- const bannerStyle = banner ? ({ "--oc-profile-banner": `url("${banner}")` } as CSSProperties) : undefined;
36
- return (
37
- <section ref={ref} className={cx("oc-block-profile", compact && "oc-block-profile--compact", className)} aria-labelledby={nameId} {...rest}>
38
- <div className="oc-block-profile-banner" style={bannerStyle} aria-hidden="true" />
39
- <div className="oc-block-profile-bar">
40
- <span className="oc-avatar oc-block-profile-avatar" aria-hidden="true">{avatar}</span>
41
- <div className="oc-block-profile-info">
42
- <Heading className="oc-block-profile-name" id={nameId}>{name}</Heading>
43
- {meta != null && <p className="oc-block-profile-meta">{meta}</p>}
44
- {bio != null && <p className="oc-block-profile-bio">{bio}</p>}
45
- </div>
46
- {actions != null && <div className="oc-block-profile-actions">{actions}</div>}
47
- </div>
48
- {tabs.length > 0 && (
49
- <nav className="oc-block-profile-tabs" aria-label={tabsLabel}>
50
- {tabs.map((tab, i) => (
51
- <a key={i} className="oc-block-profile-tab" href={tab.href} aria-current={tab.current ? "page" : undefined}>{tab.label}</a>
52
- ))}
53
- </nav>
54
- )}
55
- </section>
56
- );
57
- });
1
+ // File: packages/react/src/blocks/BlockProfileHeader.tsx | Map: types -> BlockProfileHeader (banner -> bar (avatar, info, actions) -> tabs)
2
+ import { forwardRef, useId } from "react";
3
+ import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
4
+ import { cx } from "../utils";
5
+
6
+ export interface ProfileHeaderTab {
7
+ label: ReactNode;
8
+ href: string;
9
+ current?: boolean;
10
+ }
11
+
12
+ export interface BlockProfileHeaderProps extends HTMLAttributes<HTMLElement> {
13
+ name: ReactNode;
14
+ meta?: ReactNode;
15
+ bio?: ReactNode;
16
+ /** Initials, or an <img> for the avatar. */
17
+ avatar: ReactNode;
18
+ /** Banner image URL; without it the banner is an accent gradient. */
19
+ banner?: string;
20
+ actions?: ReactNode;
21
+ tabs?: ProfileHeaderTab[];
22
+ tabsLabel?: string;
23
+ compact?: boolean;
24
+ /** Heading level of the name (h1 on a profile page, h2 inside a page). */
25
+ headingLevel?: 1 | 2;
26
+ }
27
+
28
+ /** `.oc-block-profile`: banner, overhanging avatar, name, meta, actions and link tabs. */
29
+ export const BlockProfileHeader = forwardRef<HTMLElement, BlockProfileHeaderProps>(function BlockProfileHeader(
30
+ { name, meta, bio, avatar, banner, actions, tabs = [], tabsLabel = "Profile sections", compact = false, headingLevel = 1, className, ...rest },
31
+ ref,
32
+ ) {
33
+ const nameId = `oc-profile-name-${useId().replace(/[^\w-]/g, "")}`;
34
+ const Heading = headingLevel === 1 ? "h1" : "h2";
35
+ const bannerStyle = banner ? ({ "--oc-profile-banner": `url("${banner}")` } as CSSProperties) : undefined;
36
+ return (
37
+ <section ref={ref} className={cx("oc-block-profile", compact && "oc-block-profile--compact", className)} aria-labelledby={nameId} {...rest}>
38
+ <div className="oc-block-profile-banner" style={bannerStyle} aria-hidden="true" />
39
+ <div className="oc-block-profile-bar">
40
+ <span className="oc-avatar oc-block-profile-avatar" aria-hidden="true">{avatar}</span>
41
+ <div className="oc-block-profile-info">
42
+ <Heading className="oc-block-profile-name" id={nameId}>{name}</Heading>
43
+ {meta != null && <p className="oc-block-profile-meta">{meta}</p>}
44
+ {bio != null && <p className="oc-block-profile-bio">{bio}</p>}
45
+ </div>
46
+ {actions != null && <div className="oc-block-profile-actions">{actions}</div>}
47
+ </div>
48
+ {tabs.length > 0 && (
49
+ <nav className="oc-block-profile-tabs" aria-label={tabsLabel}>
50
+ {tabs.map((tab, i) => (
51
+ <a key={i} className="oc-block-profile-tab" href={tab.href} aria-current={tab.current ? "page" : undefined}>{tab.label}</a>
52
+ ))}
53
+ </nav>
54
+ )}
55
+ </section>
56
+ );
57
+ });
@@ -1,58 +1,58 @@
1
- import { forwardRef } from "react";
2
- import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
3
- import { cx } from "../utils";
4
-
5
- export interface ShowcaseProject {
6
- href: string;
7
- accent?: string;
8
- /** CSS `background` of the browser frame body (e.g. a gradient of palette variables) or an image URL via `image`. */
9
- background?: string;
10
- image?: string;
11
- imageAlt?: string;
12
- pill?: ReactNode;
13
- name: ReactNode;
14
- summary?: ReactNode;
15
- more?: ReactNode;
16
- }
17
-
18
- export interface BlockShowcaseProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
19
- eyebrow?: ReactNode;
20
- /** Display title; wrap a word in `<span className="oc-text-gradient">`. */
21
- title: ReactNode;
22
- projects: ShowcaseProject[];
23
- /** Minimum card width (`--oc-card-min`, default 26rem). */
24
- cardMin?: string;
25
- }
26
-
27
- /** Portfolio grid: project cards with a browser frame around the screenshot. */
28
- export const BlockShowcase = forwardRef<HTMLElement, BlockShowcaseProps>(function BlockShowcase(
29
- { eyebrow, title, projects, cardMin = "26rem", className, ...rest },
30
- ref,
31
- ) {
32
- return (
33
- <section ref={ref} className={cx("oc-section-pad", className)} {...rest}>
34
- <div className="oc-container">
35
- {eyebrow != null && <p className="oc-eyebrow">{eyebrow}</p>}
36
- <h2 className="oc-display oc-display--md">{title}</h2>
37
- <div className="oc-card-grid" style={{ "--oc-card-min": cardMin } as CSSProperties}>
38
- {projects.map((p, i) => (
39
- <a className={cx("oc-product-card", p.accent && `oc-accent--${p.accent}`, "p-0 overflow-hidden")} href={p.href} key={i}>
40
- <span className="oc-browser-frame m-1">
41
- <span className="oc-browser-frame__bar"><i /><i /><i /></span>
42
- <span className="oc-browser-frame__body" style={p.background ? { background: p.background } : undefined}>
43
- {p.image && <img src={p.image} alt={p.imageAlt ?? ""} />}
44
- </span>
45
- </span>
46
- <span className="grid gap-0.5 p-1.5 pt-0">
47
- {p.pill != null && <span className="oc-pill oc-pill--accent">{p.pill}</span>}
48
- <span className="oc-product-card__name">{p.name}</span>
49
- {p.summary != null && <span className="oc-product-card__summary">{p.summary}</span>}
50
- {p.more != null && <span className="oc-product-card__more">{p.more}</span>}
51
- </span>
52
- </a>
53
- ))}
54
- </div>
55
- </div>
56
- </section>
57
- );
58
- });
1
+ import { forwardRef } from "react";
2
+ import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
3
+ import { cx } from "../utils";
4
+
5
+ export interface ShowcaseProject {
6
+ href: string;
7
+ accent?: string;
8
+ /** CSS `background` of the browser frame body (e.g. a gradient of palette variables) or an image URL via `image`. */
9
+ background?: string;
10
+ image?: string;
11
+ imageAlt?: string;
12
+ pill?: ReactNode;
13
+ name: ReactNode;
14
+ summary?: ReactNode;
15
+ more?: ReactNode;
16
+ }
17
+
18
+ export interface BlockShowcaseProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
19
+ eyebrow?: ReactNode;
20
+ /** Display title; wrap a word in `<span className="oc-text-gradient">`. */
21
+ title: ReactNode;
22
+ projects: ShowcaseProject[];
23
+ /** Minimum card width (`--oc-card-min`, default 26rem). */
24
+ cardMin?: string;
25
+ }
26
+
27
+ /** Portfolio grid: project cards with a browser frame around the screenshot. */
28
+ export const BlockShowcase = forwardRef<HTMLElement, BlockShowcaseProps>(function BlockShowcase(
29
+ { eyebrow, title, projects, cardMin = "26rem", className, ...rest },
30
+ ref,
31
+ ) {
32
+ return (
33
+ <section ref={ref} className={cx("oc-section-pad", className)} {...rest}>
34
+ <div className="oc-container">
35
+ {eyebrow != null && <p className="oc-eyebrow">{eyebrow}</p>}
36
+ <h2 className="oc-display oc-display--md">{title}</h2>
37
+ <div className="oc-card-grid" style={{ "--oc-card-min": cardMin } as CSSProperties}>
38
+ {projects.map((p, i) => (
39
+ <a className={cx("oc-product-card", p.accent && `oc-accent--${p.accent}`, "p-0 overflow-hidden")} href={p.href} key={i}>
40
+ <span className="oc-browser-frame m-1">
41
+ <span className="oc-browser-frame__bar"><i /><i /><i /></span>
42
+ <span className="oc-browser-frame__body" style={p.background ? { background: p.background } : undefined}>
43
+ {p.image && <img src={p.image} alt={p.imageAlt ?? ""} />}
44
+ </span>
45
+ </span>
46
+ <span className="grid gap-0.5 p-1.5 pt-0">
47
+ {p.pill != null && <span className="oc-pill oc-pill--accent">{p.pill}</span>}
48
+ <span className="oc-product-card__name">{p.name}</span>
49
+ {p.summary != null && <span className="oc-product-card__summary">{p.summary}</span>}
50
+ {p.more != null && <span className="oc-product-card__more">{p.more}</span>}
51
+ </span>
52
+ </a>
53
+ ))}
54
+ </div>
55
+ </div>
56
+ </section>
57
+ );
58
+ });
@@ -1,60 +1,60 @@
1
- import { forwardRef } from "react";
2
- import type { HTMLAttributes, ReactNode } from "react";
3
- import { cx } from "../utils";
4
- import type { FooterLink } from "./BlockFooter";
5
-
6
- export interface SiteFooterColumn {
7
- title: ReactNode;
8
- /** Link list column. */
9
- links?: FooterLink[];
10
- /** Address column: children are rendered in an `<address class="oc-site-footer__list">`. */
11
- address?: ReactNode;
12
- }
13
-
14
- export interface BlockSiteFooterProps extends Omit<HTMLAttributes<HTMLElement>, "about"> {
15
- /** Brand link content, e.g. icon tile + name (rendered in `oc-site-header__brand`). */
16
- brand: ReactNode;
17
- brandHref?: string;
18
- about?: ReactNode;
19
- columns: SiteFooterColumn[];
20
- copyright?: ReactNode;
21
- credit?: ReactNode;
22
- }
23
-
24
- /** `.oc-site-footer`: brand column, link columns and a bottom bar. */
25
- export const BlockSiteFooter = forwardRef<HTMLElement, BlockSiteFooterProps>(function BlockSiteFooter(
26
- { brand, brandHref = "/", about, columns, copyright, credit, className, ...rest },
27
- ref,
28
- ) {
29
- return (
30
- <footer ref={ref} className={cx("oc-site-footer", className)} {...rest}>
31
- <div className="oc-container">
32
- <div className="oc-site-footer__grid">
33
- <div className="oc-site-footer__about">
34
- <a className="oc-site-header__brand" href={brandHref}>{brand}</a>
35
- {about != null && <p>{about}</p>}
36
- </div>
37
- {columns.map((col, i) => (
38
- <div key={i}>
39
- <h2 className="oc-site-footer__title">{col.title}</h2>
40
- {col.links && (
41
- <ul className="oc-site-footer__list">
42
- {col.links.map((l, j) => (
43
- <li key={j}><a href={l.href}>{l.label}</a></li>
44
- ))}
45
- </ul>
46
- )}
47
- {col.address != null && <address className="oc-site-footer__list">{col.address}</address>}
48
- </div>
49
- ))}
50
- </div>
51
- {(copyright != null || credit != null) && (
52
- <div className="oc-site-footer__bottom">
53
- <span>{copyright}</span>
54
- <span>{credit}</span>
55
- </div>
56
- )}
57
- </div>
58
- </footer>
59
- );
60
- });
1
+ import { forwardRef } from "react";
2
+ import type { HTMLAttributes, ReactNode } from "react";
3
+ import { cx } from "../utils";
4
+ import type { FooterLink } from "./BlockFooter";
5
+
6
+ export interface SiteFooterColumn {
7
+ title: ReactNode;
8
+ /** Link list column. */
9
+ links?: FooterLink[];
10
+ /** Address column: children are rendered in an `<address class="oc-site-footer__list">`. */
11
+ address?: ReactNode;
12
+ }
13
+
14
+ export interface BlockSiteFooterProps extends Omit<HTMLAttributes<HTMLElement>, "about"> {
15
+ /** Brand link content, e.g. icon tile + name (rendered in `oc-site-header__brand`). */
16
+ brand: ReactNode;
17
+ brandHref?: string;
18
+ about?: ReactNode;
19
+ columns: SiteFooterColumn[];
20
+ copyright?: ReactNode;
21
+ credit?: ReactNode;
22
+ }
23
+
24
+ /** `.oc-site-footer`: brand column, link columns and a bottom bar. */
25
+ export const BlockSiteFooter = forwardRef<HTMLElement, BlockSiteFooterProps>(function BlockSiteFooter(
26
+ { brand, brandHref = "/", about, columns, copyright, credit, className, ...rest },
27
+ ref,
28
+ ) {
29
+ return (
30
+ <footer ref={ref} className={cx("oc-site-footer", className)} {...rest}>
31
+ <div className="oc-container">
32
+ <div className="oc-site-footer__grid">
33
+ <div className="oc-site-footer__about">
34
+ <a className="oc-site-header__brand" href={brandHref}>{brand}</a>
35
+ {about != null && <p>{about}</p>}
36
+ </div>
37
+ {columns.map((col, i) => (
38
+ <div key={i}>
39
+ <h2 className="oc-site-footer__title">{col.title}</h2>
40
+ {col.links && (
41
+ <ul className="oc-site-footer__list">
42
+ {col.links.map((l, j) => (
43
+ <li key={j}><a href={l.href}>{l.label}</a></li>
44
+ ))}
45
+ </ul>
46
+ )}
47
+ {col.address != null && <address className="oc-site-footer__list">{col.address}</address>}
48
+ </div>
49
+ ))}
50
+ </div>
51
+ {(copyright != null || credit != null) && (
52
+ <div className="oc-site-footer__bottom">
53
+ <span>{copyright}</span>
54
+ <span>{credit}</span>
55
+ </div>
56
+ )}
57
+ </div>
58
+ </footer>
59
+ );
60
+ });