@birdycom/omniaui-react 0.0.0-stage → 0.11.0

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 (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -2
  3. package/dist/index.d.ts +2640 -0
  4. package/dist/index.js +4813 -0
  5. package/manifest.json +838 -0
  6. package/package.json +69 -4
  7. package/src/blocks/BlockAboutTimeline.tsx +46 -0
  8. package/src/blocks/BlockAppHeader.tsx +52 -0
  9. package/src/blocks/BlockAppShell.tsx +56 -0
  10. package/src/blocks/BlockAuth.tsx +53 -0
  11. package/src/blocks/BlockAuthSplit.tsx +84 -0
  12. package/src/blocks/BlockContactGrid.tsx +71 -0
  13. package/src/blocks/BlockCta.tsx +23 -0
  14. package/src/blocks/BlockDataTable.tsx +57 -0
  15. package/src/blocks/BlockFaq.tsx +55 -0
  16. package/src/blocks/BlockFeatures.tsx +38 -0
  17. package/src/blocks/BlockFooter.tsx +50 -0
  18. package/src/blocks/BlockFormPage.tsx +26 -0
  19. package/src/blocks/BlockHero.tsx +27 -0
  20. package/src/blocks/BlockHeroCode.tsx +63 -0
  21. package/src/blocks/BlockHeroOrbit.tsx +73 -0
  22. package/src/blocks/BlockHeroSplit.tsx +28 -0
  23. package/src/blocks/BlockModuleHeader.tsx +34 -0
  24. package/src/blocks/BlockPageHero.tsx +25 -0
  25. package/src/blocks/BlockPortfolioGrid.tsx +50 -0
  26. package/src/blocks/BlockPricing.tsx +62 -0
  27. package/src/blocks/BlockProcess.tsx +50 -0
  28. package/src/blocks/BlockProductGrid.tsx +54 -0
  29. package/src/blocks/BlockProfileHeader.tsx +57 -0
  30. package/src/blocks/BlockShowcase.tsx +58 -0
  31. package/src/blocks/BlockSiteFooter.tsx +60 -0
  32. package/src/blocks/BlockSiteHeader.tsx +42 -0
  33. package/src/blocks/BlockSpotlight.tsx +58 -0
  34. package/src/blocks/BlockStats.tsx +26 -0
  35. package/src/blocks/BlockStatsCounter.tsx +31 -0
  36. package/src/blocks/BlockTeamList.tsx +48 -0
  37. package/src/blocks/BlockTwoFactor.tsx +57 -0
  38. package/src/blocks/BlockWizardDialog.tsx +101 -0
  39. package/src/components/Accordion.tsx +31 -0
  40. package/src/components/AccordionAccent.tsx +39 -0
  41. package/src/components/AffixInput.tsx +47 -0
  42. package/src/components/Alert.tsx +24 -0
  43. package/src/components/AlertDialog.tsx +60 -0
  44. package/src/components/AlphaTabs.tsx +63 -0
  45. package/src/components/Attachment.tsx +140 -0
  46. package/src/components/Avatar.tsx +32 -0
  47. package/src/components/BackLink.tsx +11 -0
  48. package/src/components/Badge.tsx +14 -0
  49. package/src/components/BarcodeInput.tsx +64 -0
  50. package/src/components/Breadcrumb.tsx +32 -0
  51. package/src/components/BrowserFrame.tsx +48 -0
  52. package/src/components/BusyOverlay.tsx +52 -0
  53. package/src/components/Button.tsx +49 -0
  54. package/src/components/Card.tsx +27 -0
  55. package/src/components/ChartLine.tsx +119 -0
  56. package/src/components/ChatComposer.tsx +73 -0
  57. package/src/components/Checkbox.tsx +14 -0
  58. package/src/components/Checklist.tsx +45 -0
  59. package/src/components/ChipFilter.tsx +60 -0
  60. package/src/components/Collapsible.tsx +43 -0
  61. package/src/components/ColorField.tsx +48 -0
  62. package/src/components/ColorSelect.tsx +49 -0
  63. package/src/components/ColumnMenu.tsx +66 -0
  64. package/src/components/Command.tsx +113 -0
  65. package/src/components/ConfirmDialog.tsx +95 -0
  66. package/src/components/CookieConsent.tsx +137 -0
  67. package/src/components/CopyField.tsx +44 -0
  68. package/src/components/Counter.tsx +21 -0
  69. package/src/components/DataTable.tsx +201 -0
  70. package/src/components/DecimalInput.tsx +87 -0
  71. package/src/components/DefinitionList.tsx +38 -0
  72. package/src/components/DocViewer.tsx +110 -0
  73. package/src/components/Dropdown.tsx +52 -0
  74. package/src/components/Dropzone.tsx +46 -0
  75. package/src/components/EmojiPicker.tsx +72 -0
  76. package/src/components/EmptyState.tsx +27 -0
  77. package/src/components/EntityCard.tsx +56 -0
  78. package/src/components/FactList.tsx +43 -0
  79. package/src/components/FilterCounts.tsx +50 -0
  80. package/src/components/FlipCard.tsx +55 -0
  81. package/src/components/FormField.tsx +54 -0
  82. package/src/components/Greeting.tsx +63 -0
  83. package/src/components/Heading.tsx +27 -0
  84. package/src/components/Icon.tsx +25 -0
  85. package/src/components/ImageZoom.tsx +51 -0
  86. package/src/components/Input.tsx +21 -0
  87. package/src/components/InputGroup.tsx +24 -0
  88. package/src/components/LanguageSelect.tsx +64 -0
  89. package/src/components/LauncherCard.tsx +50 -0
  90. package/src/components/Lightbox.tsx +83 -0
  91. package/src/components/LinkArrow.tsx +27 -0
  92. package/src/components/ListToolbar.tsx +67 -0
  93. package/src/components/Marquee.tsx +38 -0
  94. package/src/components/MediaCard.tsx +47 -0
  95. package/src/components/MediaRow.tsx +53 -0
  96. package/src/components/MediaThumb.tsx +49 -0
  97. package/src/components/Message.tsx +171 -0
  98. package/src/components/MessageThread.tsx +177 -0
  99. package/src/components/Modal.tsx +81 -0
  100. package/src/components/Navmenu.tsx +62 -0
  101. package/src/components/NextTeaser.tsx +29 -0
  102. package/src/components/NumberInput.tsx +64 -0
  103. package/src/components/OtpInput.tsx +48 -0
  104. package/src/components/PageHeader.tsx +43 -0
  105. package/src/components/Pagination.tsx +42 -0
  106. package/src/components/PasswordInput.tsx +36 -0
  107. package/src/components/PasswordRequirements.tsx +46 -0
  108. package/src/components/PhoneInput.tsx +93 -0
  109. package/src/components/Popover.tsx +34 -0
  110. package/src/components/PriceCard.tsx +83 -0
  111. package/src/components/ProductMark.tsx +35 -0
  112. package/src/components/Progress.tsx +32 -0
  113. package/src/components/ProjectCard.tsx +46 -0
  114. package/src/components/Prose.tsx +79 -0
  115. package/src/components/RecoveryCodes.tsx +65 -0
  116. package/src/components/Segmented.tsx +47 -0
  117. package/src/components/Select.tsx +27 -0
  118. package/src/components/SeverityCard.tsx +53 -0
  119. package/src/components/Sheet.tsx +62 -0
  120. package/src/components/Sidebar.tsx +145 -0
  121. package/src/components/Skeleton.tsx +12 -0
  122. package/src/components/SortableRow.tsx +46 -0
  123. package/src/components/StackCard.tsx +40 -0
  124. package/src/components/StackList.tsx +42 -0
  125. package/src/components/StatCard.tsx +49 -0
  126. package/src/components/Stepper.tsx +54 -0
  127. package/src/components/Switch.tsx +18 -0
  128. package/src/components/Table.tsx +75 -0
  129. package/src/components/TableActions.tsx +54 -0
  130. package/src/components/TableCompact.tsx +61 -0
  131. package/src/components/Tabs.tsx +45 -0
  132. package/src/components/TagInput.tsx +73 -0
  133. package/src/components/TerminalLog.tsx +63 -0
  134. package/src/components/Textarea.tsx +19 -0
  135. package/src/components/ThemeSwitch.tsx +45 -0
  136. package/src/components/TimeInput.tsx +76 -0
  137. package/src/components/Timeline.tsx +45 -0
  138. package/src/components/Toast.tsx +138 -0
  139. package/src/components/ToggleField.tsx +39 -0
  140. package/src/components/Tooltip.tsx +14 -0
  141. package/src/components/TopProgress.tsx +54 -0
  142. package/src/components/Tree.tsx +97 -0
  143. package/src/components/UpgradeBanner.tsx +62 -0
  144. package/src/components/User.tsx +31 -0
  145. package/src/components/VersionBanner.tsx +41 -0
  146. package/src/hooks.tsx +141 -0
  147. package/src/index.ts +142 -0
  148. package/src/utils.ts +25 -0
@@ -0,0 +1,26 @@
1
+ import { forwardRef } from "react";
2
+ import type { FormHTMLAttributes, HTMLAttributes, ReactNode } from "react";
3
+ import { cx } from "../utils";
4
+
5
+ export interface BlockFormPageProps extends FormHTMLAttributes<HTMLFormElement> {
6
+ /** Sticky action bar content (cancel + save buttons). */
7
+ actions?: ReactNode;
8
+ }
9
+
10
+ /** `form.oc-block-formpage`: collapsible sections (children) with a sticky action bar. */
11
+ export const BlockFormPage = forwardRef<HTMLFormElement, BlockFormPageProps>(function BlockFormPage(
12
+ { actions, className, children, ...rest },
13
+ ref,
14
+ ) {
15
+ return (
16
+ <form ref={ref} className={cx("oc-block-formpage", className)} {...rest}>
17
+ {children}
18
+ {actions != null && <div className="oc-block-formpage-actions">{actions}</div>}
19
+ </form>
20
+ );
21
+ });
22
+
23
+ /** `.oc-block-formpage-grid`: two-column field grid from 40rem. */
24
+ export function BlockFormPageGrid({ className, ...rest }: HTMLAttributes<HTMLDivElement>) {
25
+ return <div className={cx("oc-block-formpage-grid", className)} {...rest} />;
26
+ }
@@ -0,0 +1,27 @@
1
+ import { forwardRef } from "react";
2
+ import type { HTMLAttributes, ReactNode } from "react";
3
+ import { cx } from "../utils";
4
+
5
+ export interface BlockHeroProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
6
+ /** Small badge above the headline. */
7
+ badge?: ReactNode;
8
+ title: ReactNode;
9
+ description?: ReactNode;
10
+ /** Buttons row (`oc-block-hero-actions`). */
11
+ actions?: ReactNode;
12
+ }
13
+
14
+ /** `.oc-block-hero`: centered hero with badge, headline, text and actions. */
15
+ export const BlockHero = forwardRef<HTMLDivElement, BlockHeroProps>(function BlockHero(
16
+ { badge, title, description, actions, className, ...rest },
17
+ ref,
18
+ ) {
19
+ return (
20
+ <div ref={ref} className={cx("oc-block-hero", className)} {...rest}>
21
+ {badge != null && <span className="oc-block-hero-badge">{badge}</span>}
22
+ <h1>{title}</h1>
23
+ {description != null && <p>{description}</p>}
24
+ {actions != null && <div className="oc-block-hero-actions">{actions}</div>}
25
+ </div>
26
+ );
27
+ });
@@ -0,0 +1,63 @@
1
+ import { forwardRef } from "react";
2
+ import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
3
+ import { cx } from "../utils";
4
+
5
+ export interface HeroTile {
6
+ glyph: ReactNode;
7
+ accent: string;
8
+ style: CSSProperties;
9
+ }
10
+
11
+ const DEFAULT_TILES: HeroTile[] = [
12
+ { glyph: "◆", accent: "green", style: { inset: "-0.5rem auto auto 8%" } },
13
+ { glyph: "◉", accent: "violet", style: { inset: "0 6% auto auto", animationDelay: "-2s" } },
14
+ { glyph: "▲", accent: "orange", style: { inset: "auto 10% -0.5rem auto", animationDelay: "-4s" } },
15
+ ];
16
+
17
+ export interface BlockHeroCodeProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
18
+ eyebrow?: ReactNode;
19
+ /** Display title; wrap a word in `<span className="oc-text-gradient">`. */
20
+ title: ReactNode;
21
+ lead?: ReactNode;
22
+ /** Buttons (usually two `oc-btn--gradient` / `oc-btn--glass` links). */
23
+ actions?: ReactNode;
24
+ /** File name in the code window bar. */
25
+ codeTitle?: ReactNode;
26
+ /** Code shown in the window. */
27
+ code: string;
28
+ /** Floating icon tiles (accent name from the accent palette + position style). */
29
+ tiles?: HeroTile[];
30
+ }
31
+
32
+ /** Landing hero: eyebrow, display title, lead, buttons and a code window with floating icon tiles. */
33
+ export const BlockHeroCode = forwardRef<HTMLElement, BlockHeroCodeProps>(function BlockHeroCode(
34
+ { eyebrow, title, lead, actions, codeTitle, code, tiles = DEFAULT_TILES, className, ...rest },
35
+ ref,
36
+ ) {
37
+ return (
38
+ <section ref={ref} className={cx("oc-section-pad", className)} {...rest}>
39
+ <div className="oc-container oc-split">
40
+ <div>
41
+ {eyebrow != null && <p className="oc-eyebrow">{eyebrow}</p>}
42
+ <h1 className="oc-display">{title}</h1>
43
+ {lead != null && <p className="oc-lead">{lead}</p>}
44
+ {actions != null && <div className="flex flex-wrap gap-1 mt-2">{actions}</div>}
45
+ </div>
46
+ <div className="relative p-2">
47
+ {tiles.map((tile, i) => (
48
+ <span key={i} className={`oc-icon-tile oc-icon-tile--float oc-accent--${tile.accent} absolute`} style={tile.style} aria-hidden="true">
49
+ {tile.glyph}
50
+ </span>
51
+ ))}
52
+ <div className="oc-code-window">
53
+ <div className="oc-code-window__bar">
54
+ <i /><i /><i />
55
+ {codeTitle != null && <span className="oc-code-window__title">{codeTitle}</span>}
56
+ </div>
57
+ <pre className="oc-code-window__body">{code}</pre>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </section>
62
+ );
63
+ });
@@ -0,0 +1,73 @@
1
+ // File: packages/react/src/blocks/BlockHeroOrbit.tsx | Map: types + defaults -> BlockHeroOrbit (copy -> visual: ring with marks + center)
2
+ import { forwardRef } from "react";
3
+ import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
4
+ import { cx } from "../utils";
5
+
6
+ export interface HeroOrbitMark {
7
+ glyph: ReactNode;
8
+ /** Accent preset (green, violet, orange, cyan, red, yellow, blue). */
9
+ accent: string;
10
+ }
11
+
12
+ const DEFAULT_MARKS: HeroOrbitMark[] = [
13
+ { glyph: "◆", accent: "green" },
14
+ { glyph: "◉", accent: "violet" },
15
+ { glyph: "▲", accent: "orange" },
16
+ { glyph: "★", accent: "cyan" },
17
+ { glyph: "♥", accent: "red" },
18
+ { glyph: "☀", accent: "yellow" },
19
+ ];
20
+
21
+ export interface BlockHeroOrbitProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
22
+ eyebrow?: ReactNode;
23
+ /** Display title; wrap a word in `<span className="oc-text-gradient">`. */
24
+ title: ReactNode;
25
+ lead?: ReactNode;
26
+ actions?: ReactNode;
27
+ /** Marks on the ring; they are spread evenly. */
28
+ marks?: HeroOrbitMark[];
29
+ codeTitle?: ReactNode;
30
+ code?: string;
31
+ /** Replaces the code window in the center. Give it className="oc-block-orbit-center". */
32
+ center?: ReactNode;
33
+ /** One turn, e.g. "30s" (default 60s). */
34
+ duration?: string;
35
+ }
36
+
37
+ /** Hero with a code window and product marks that orbit around it. */
38
+ export const BlockHeroOrbit = forwardRef<HTMLElement, BlockHeroOrbitProps>(function BlockHeroOrbit(
39
+ { eyebrow, title, lead, actions, marks = DEFAULT_MARKS, codeTitle, code = "", center, duration, className, ...rest },
40
+ ref,
41
+ ) {
42
+ const visualStyle = duration ? ({ "--oc-orbit-duration": duration } as CSSProperties) : undefined;
43
+ return (
44
+ <section ref={ref} className={cx("oc-block-orbit oc-section-pad", className)} {...rest}>
45
+ <div className="oc-container oc-split">
46
+ <div>
47
+ {eyebrow != null && <p className="oc-eyebrow">{eyebrow}</p>}
48
+ <h1 className="oc-display">{title}</h1>
49
+ {lead != null && <p className="oc-lead">{lead}</p>}
50
+ {actions != null && <div className="flex flex-wrap gap-1 mt-2">{actions}</div>}
51
+ </div>
52
+ <div className="oc-block-orbit-visual" style={visualStyle}>
53
+ <div className="oc-block-orbit-ring" aria-hidden="true">
54
+ {marks.map((mark, i) => (
55
+ <span key={i} className="oc-block-orbit-mark" style={{ "--oc-orbit-angle": `${(360 / marks.length) * i}deg` } as CSSProperties}>
56
+ <span className={`oc-product-mark oc-product-mark--sm oc-accent--${mark.accent}`}>{mark.glyph}</span>
57
+ </span>
58
+ ))}
59
+ </div>
60
+ {center ?? (
61
+ <div className="oc-code-window oc-block-orbit-center">
62
+ <div className="oc-code-window__bar">
63
+ <i /><i /><i />
64
+ {codeTitle != null && <span className="oc-code-window__title">{codeTitle}</span>}
65
+ </div>
66
+ <pre className="oc-code-window__body">{code}</pre>
67
+ </div>
68
+ )}
69
+ </div>
70
+ </div>
71
+ </section>
72
+ );
73
+ });
@@ -0,0 +1,28 @@
1
+ import { forwardRef } from "react";
2
+ import type { HTMLAttributes, ReactNode } from "react";
3
+ import { cx } from "../utils";
4
+
5
+ export interface BlockHeroSplitProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
6
+ title: ReactNode;
7
+ description?: ReactNode;
8
+ actions?: ReactNode;
9
+ /** Right column (image, screenshot, placeholder). */
10
+ visual?: ReactNode;
11
+ }
12
+
13
+ /** `.oc-block-hero-split`: text on the left, visual on the right. */
14
+ export const BlockHeroSplit = forwardRef<HTMLDivElement, BlockHeroSplitProps>(function BlockHeroSplit(
15
+ { title, description, actions, visual, className, ...rest },
16
+ ref,
17
+ ) {
18
+ return (
19
+ <div ref={ref} className={cx("oc-block-hero-split", className)} {...rest}>
20
+ <div>
21
+ <h1>{title}</h1>
22
+ {description != null && <p>{description}</p>}
23
+ {actions != null && <div className="oc-block-hero-actions justify-start">{actions}</div>}
24
+ </div>
25
+ <div className="oc-block-hero-visual">{visual}</div>
26
+ </div>
27
+ );
28
+ });
@@ -0,0 +1,34 @@
1
+ import { forwardRef } from "react";
2
+ import type { HTMLAttributes, ReactNode } from "react";
3
+ import { cx } from "../utils";
4
+
5
+ export interface BlockModuleHeaderProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
6
+ /** Inline SVG in the icon tile. */
7
+ icon?: ReactNode;
8
+ title: ReactNode;
9
+ intro?: ReactNode;
10
+ /** Badges under the title. */
11
+ meta?: ReactNode;
12
+ /** Buttons below the band (back link, primary action). */
13
+ actions?: ReactNode;
14
+ }
15
+
16
+ /** `.oc-block-modulehead`: page header with icon tile, title, intro, meta badges and actions. */
17
+ export const BlockModuleHeader = forwardRef<HTMLElement, BlockModuleHeaderProps>(function BlockModuleHeader(
18
+ { icon, title, intro, meta, actions, className, ...rest },
19
+ ref,
20
+ ) {
21
+ return (
22
+ <header ref={ref} className={cx("oc-block-modulehead", className)} {...rest}>
23
+ <div className="oc-block-modulehead-band">
24
+ {icon != null && <span className="oc-block-modulehead-icon" aria-hidden="true">{icon}</span>}
25
+ <div className="oc-block-modulehead-body">
26
+ <h1 className="oc-block-modulehead-title">{title}</h1>
27
+ {intro != null && <p className="oc-block-modulehead-intro">{intro}</p>}
28
+ {meta != null && <div className="oc-block-modulehead-meta">{meta}</div>}
29
+ </div>
30
+ </div>
31
+ {actions != null && <div className="oc-block-modulehead-actions">{actions}</div>}
32
+ </header>
33
+ );
34
+ });
@@ -0,0 +1,25 @@
1
+ import { forwardRef } from "react";
2
+ import type { HTMLAttributes, ReactNode } from "react";
3
+ import { cx } from "../utils";
4
+
5
+ export interface BlockPageHeroProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
6
+ eyebrow?: ReactNode;
7
+ title: ReactNode;
8
+ description?: ReactNode;
9
+ actions?: ReactNode;
10
+ }
11
+
12
+ /** `.oc-hero`: page header with eyebrow, title, text and actions. */
13
+ export const BlockPageHero = forwardRef<HTMLElement, BlockPageHeroProps>(function BlockPageHero(
14
+ { eyebrow, title, description, actions, className, ...rest },
15
+ ref,
16
+ ) {
17
+ return (
18
+ <section ref={ref} className={cx("oc-hero", className)} {...rest}>
19
+ {eyebrow != null && <span className="oc-hero-eyebrow">{eyebrow}</span>}
20
+ <h1 className="oc-hero-title">{title}</h1>
21
+ {description != null && <p className="oc-hero-text">{description}</p>}
22
+ {actions != null && <div className="oc-hero-actions">{actions}</div>}
23
+ </section>
24
+ );
25
+ });
@@ -0,0 +1,50 @@
1
+ // File: packages/react/src/blocks/BlockPortfolioGrid.tsx | Map: types -> BlockPortfolioGrid (head (title, lead, link) -> grid of ProjectCard)
2
+ import { forwardRef, useId } from "react";
3
+ import type { HTMLAttributes, ReactNode } from "react";
4
+ import { cx } from "../utils";
5
+ import { ProjectCard } from "../components/ProjectCard";
6
+ import type { ProjectCardProps } from "../components/ProjectCard";
7
+
8
+ export type PortfolioGridProject = ProjectCardProps & {
9
+ /** Accent preset for the card (green, violet, ...). */
10
+ accent?: string;
11
+ };
12
+
13
+ export interface BlockPortfolioGridProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
14
+ eyebrow?: ReactNode;
15
+ /** Display title; wrap a word in `<span className="oc-text-gradient">`. */
16
+ title: ReactNode;
17
+ lead?: ReactNode;
18
+ /** Link at the right of the head, e.g. <LinkArrow href="/work">All projects</LinkArrow>. */
19
+ link?: ReactNode;
20
+ projects: PortfolioGridProject[];
21
+ /** Wider cards. */
22
+ two?: boolean;
23
+ }
24
+
25
+ /** `.oc-block-portfolio`: heading plus a grid of project cards. */
26
+ export const BlockPortfolioGrid = forwardRef<HTMLElement, BlockPortfolioGridProps>(function BlockPortfolioGrid(
27
+ { eyebrow, title, lead, link, projects, two = false, className, ...rest },
28
+ ref,
29
+ ) {
30
+ const titleId = `oc-portfolio-title-${useId().replace(/[^\w-]/g, "")}`;
31
+ return (
32
+ <section ref={ref} className={cx("oc-block-portfolio oc-section-pad", two && "oc-block-portfolio--two", className)} aria-labelledby={titleId} {...rest}>
33
+ <div className="oc-container">
34
+ <div className="oc-block-portfolio-head">
35
+ <div>
36
+ {eyebrow != null && <p className="oc-eyebrow">{eyebrow}</p>}
37
+ <h2 className="oc-display oc-display--md" id={titleId}>{title}</h2>
38
+ {lead != null && <p className="oc-block-portfolio-lead">{lead}</p>}
39
+ </div>
40
+ {link}
41
+ </div>
42
+ <div className="oc-block-portfolio-grid">
43
+ {projects.map(({ accent, className: cardClass, ...card }, i) => (
44
+ <ProjectCard key={i} className={cx(accent && `oc-accent--${accent}`, cardClass)} {...card} />
45
+ ))}
46
+ </div>
47
+ </div>
48
+ </section>
49
+ );
50
+ });
@@ -0,0 +1,62 @@
1
+ import { forwardRef } from "react";
2
+ import type { HTMLAttributes, ReactNode } from "react";
3
+ import { cx } from "../utils";
4
+ import { Button, ButtonLink } from "../components/Button";
5
+
6
+ export interface PricingPlan {
7
+ name: ReactNode;
8
+ description?: ReactNode;
9
+ price: ReactNode;
10
+ /** e.g. `/month` */
11
+ period?: ReactNode;
12
+ features: ReactNode[];
13
+ /** Highlighted plan (`oc-block-pricing-card--featured`, primary button). */
14
+ featured?: boolean;
15
+ /** Button label. */
16
+ action: ReactNode;
17
+ /** Renders the action as a link when set. */
18
+ href?: string;
19
+ onAction?: () => void;
20
+ }
21
+
22
+ export interface BlockPricingProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
23
+ title?: ReactNode;
24
+ description?: ReactNode;
25
+ plans: PricingPlan[];
26
+ }
27
+
28
+ /** `.oc-block-pricing`: pricing cards with a featured plan. */
29
+ export const BlockPricing = forwardRef<HTMLDivElement, BlockPricingProps>(function BlockPricing(
30
+ { title, description, plans, className, ...rest },
31
+ ref,
32
+ ) {
33
+ return (
34
+ <div ref={ref} className={cx("oc-block-pricing", className)} {...rest}>
35
+ {title != null && <h2>{title}</h2>}
36
+ {description != null && <p>{description}</p>}
37
+ <div className="oc-block-pricing-grid">
38
+ {plans.map((plan, i) => {
39
+ const variant = plan.featured ? "default" : "outline";
40
+ return (
41
+ <div className={cx("oc-block-pricing-card", plan.featured && "oc-block-pricing-card--featured")} key={i}>
42
+ <p className="oc-block-pricing-name">{plan.name}</p>
43
+ {plan.description != null && <p className="oc-block-pricing-desc">{plan.description}</p>}
44
+ <p className="oc-block-pricing-price">
45
+ {plan.price}
46
+ {plan.period != null && <> <span>{plan.period}</span></>}
47
+ </p>
48
+ <ul className="oc-block-pricing-features">
49
+ {plan.features.map((f, j) => <li key={j}>{f}</li>)}
50
+ </ul>
51
+ {plan.href ? (
52
+ <ButtonLink variant={variant} href={plan.href} style={{ width: "100%" }}>{plan.action}</ButtonLink>
53
+ ) : (
54
+ <Button variant={variant} style={{ width: "100%" }} onClick={plan.onAction}>{plan.action}</Button>
55
+ )}
56
+ </div>
57
+ );
58
+ })}
59
+ </div>
60
+ </div>
61
+ );
62
+ });
@@ -0,0 +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
+
@@ -0,0 +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
+ });
@@ -0,0 +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
+ });
@@ -0,0 +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
+ });