@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.
- package/LICENSE +21 -21
- package/README.md +21 -21
- package/package.json +71 -71
- package/src/blocks/BlockAboutTimeline.tsx +46 -46
- package/src/blocks/BlockAppHeader.tsx +52 -52
- package/src/blocks/BlockAppShell.tsx +56 -56
- package/src/blocks/BlockAuth.tsx +53 -53
- package/src/blocks/BlockAuthSplit.tsx +84 -84
- package/src/blocks/BlockContactGrid.tsx +71 -71
- package/src/blocks/BlockCta.tsx +23 -23
- package/src/blocks/BlockDataTable.tsx +57 -57
- package/src/blocks/BlockFaq.tsx +55 -55
- package/src/blocks/BlockFeatures.tsx +38 -38
- package/src/blocks/BlockFooter.tsx +50 -50
- package/src/blocks/BlockFormPage.tsx +26 -26
- package/src/blocks/BlockHero.tsx +27 -27
- package/src/blocks/BlockHeroCode.tsx +63 -63
- package/src/blocks/BlockHeroOrbit.tsx +73 -73
- package/src/blocks/BlockHeroSplit.tsx +28 -28
- package/src/blocks/BlockModuleHeader.tsx +34 -34
- package/src/blocks/BlockPageHero.tsx +25 -25
- package/src/blocks/BlockPortfolioGrid.tsx +50 -50
- package/src/blocks/BlockPricing.tsx +62 -62
- package/src/blocks/BlockProcess.tsx +50 -50
- package/src/blocks/BlockProductGrid.tsx +54 -54
- package/src/blocks/BlockProfileHeader.tsx +57 -57
- package/src/blocks/BlockShowcase.tsx +58 -58
- package/src/blocks/BlockSiteFooter.tsx +60 -60
- package/src/blocks/BlockSiteHeader.tsx +42 -42
- package/src/blocks/BlockSpotlight.tsx +58 -58
- package/src/blocks/BlockStats.tsx +26 -26
- package/src/blocks/BlockStatsCounter.tsx +31 -31
- package/src/blocks/BlockTeamList.tsx +48 -48
- package/src/blocks/BlockTwoFactor.tsx +57 -57
- package/src/blocks/BlockWizardDialog.tsx +101 -101
- package/src/components/Accordion.tsx +31 -31
- package/src/components/AccordionAccent.tsx +39 -39
- package/src/components/AffixInput.tsx +47 -47
- package/src/components/Alert.tsx +24 -24
- package/src/components/AlertDialog.tsx +60 -60
- package/src/components/AlphaTabs.tsx +63 -63
- package/src/components/Attachment.tsx +140 -140
- package/src/components/Avatar.tsx +32 -32
- package/src/components/BackLink.tsx +11 -11
- package/src/components/Badge.tsx +14 -14
- package/src/components/BarcodeInput.tsx +64 -64
- package/src/components/Breadcrumb.tsx +32 -32
- package/src/components/BrowserFrame.tsx +48 -48
- package/src/components/BusyOverlay.tsx +52 -52
- package/src/components/Button.tsx +49 -49
- package/src/components/Card.tsx +27 -27
- package/src/components/ChartLine.tsx +119 -119
- package/src/components/ChatComposer.tsx +73 -73
- package/src/components/Checkbox.tsx +14 -14
- package/src/components/Checklist.tsx +45 -45
- package/src/components/ChipFilter.tsx +60 -60
- package/src/components/Collapsible.tsx +43 -43
- package/src/components/ColorField.tsx +48 -48
- package/src/components/ColorSelect.tsx +49 -49
- package/src/components/ColumnMenu.tsx +66 -66
- package/src/components/Command.tsx +113 -113
- package/src/components/ConfirmDialog.tsx +95 -95
- package/src/components/CookieConsent.tsx +137 -137
- package/src/components/CopyField.tsx +44 -44
- package/src/components/Counter.tsx +21 -21
- package/src/components/DataTable.tsx +201 -201
- package/src/components/DecimalInput.tsx +87 -87
- package/src/components/DefinitionList.tsx +38 -38
- package/src/components/DocViewer.tsx +110 -110
- package/src/components/Dropdown.tsx +52 -52
- package/src/components/Dropzone.tsx +46 -46
- package/src/components/EmojiPicker.tsx +72 -72
- package/src/components/EmptyState.tsx +27 -27
- package/src/components/EntityCard.tsx +56 -56
- package/src/components/FactList.tsx +43 -43
- package/src/components/FilterCounts.tsx +50 -50
- package/src/components/FlipCard.tsx +55 -55
- package/src/components/FormField.tsx +54 -54
- package/src/components/Greeting.tsx +63 -63
- package/src/components/Heading.tsx +27 -27
- package/src/components/Icon.tsx +25 -25
- package/src/components/ImageZoom.tsx +51 -51
- package/src/components/Input.tsx +21 -21
- package/src/components/InputGroup.tsx +24 -24
- package/src/components/LanguageSelect.tsx +64 -64
- package/src/components/LauncherCard.tsx +50 -50
- package/src/components/Lightbox.tsx +83 -83
- package/src/components/LinkArrow.tsx +27 -27
- package/src/components/ListToolbar.tsx +67 -67
- package/src/components/Marquee.tsx +38 -38
- package/src/components/MediaCard.tsx +47 -47
- package/src/components/MediaRow.tsx +53 -53
- package/src/components/MediaThumb.tsx +49 -49
- package/src/components/Message.tsx +171 -171
- package/src/components/MessageThread.tsx +177 -177
- package/src/components/Modal.tsx +81 -81
- package/src/components/Navmenu.tsx +62 -62
- package/src/components/NextTeaser.tsx +29 -29
- package/src/components/NumberInput.tsx +64 -64
- package/src/components/OtpInput.tsx +48 -48
- package/src/components/PageHeader.tsx +43 -43
- package/src/components/Pagination.tsx +42 -42
- package/src/components/PasswordInput.tsx +36 -36
- package/src/components/PasswordRequirements.tsx +46 -46
- package/src/components/PhoneInput.tsx +93 -93
- package/src/components/Popover.tsx +34 -34
- package/src/components/PriceCard.tsx +83 -83
- package/src/components/ProductMark.tsx +35 -35
- package/src/components/Progress.tsx +32 -32
- package/src/components/ProjectCard.tsx +46 -46
- package/src/components/Prose.tsx +79 -79
- package/src/components/RecoveryCodes.tsx +65 -65
- package/src/components/Segmented.tsx +47 -47
- package/src/components/Select.tsx +27 -27
- package/src/components/SeverityCard.tsx +53 -53
- package/src/components/Sheet.tsx +62 -62
- package/src/components/Skeleton.tsx +12 -12
- package/src/components/SortableRow.tsx +46 -46
- package/src/components/StackCard.tsx +40 -40
- package/src/components/StackList.tsx +42 -42
- package/src/components/StatCard.tsx +49 -49
- package/src/components/Stepper.tsx +54 -54
- package/src/components/Switch.tsx +18 -18
- package/src/components/Table.tsx +75 -75
- package/src/components/TableActions.tsx +54 -54
- package/src/components/TableCompact.tsx +61 -61
- package/src/components/Tabs.tsx +45 -45
- package/src/components/TagInput.tsx +73 -73
- package/src/components/TerminalLog.tsx +63 -63
- package/src/components/Textarea.tsx +19 -19
- package/src/components/ThemeSwitch.tsx +45 -45
- package/src/components/TimeInput.tsx +76 -76
- package/src/components/Timeline.tsx +45 -45
- package/src/components/Toast.tsx +138 -138
- package/src/components/ToggleField.tsx +39 -39
- package/src/components/Tooltip.tsx +14 -14
- package/src/components/TopProgress.tsx +54 -54
- package/src/components/Tree.tsx +97 -97
- package/src/components/UpgradeBanner.tsx +62 -62
- package/src/components/User.tsx +31 -31
- package/src/components/VersionBanner.tsx +41 -41
- package/src/hooks.tsx +141 -141
- package/src/utils.ts +25 -25
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
// File: packages/react/src/components/ProductMark.tsx | Map: types -> ProductMark (span.oc-product-mark + modifiers)
|
|
2
|
-
import { forwardRef } from "react";
|
|
3
|
-
import type { HTMLAttributes } from "react";
|
|
4
|
-
import { cx } from "../utils";
|
|
5
|
-
|
|
6
|
-
export type ProductMarkSize = "sm" | "default" | "lg" | "xl";
|
|
7
|
-
export type ProductMarkAccent = "blue" | "cyan" | "green" | "orange" | "red" | "violet" | "yellow";
|
|
8
|
-
|
|
9
|
-
export interface ProductMarkProps extends HTMLAttributes<HTMLSpanElement> {
|
|
10
|
-
size?: ProductMarkSize;
|
|
11
|
-
/** Accent preset (adds .oc-accent--{accent}). */
|
|
12
|
-
accent?: ProductMarkAccent;
|
|
13
|
-
/** Tinted background with an accent glyph. */
|
|
14
|
-
soft?: boolean;
|
|
15
|
-
glow?: boolean;
|
|
16
|
-
/** Full-bleed image inside. */
|
|
17
|
-
cover?: boolean;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** `<span class="oc-product-mark">`: squircle app-icon tile. Decorative by default (aria-hidden). */
|
|
21
|
-
export const ProductMark = forwardRef<HTMLSpanElement, ProductMarkProps>(function ProductMark(
|
|
22
|
-
{ size = "default", accent, soft = false, glow = false, cover = false, className, ...rest },
|
|
23
|
-
ref,
|
|
24
|
-
) {
|
|
25
|
-
const classes = cx(
|
|
26
|
-
"oc-product-mark",
|
|
27
|
-
size !== "default" && `oc-product-mark--${size}`,
|
|
28
|
-
soft && "oc-product-mark--soft",
|
|
29
|
-
glow && "oc-product-mark--glow",
|
|
30
|
-
cover && "oc-product-mark--cover",
|
|
31
|
-
accent && `oc-accent--${accent}`,
|
|
32
|
-
className,
|
|
33
|
-
);
|
|
34
|
-
return <span ref={ref} className={classes} aria-hidden="true" {...rest} />;
|
|
35
|
-
});
|
|
1
|
+
// File: packages/react/src/components/ProductMark.tsx | Map: types -> ProductMark (span.oc-product-mark + modifiers)
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import type { HTMLAttributes } from "react";
|
|
4
|
+
import { cx } from "../utils";
|
|
5
|
+
|
|
6
|
+
export type ProductMarkSize = "sm" | "default" | "lg" | "xl";
|
|
7
|
+
export type ProductMarkAccent = "blue" | "cyan" | "green" | "orange" | "red" | "violet" | "yellow";
|
|
8
|
+
|
|
9
|
+
export interface ProductMarkProps extends HTMLAttributes<HTMLSpanElement> {
|
|
10
|
+
size?: ProductMarkSize;
|
|
11
|
+
/** Accent preset (adds .oc-accent--{accent}). */
|
|
12
|
+
accent?: ProductMarkAccent;
|
|
13
|
+
/** Tinted background with an accent glyph. */
|
|
14
|
+
soft?: boolean;
|
|
15
|
+
glow?: boolean;
|
|
16
|
+
/** Full-bleed image inside. */
|
|
17
|
+
cover?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** `<span class="oc-product-mark">`: squircle app-icon tile. Decorative by default (aria-hidden). */
|
|
21
|
+
export const ProductMark = forwardRef<HTMLSpanElement, ProductMarkProps>(function ProductMark(
|
|
22
|
+
{ size = "default", accent, soft = false, glow = false, cover = false, className, ...rest },
|
|
23
|
+
ref,
|
|
24
|
+
) {
|
|
25
|
+
const classes = cx(
|
|
26
|
+
"oc-product-mark",
|
|
27
|
+
size !== "default" && `oc-product-mark--${size}`,
|
|
28
|
+
soft && "oc-product-mark--soft",
|
|
29
|
+
glow && "oc-product-mark--glow",
|
|
30
|
+
cover && "oc-product-mark--cover",
|
|
31
|
+
accent && `oc-accent--${accent}`,
|
|
32
|
+
className,
|
|
33
|
+
);
|
|
34
|
+
return <span ref={ref} className={classes} aria-hidden="true" {...rest} />;
|
|
35
|
+
});
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import { forwardRef } from "react";
|
|
2
|
-
import type { HTMLAttributes } from "react";
|
|
3
|
-
import { cx } from "../utils";
|
|
4
|
-
|
|
5
|
-
export interface ProgressProps extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
-
value: number;
|
|
7
|
-
/** Maximum (default 100). */
|
|
8
|
-
max?: number;
|
|
9
|
-
/** Bar color. */
|
|
10
|
-
variant?: "default" | "success" | "warning" | "danger";
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** `<div class="oc-progress" role="progressbar">` with an `oc-progress-bar` sized in percent. */
|
|
14
|
-
export const Progress = forwardRef<HTMLDivElement, ProgressProps>(function Progress(
|
|
15
|
-
{ value, max = 100, variant = "default", className, ...rest },
|
|
16
|
-
ref,
|
|
17
|
-
) {
|
|
18
|
-
const pct = max > 0 ? Math.min(100, Math.max(0, Math.round((value / max) * 100))) : 0;
|
|
19
|
-
return (
|
|
20
|
-
<div
|
|
21
|
-
ref={ref}
|
|
22
|
-
className={cx("oc-progress", className)}
|
|
23
|
-
role="progressbar"
|
|
24
|
-
aria-valuenow={value}
|
|
25
|
-
aria-valuemin={0}
|
|
26
|
-
aria-valuemax={max}
|
|
27
|
-
{...rest}
|
|
28
|
-
>
|
|
29
|
-
<div className={cx("oc-progress-bar", variant !== "default" && `oc-progress-bar--${variant}`)} style={{ width: `${pct}%` }} />
|
|
30
|
-
</div>
|
|
31
|
-
);
|
|
32
|
-
});
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import type { HTMLAttributes } from "react";
|
|
3
|
+
import { cx } from "../utils";
|
|
4
|
+
|
|
5
|
+
export interface ProgressProps extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
value: number;
|
|
7
|
+
/** Maximum (default 100). */
|
|
8
|
+
max?: number;
|
|
9
|
+
/** Bar color. */
|
|
10
|
+
variant?: "default" | "success" | "warning" | "danger";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** `<div class="oc-progress" role="progressbar">` with an `oc-progress-bar` sized in percent. */
|
|
14
|
+
export const Progress = forwardRef<HTMLDivElement, ProgressProps>(function Progress(
|
|
15
|
+
{ value, max = 100, variant = "default", className, ...rest },
|
|
16
|
+
ref,
|
|
17
|
+
) {
|
|
18
|
+
const pct = max > 0 ? Math.min(100, Math.max(0, Math.round((value / max) * 100))) : 0;
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
ref={ref}
|
|
22
|
+
className={cx("oc-progress", className)}
|
|
23
|
+
role="progressbar"
|
|
24
|
+
aria-valuenow={value}
|
|
25
|
+
aria-valuemin={0}
|
|
26
|
+
aria-valuemax={max}
|
|
27
|
+
{...rest}
|
|
28
|
+
>
|
|
29
|
+
<div className={cx("oc-progress-bar", variant !== "default" && `oc-progress-bar--${variant}`)} style={{ width: `${pct}%` }} />
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
});
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
// File: packages/react/src/components/ProjectCard.tsx | Map: props -> ProjectCard (a -> frame (bar + shot) -> body)
|
|
2
|
-
import { forwardRef } from "react";
|
|
3
|
-
import type { AnchorHTMLAttributes, ReactNode } from "react";
|
|
4
|
-
import { cx } from "../utils";
|
|
5
|
-
|
|
6
|
-
// The native anchor "type" (MIME hint) is replaced by the badge text prop below.
|
|
7
|
-
export interface ProjectCardProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "type"> {
|
|
8
|
-
name: ReactNode;
|
|
9
|
-
/** Badge text ("Website"). */
|
|
10
|
-
type?: ReactNode;
|
|
11
|
-
/** Sector / location line. */
|
|
12
|
-
meta?: ReactNode;
|
|
13
|
-
/** Text of the arrow link (default "View project"). */
|
|
14
|
-
more?: ReactNode;
|
|
15
|
-
/** Screenshot URL; without it the preview is an accent gradient. */
|
|
16
|
-
image?: string;
|
|
17
|
-
/** Accessible description of the screenshot. */
|
|
18
|
-
imageAlt?: string;
|
|
19
|
-
/** Edge-to-edge preview. */
|
|
20
|
-
flat?: boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** `<a class="oc-project-card">`: portfolio card with a browser-like preview. */
|
|
24
|
-
export const ProjectCard = forwardRef<HTMLAnchorElement, ProjectCardProps>(function ProjectCard(
|
|
25
|
-
{ name, type, meta, more = "View project", image, imageAlt = "", flat = false, className, ...rest },
|
|
26
|
-
ref,
|
|
27
|
-
) {
|
|
28
|
-
return (
|
|
29
|
-
<a ref={ref} className={cx("oc-project-card", flat && "oc-project-card--flat", className)} {...rest}>
|
|
30
|
-
<span className="oc-project-card__frame">
|
|
31
|
-
<span className="oc-project-card__bar" aria-hidden="true"><i /><i /><i /></span>
|
|
32
|
-
{image ? (
|
|
33
|
-
<img className="oc-project-card__shot" src={image} alt={imageAlt} loading="lazy" />
|
|
34
|
-
) : (
|
|
35
|
-
<span className="oc-project-card__shot" role="img" aria-label={imageAlt || undefined} aria-hidden={imageAlt ? undefined : true} />
|
|
36
|
-
)}
|
|
37
|
-
</span>
|
|
38
|
-
<span className="oc-project-card__body">
|
|
39
|
-
{type != null && <span className="oc-project-card__type">{type}</span>}
|
|
40
|
-
<span className="oc-project-card__name">{name}</span>
|
|
41
|
-
{meta != null && <span className="oc-project-card__meta">{meta}</span>}
|
|
42
|
-
{more != null && <span className="oc-project-card__more">{more}</span>}
|
|
43
|
-
</span>
|
|
44
|
-
</a>
|
|
45
|
-
);
|
|
46
|
-
});
|
|
1
|
+
// File: packages/react/src/components/ProjectCard.tsx | Map: props -> ProjectCard (a -> frame (bar + shot) -> body)
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import type { AnchorHTMLAttributes, ReactNode } from "react";
|
|
4
|
+
import { cx } from "../utils";
|
|
5
|
+
|
|
6
|
+
// The native anchor "type" (MIME hint) is replaced by the badge text prop below.
|
|
7
|
+
export interface ProjectCardProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "type"> {
|
|
8
|
+
name: ReactNode;
|
|
9
|
+
/** Badge text ("Website"). */
|
|
10
|
+
type?: ReactNode;
|
|
11
|
+
/** Sector / location line. */
|
|
12
|
+
meta?: ReactNode;
|
|
13
|
+
/** Text of the arrow link (default "View project"). */
|
|
14
|
+
more?: ReactNode;
|
|
15
|
+
/** Screenshot URL; without it the preview is an accent gradient. */
|
|
16
|
+
image?: string;
|
|
17
|
+
/** Accessible description of the screenshot. */
|
|
18
|
+
imageAlt?: string;
|
|
19
|
+
/** Edge-to-edge preview. */
|
|
20
|
+
flat?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** `<a class="oc-project-card">`: portfolio card with a browser-like preview. */
|
|
24
|
+
export const ProjectCard = forwardRef<HTMLAnchorElement, ProjectCardProps>(function ProjectCard(
|
|
25
|
+
{ name, type, meta, more = "View project", image, imageAlt = "", flat = false, className, ...rest },
|
|
26
|
+
ref,
|
|
27
|
+
) {
|
|
28
|
+
return (
|
|
29
|
+
<a ref={ref} className={cx("oc-project-card", flat && "oc-project-card--flat", className)} {...rest}>
|
|
30
|
+
<span className="oc-project-card__frame">
|
|
31
|
+
<span className="oc-project-card__bar" aria-hidden="true"><i /><i /><i /></span>
|
|
32
|
+
{image ? (
|
|
33
|
+
<img className="oc-project-card__shot" src={image} alt={imageAlt} loading="lazy" />
|
|
34
|
+
) : (
|
|
35
|
+
<span className="oc-project-card__shot" role="img" aria-label={imageAlt || undefined} aria-hidden={imageAlt ? undefined : true} />
|
|
36
|
+
)}
|
|
37
|
+
</span>
|
|
38
|
+
<span className="oc-project-card__body">
|
|
39
|
+
{type != null && <span className="oc-project-card__type">{type}</span>}
|
|
40
|
+
<span className="oc-project-card__name">{name}</span>
|
|
41
|
+
{meta != null && <span className="oc-project-card__meta">{meta}</span>}
|
|
42
|
+
{more != null && <span className="oc-project-card__more">{more}</span>}
|
|
43
|
+
</span>
|
|
44
|
+
</a>
|
|
45
|
+
);
|
|
46
|
+
});
|
package/src/components/Prose.tsx
CHANGED
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
// File: packages/react/src/components/Prose.tsx
|
|
2
|
-
// Map: types -> demo article (same text as the snippet) -> Prose (wrapper with size modifiers) -> ProseLead
|
|
3
|
-
// Same markup as packages/ui/snippets/prose.html. Put plain HTML (h1-h4, p, lists, blockquote, table, figure, pre) inside.
|
|
4
|
-
import { forwardRef } from "react";
|
|
5
|
-
import type { HTMLAttributes, ReactNode } from "react";
|
|
6
|
-
import { cx } from "../utils";
|
|
7
|
-
|
|
8
|
-
export interface ProseProps extends HTMLAttributes<HTMLElement> {
|
|
9
|
-
/** All spacing scales along. */
|
|
10
|
-
size?: "sm" | "md" | "lg";
|
|
11
|
-
/** No max width (default 68ch). */
|
|
12
|
-
full?: boolean;
|
|
13
|
-
as?: "article" | "div" | "section";
|
|
14
|
-
/** The text; the snippet's demo article when empty. */
|
|
15
|
-
children?: ReactNode;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** `p.oc-prose-lead`: larger intro paragraph. */
|
|
19
|
-
export function ProseLead({ className, ...rest }: HTMLAttributes<HTMLParagraphElement>) {
|
|
20
|
-
return <p className={cx("oc-prose-lead", className)} {...rest} />;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const DEMO = (
|
|
24
|
-
<>
|
|
25
|
-
<h1>Planning a kitchen renovation</h1>
|
|
26
|
-
<ProseLead>A new kitchen takes eight to twelve weeks from the first visit to the last cabinet handle. This guide explains every step, so you know what to expect and when we need you.</ProseLead>
|
|
27
|
-
<h2>Before the first visit</h2>
|
|
28
|
-
<p>Collect a few photos of kitchens you like and measure the room roughly. Exact measurements are not needed yet: our site manager does that during the visit. Read the <a href="#checklist">preparation checklist</a> if you want to be fully ready.</p>
|
|
29
|
-
<ul>
|
|
30
|
-
<li>The <strong>budget range</strong> you have in mind, including appliances</li>
|
|
31
|
-
<li>Which walls, doors and windows can move, and which cannot
|
|
32
|
-
<ul><li>Load-bearing walls need a structural engineer</li><li>Moving a window needs a permit in most towns</li></ul>
|
|
33
|
-
</li>
|
|
34
|
-
<li>Appliances you want to keep</li>
|
|
35
|
-
</ul>
|
|
36
|
-
<figure>
|
|
37
|
-
<div className="oc-media-art oc-media-art--6" role="img" aria-label="Kitchen with oak fronts and a light stone worktop" />
|
|
38
|
-
<figcaption>Villa Dewitte: oak fronts with a Calacatta matt worktop, finished in October 2026.</figcaption>
|
|
39
|
-
</figure>
|
|
40
|
-
<h3>From design to installation</h3>
|
|
41
|
-
<ol>
|
|
42
|
-
<li>Site visit and measurements (week 1)</li>
|
|
43
|
-
<li>Design and quote, with two revision rounds (weeks 2–3)</li>
|
|
44
|
-
<li>Order and production of the cabinets (weeks 4–9)</li>
|
|
45
|
-
<li>Installation, worktop and finishing (weeks 10–11)</li>
|
|
46
|
-
</ol>
|
|
47
|
-
<blockquote>
|
|
48
|
-
<p>The planning was clear from day one. We always knew who was coming and when, and the kitchen was ready a week early.</p>
|
|
49
|
-
<footer>— Ann Dewitte, client in Ghent</footer>
|
|
50
|
-
</blockquote>
|
|
51
|
-
<h2>Worktop materials compared</h2>
|
|
52
|
-
<table>
|
|
53
|
-
<caption>Prices per running metre, 60 cm deep, installed.</caption>
|
|
54
|
-
<thead><tr><th>Material</th><th>Thickness</th><th>Heat resistant</th><th>Price</th></tr></thead>
|
|
55
|
-
<tbody>
|
|
56
|
-
<tr><td>Compact laminate</td><td>12 mm</td><td>Up to 180 °C</td><td>€ 290</td></tr>
|
|
57
|
-
<tr><td>Quartz composite</td><td>20 mm</td><td>Up to 150 °C</td><td>€ 540</td></tr>
|
|
58
|
-
<tr><td>Natural stone</td><td>20 mm</td><td>Yes</td><td>€ 720</td></tr>
|
|
59
|
-
</tbody>
|
|
60
|
-
</table>
|
|
61
|
-
<h3>Sharing your floor plan</h3>
|
|
62
|
-
<p>Upload a PDF or a photo in the client portal, or send it by e-mail. Press <kbd>Ctrl</kbd> + <kbd>V</kbd> in the chat to paste a screenshot. Files are stored as <code>project-id/plans/</code> and only your project team can see them.</p>
|
|
63
|
-
<pre><code>{"Project: 2026-114 Villa Dewitte\nRoom: Kitchen, ground floor\nWall A: 412 cm (window at 160 cm)\nWall B: 298 cm (door at the left)"}</code></pre>
|
|
64
|
-
<hr />
|
|
65
|
-
<p><small>Lead times are an estimate. <mark>Natural stone</mark> can take two weeks longer in summer.</small></p>
|
|
66
|
-
</>
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
/** `.oc-prose`: typography for long-form text written without classes. */
|
|
70
|
-
export const Prose = forwardRef<HTMLElement, ProseProps>(function Prose(
|
|
71
|
-
{ size = "md", full, as: Tag = "article", className, children, ...rest },
|
|
72
|
-
ref,
|
|
73
|
-
) {
|
|
74
|
-
return (
|
|
75
|
-
<Tag ref={ref as React.Ref<HTMLElement & HTMLDivElement>} className={cx("oc-prose", size !== "md" && `oc-prose--${size}`, full && "oc-prose--full", className)} {...rest}>
|
|
76
|
-
{children ?? DEMO}
|
|
77
|
-
</Tag>
|
|
78
|
-
);
|
|
79
|
-
});
|
|
1
|
+
// File: packages/react/src/components/Prose.tsx
|
|
2
|
+
// Map: types -> demo article (same text as the snippet) -> Prose (wrapper with size modifiers) -> ProseLead
|
|
3
|
+
// Same markup as packages/ui/snippets/prose.html. Put plain HTML (h1-h4, p, lists, blockquote, table, figure, pre) inside.
|
|
4
|
+
import { forwardRef } from "react";
|
|
5
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
6
|
+
import { cx } from "../utils";
|
|
7
|
+
|
|
8
|
+
export interface ProseProps extends HTMLAttributes<HTMLElement> {
|
|
9
|
+
/** All spacing scales along. */
|
|
10
|
+
size?: "sm" | "md" | "lg";
|
|
11
|
+
/** No max width (default 68ch). */
|
|
12
|
+
full?: boolean;
|
|
13
|
+
as?: "article" | "div" | "section";
|
|
14
|
+
/** The text; the snippet's demo article when empty. */
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** `p.oc-prose-lead`: larger intro paragraph. */
|
|
19
|
+
export function ProseLead({ className, ...rest }: HTMLAttributes<HTMLParagraphElement>) {
|
|
20
|
+
return <p className={cx("oc-prose-lead", className)} {...rest} />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const DEMO = (
|
|
24
|
+
<>
|
|
25
|
+
<h1>Planning a kitchen renovation</h1>
|
|
26
|
+
<ProseLead>A new kitchen takes eight to twelve weeks from the first visit to the last cabinet handle. This guide explains every step, so you know what to expect and when we need you.</ProseLead>
|
|
27
|
+
<h2>Before the first visit</h2>
|
|
28
|
+
<p>Collect a few photos of kitchens you like and measure the room roughly. Exact measurements are not needed yet: our site manager does that during the visit. Read the <a href="#checklist">preparation checklist</a> if you want to be fully ready.</p>
|
|
29
|
+
<ul>
|
|
30
|
+
<li>The <strong>budget range</strong> you have in mind, including appliances</li>
|
|
31
|
+
<li>Which walls, doors and windows can move, and which cannot
|
|
32
|
+
<ul><li>Load-bearing walls need a structural engineer</li><li>Moving a window needs a permit in most towns</li></ul>
|
|
33
|
+
</li>
|
|
34
|
+
<li>Appliances you want to keep</li>
|
|
35
|
+
</ul>
|
|
36
|
+
<figure>
|
|
37
|
+
<div className="oc-media-art oc-media-art--6" role="img" aria-label="Kitchen with oak fronts and a light stone worktop" />
|
|
38
|
+
<figcaption>Villa Dewitte: oak fronts with a Calacatta matt worktop, finished in October 2026.</figcaption>
|
|
39
|
+
</figure>
|
|
40
|
+
<h3>From design to installation</h3>
|
|
41
|
+
<ol>
|
|
42
|
+
<li>Site visit and measurements (week 1)</li>
|
|
43
|
+
<li>Design and quote, with two revision rounds (weeks 2–3)</li>
|
|
44
|
+
<li>Order and production of the cabinets (weeks 4–9)</li>
|
|
45
|
+
<li>Installation, worktop and finishing (weeks 10–11)</li>
|
|
46
|
+
</ol>
|
|
47
|
+
<blockquote>
|
|
48
|
+
<p>The planning was clear from day one. We always knew who was coming and when, and the kitchen was ready a week early.</p>
|
|
49
|
+
<footer>— Ann Dewitte, client in Ghent</footer>
|
|
50
|
+
</blockquote>
|
|
51
|
+
<h2>Worktop materials compared</h2>
|
|
52
|
+
<table>
|
|
53
|
+
<caption>Prices per running metre, 60 cm deep, installed.</caption>
|
|
54
|
+
<thead><tr><th>Material</th><th>Thickness</th><th>Heat resistant</th><th>Price</th></tr></thead>
|
|
55
|
+
<tbody>
|
|
56
|
+
<tr><td>Compact laminate</td><td>12 mm</td><td>Up to 180 °C</td><td>€ 290</td></tr>
|
|
57
|
+
<tr><td>Quartz composite</td><td>20 mm</td><td>Up to 150 °C</td><td>€ 540</td></tr>
|
|
58
|
+
<tr><td>Natural stone</td><td>20 mm</td><td>Yes</td><td>€ 720</td></tr>
|
|
59
|
+
</tbody>
|
|
60
|
+
</table>
|
|
61
|
+
<h3>Sharing your floor plan</h3>
|
|
62
|
+
<p>Upload a PDF or a photo in the client portal, or send it by e-mail. Press <kbd>Ctrl</kbd> + <kbd>V</kbd> in the chat to paste a screenshot. Files are stored as <code>project-id/plans/</code> and only your project team can see them.</p>
|
|
63
|
+
<pre><code>{"Project: 2026-114 Villa Dewitte\nRoom: Kitchen, ground floor\nWall A: 412 cm (window at 160 cm)\nWall B: 298 cm (door at the left)"}</code></pre>
|
|
64
|
+
<hr />
|
|
65
|
+
<p><small>Lead times are an estimate. <mark>Natural stone</mark> can take two weeks longer in summer.</small></p>
|
|
66
|
+
</>
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
/** `.oc-prose`: typography for long-form text written without classes. */
|
|
70
|
+
export const Prose = forwardRef<HTMLElement, ProseProps>(function Prose(
|
|
71
|
+
{ size = "md", full, as: Tag = "article", className, children, ...rest },
|
|
72
|
+
ref,
|
|
73
|
+
) {
|
|
74
|
+
return (
|
|
75
|
+
<Tag ref={ref as React.Ref<HTMLElement & HTMLDivElement>} className={cx("oc-prose", size !== "md" && `oc-prose--${size}`, full && "oc-prose--full", className)} {...rest}>
|
|
76
|
+
{children ?? DEMO}
|
|
77
|
+
</Tag>
|
|
78
|
+
);
|
|
79
|
+
});
|
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { forwardRef } from "react";
|
|
4
|
-
import type { HTMLAttributes, ReactNode } from "react";
|
|
5
|
-
import { cx } from "../utils";
|
|
6
|
-
|
|
7
|
-
export interface RecoveryCodesProps extends Omit<HTMLAttributes<HTMLDivElement>, "title" | "onCopy"> {
|
|
8
|
-
codes: string[];
|
|
9
|
-
title?: ReactNode;
|
|
10
|
-
warning?: ReactNode;
|
|
11
|
-
copyLabel?: string;
|
|
12
|
-
downloadLabel?: string;
|
|
13
|
-
/** File name of the download (default `recovery-codes.txt`). */
|
|
14
|
-
fileName?: string;
|
|
15
|
-
onCopy?: (codes: string[]) => void;
|
|
16
|
-
onDownload?: (codes: string[]) => void;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** `.oc-recovery-codes`: warning panel with a code grid, "Copy all" and "Download .txt". */
|
|
20
|
-
export const RecoveryCodes = forwardRef<HTMLDivElement, RecoveryCodesProps>(function RecoveryCodes(
|
|
21
|
-
{
|
|
22
|
-
codes,
|
|
23
|
-
title = "Recovery codes",
|
|
24
|
-
warning = "Store these codes somewhere safe. Each code works once.",
|
|
25
|
-
copyLabel = "Copy all",
|
|
26
|
-
downloadLabel = "Download .txt",
|
|
27
|
-
fileName = "recovery-codes.txt",
|
|
28
|
-
onCopy,
|
|
29
|
-
onDownload,
|
|
30
|
-
className,
|
|
31
|
-
...rest
|
|
32
|
-
},
|
|
33
|
-
ref,
|
|
34
|
-
) {
|
|
35
|
-
const copy = async () => {
|
|
36
|
-
try {
|
|
37
|
-
await navigator.clipboard.writeText(codes.join("\n"));
|
|
38
|
-
onCopy?.(codes);
|
|
39
|
-
} catch {
|
|
40
|
-
/* clipboard unavailable */
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
const download = () => {
|
|
44
|
-
const url = URL.createObjectURL(new Blob([codes.join("\n") + "\n"], { type: "text/plain" }));
|
|
45
|
-
const a = document.createElement("a");
|
|
46
|
-
a.href = url;
|
|
47
|
-
a.download = fileName;
|
|
48
|
-
a.click();
|
|
49
|
-
URL.revokeObjectURL(url);
|
|
50
|
-
onDownload?.(codes);
|
|
51
|
-
};
|
|
52
|
-
return (
|
|
53
|
-
<div ref={ref} className={cx("oc-recovery-codes", className)} {...rest}>
|
|
54
|
-
<h3 className="oc-recovery-codes-title">{title}</h3>
|
|
55
|
-
<p className="oc-recovery-codes-warning">{warning}</p>
|
|
56
|
-
<ul className="oc-recovery-codes-list">
|
|
57
|
-
{codes.map((code) => <li key={code}>{code}</li>)}
|
|
58
|
-
</ul>
|
|
59
|
-
<div className="oc-recovery-codes-actions">
|
|
60
|
-
<button className="oc-btn oc-btn--outline oc-btn--sm" type="button" onClick={copy}>{copyLabel}</button>
|
|
61
|
-
<button className="oc-btn oc-btn--outline oc-btn--sm" type="button" onClick={download}>{downloadLabel}</button>
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
);
|
|
65
|
-
});
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { forwardRef } from "react";
|
|
4
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
5
|
+
import { cx } from "../utils";
|
|
6
|
+
|
|
7
|
+
export interface RecoveryCodesProps extends Omit<HTMLAttributes<HTMLDivElement>, "title" | "onCopy"> {
|
|
8
|
+
codes: string[];
|
|
9
|
+
title?: ReactNode;
|
|
10
|
+
warning?: ReactNode;
|
|
11
|
+
copyLabel?: string;
|
|
12
|
+
downloadLabel?: string;
|
|
13
|
+
/** File name of the download (default `recovery-codes.txt`). */
|
|
14
|
+
fileName?: string;
|
|
15
|
+
onCopy?: (codes: string[]) => void;
|
|
16
|
+
onDownload?: (codes: string[]) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** `.oc-recovery-codes`: warning panel with a code grid, "Copy all" and "Download .txt". */
|
|
20
|
+
export const RecoveryCodes = forwardRef<HTMLDivElement, RecoveryCodesProps>(function RecoveryCodes(
|
|
21
|
+
{
|
|
22
|
+
codes,
|
|
23
|
+
title = "Recovery codes",
|
|
24
|
+
warning = "Store these codes somewhere safe. Each code works once.",
|
|
25
|
+
copyLabel = "Copy all",
|
|
26
|
+
downloadLabel = "Download .txt",
|
|
27
|
+
fileName = "recovery-codes.txt",
|
|
28
|
+
onCopy,
|
|
29
|
+
onDownload,
|
|
30
|
+
className,
|
|
31
|
+
...rest
|
|
32
|
+
},
|
|
33
|
+
ref,
|
|
34
|
+
) {
|
|
35
|
+
const copy = async () => {
|
|
36
|
+
try {
|
|
37
|
+
await navigator.clipboard.writeText(codes.join("\n"));
|
|
38
|
+
onCopy?.(codes);
|
|
39
|
+
} catch {
|
|
40
|
+
/* clipboard unavailable */
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const download = () => {
|
|
44
|
+
const url = URL.createObjectURL(new Blob([codes.join("\n") + "\n"], { type: "text/plain" }));
|
|
45
|
+
const a = document.createElement("a");
|
|
46
|
+
a.href = url;
|
|
47
|
+
a.download = fileName;
|
|
48
|
+
a.click();
|
|
49
|
+
URL.revokeObjectURL(url);
|
|
50
|
+
onDownload?.(codes);
|
|
51
|
+
};
|
|
52
|
+
return (
|
|
53
|
+
<div ref={ref} className={cx("oc-recovery-codes", className)} {...rest}>
|
|
54
|
+
<h3 className="oc-recovery-codes-title">{title}</h3>
|
|
55
|
+
<p className="oc-recovery-codes-warning">{warning}</p>
|
|
56
|
+
<ul className="oc-recovery-codes-list">
|
|
57
|
+
{codes.map((code) => <li key={code}>{code}</li>)}
|
|
58
|
+
</ul>
|
|
59
|
+
<div className="oc-recovery-codes-actions">
|
|
60
|
+
<button className="oc-btn oc-btn--outline oc-btn--sm" type="button" onClick={copy}>{copyLabel}</button>
|
|
61
|
+
<button className="oc-btn oc-btn--outline oc-btn--sm" type="button" onClick={download}>{downloadLabel}</button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
});
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { forwardRef, useId } from "react";
|
|
4
|
-
import type { HTMLAttributes, ReactNode } from "react";
|
|
5
|
-
import { cx } from "../utils";
|
|
6
|
-
import { useControllable } from "../hooks";
|
|
7
|
-
|
|
8
|
-
export interface SegmentedOption {
|
|
9
|
-
value: string;
|
|
10
|
-
label: ReactNode;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface SegmentedProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> {
|
|
14
|
-
options: SegmentedOption[];
|
|
15
|
-
value?: string;
|
|
16
|
-
defaultValue?: string;
|
|
17
|
-
onChange?: (value: string) => void;
|
|
18
|
-
/** Radio group name (generated when omitted). */
|
|
19
|
-
name?: string;
|
|
20
|
-
/** Full width. */
|
|
21
|
-
block?: boolean;
|
|
22
|
-
size?: "sm" | "md";
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** `.oc-segmented` radio group as a pill switcher. */
|
|
26
|
-
export const Segmented = forwardRef<HTMLDivElement, SegmentedProps>(function Segmented(
|
|
27
|
-
{ options, value, defaultValue, onChange, name, block, size = "md", className, ...rest },
|
|
28
|
-
ref,
|
|
29
|
-
) {
|
|
30
|
-
const auto = useId();
|
|
31
|
-
const group = name ?? `oc-segmented-${auto.replace(/:/g, "")}`;
|
|
32
|
-
const [current, setCurrent] = useControllable<string>(value, defaultValue ?? options[0]?.value ?? "", onChange);
|
|
33
|
-
return (
|
|
34
|
-
<div
|
|
35
|
-
ref={ref}
|
|
36
|
-
className={cx("oc-segmented", block && "oc-segmented--block", size === "sm" && "oc-segmented--sm", className)}
|
|
37
|
-
role="radiogroup"
|
|
38
|
-
{...rest}
|
|
39
|
-
>
|
|
40
|
-
{options.map((o) => (
|
|
41
|
-
<label className="oc-segmented-item" key={o.value}>
|
|
42
|
-
<input type="radio" name={group} value={o.value} checked={current === o.value} onChange={() => setCurrent(o.value)} /> {o.label}
|
|
43
|
-
</label>
|
|
44
|
-
))}
|
|
45
|
-
</div>
|
|
46
|
-
);
|
|
47
|
-
});
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { forwardRef, useId } from "react";
|
|
4
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
5
|
+
import { cx } from "../utils";
|
|
6
|
+
import { useControllable } from "../hooks";
|
|
7
|
+
|
|
8
|
+
export interface SegmentedOption {
|
|
9
|
+
value: string;
|
|
10
|
+
label: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SegmentedProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> {
|
|
14
|
+
options: SegmentedOption[];
|
|
15
|
+
value?: string;
|
|
16
|
+
defaultValue?: string;
|
|
17
|
+
onChange?: (value: string) => void;
|
|
18
|
+
/** Radio group name (generated when omitted). */
|
|
19
|
+
name?: string;
|
|
20
|
+
/** Full width. */
|
|
21
|
+
block?: boolean;
|
|
22
|
+
size?: "sm" | "md";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** `.oc-segmented` radio group as a pill switcher. */
|
|
26
|
+
export const Segmented = forwardRef<HTMLDivElement, SegmentedProps>(function Segmented(
|
|
27
|
+
{ options, value, defaultValue, onChange, name, block, size = "md", className, ...rest },
|
|
28
|
+
ref,
|
|
29
|
+
) {
|
|
30
|
+
const auto = useId();
|
|
31
|
+
const group = name ?? `oc-segmented-${auto.replace(/:/g, "")}`;
|
|
32
|
+
const [current, setCurrent] = useControllable<string>(value, defaultValue ?? options[0]?.value ?? "", onChange);
|
|
33
|
+
return (
|
|
34
|
+
<div
|
|
35
|
+
ref={ref}
|
|
36
|
+
className={cx("oc-segmented", block && "oc-segmented--block", size === "sm" && "oc-segmented--sm", className)}
|
|
37
|
+
role="radiogroup"
|
|
38
|
+
{...rest}
|
|
39
|
+
>
|
|
40
|
+
{options.map((o) => (
|
|
41
|
+
<label className="oc-segmented-item" key={o.value}>
|
|
42
|
+
<input type="radio" name={group} value={o.value} checked={current === o.value} onChange={() => setCurrent(o.value)} /> {o.label}
|
|
43
|
+
</label>
|
|
44
|
+
))}
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
});
|