@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,62 +1,62 @@
|
|
|
1
|
-
import { forwardRef, useId } from "react";
|
|
2
|
-
import type { AnchorHTMLAttributes, HTMLAttributes, LiHTMLAttributes, ReactNode } from "react";
|
|
3
|
-
import { attrs, cx } from "../utils";
|
|
4
|
-
|
|
5
|
-
export interface NavmenuProps extends HTMLAttributes<HTMLUListElement> {
|
|
6
|
-
/** `aria-label` of the wrapping <nav>. */
|
|
7
|
-
label?: string;
|
|
8
|
-
/** Class for the wrapping <nav> (e.g. `oc-block-appbar-nav`). */
|
|
9
|
-
navClassName?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/** `<nav><ul class="oc-navmenu">`: horizontal navigation links. */
|
|
13
|
-
export const Navmenu = forwardRef<HTMLUListElement, NavmenuProps>(function Navmenu(
|
|
14
|
-
{ label = "Navigation", navClassName, className, ...rest },
|
|
15
|
-
ref,
|
|
16
|
-
) {
|
|
17
|
-
return (
|
|
18
|
-
<nav aria-label={label} className={navClassName}>
|
|
19
|
-
<ul ref={ref} className={cx("oc-navmenu", className)} {...rest} />
|
|
20
|
-
</nav>
|
|
21
|
-
);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
/** `<li>` of a Navmenu. */
|
|
25
|
-
export function NavmenuItem(props: LiHTMLAttributes<HTMLLIElement>) {
|
|
26
|
-
return <li {...props} />;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface NavmenuLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
30
|
-
/** Current page (`aria-current="page"`). */
|
|
31
|
-
current?: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** `<a class="oc-navmenu-link">` */
|
|
35
|
-
export const NavmenuLink = forwardRef<HTMLAnchorElement, NavmenuLinkProps>(function NavmenuLink(
|
|
36
|
-
{ current, className, ...rest },
|
|
37
|
-
ref,
|
|
38
|
-
) {
|
|
39
|
-
return <a ref={ref} className={cx("oc-navmenu-link", className)} aria-current={current ? "page" : undefined} {...rest} />;
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
export interface NavmenuDropdownProps extends HTMLAttributes<HTMLDivElement> {
|
|
43
|
-
/** Trigger text (button with a chevron). */
|
|
44
|
-
label: ReactNode;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** `<li>` with a caret trigger button and a `.oc-dropdown-menu[popover]` of children. */
|
|
48
|
-
export const NavmenuDropdown = forwardRef<HTMLDivElement, NavmenuDropdownProps>(function NavmenuDropdown(
|
|
49
|
-
{ label, className, id, children, ...rest },
|
|
50
|
-
ref,
|
|
51
|
-
) {
|
|
52
|
-
const auto = useId();
|
|
53
|
-
const menuId = id ?? `oc-navmenu-${auto.replace(/:/g, "")}`;
|
|
54
|
-
return (
|
|
55
|
-
<li>
|
|
56
|
-
<button className="oc-navmenu-link oc-navmenu-link--caret" type="button" {...attrs({ popovertarget: menuId })}>{label}</button>
|
|
57
|
-
<div ref={ref} className={cx("oc-dropdown-menu", className)} id={menuId} {...attrs({ popover: "" })} {...rest}>
|
|
58
|
-
{children}
|
|
59
|
-
</div>
|
|
60
|
-
</li>
|
|
61
|
-
);
|
|
62
|
-
});
|
|
1
|
+
import { forwardRef, useId } from "react";
|
|
2
|
+
import type { AnchorHTMLAttributes, HTMLAttributes, LiHTMLAttributes, ReactNode } from "react";
|
|
3
|
+
import { attrs, cx } from "../utils";
|
|
4
|
+
|
|
5
|
+
export interface NavmenuProps extends HTMLAttributes<HTMLUListElement> {
|
|
6
|
+
/** `aria-label` of the wrapping <nav>. */
|
|
7
|
+
label?: string;
|
|
8
|
+
/** Class for the wrapping <nav> (e.g. `oc-block-appbar-nav`). */
|
|
9
|
+
navClassName?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** `<nav><ul class="oc-navmenu">`: horizontal navigation links. */
|
|
13
|
+
export const Navmenu = forwardRef<HTMLUListElement, NavmenuProps>(function Navmenu(
|
|
14
|
+
{ label = "Navigation", navClassName, className, ...rest },
|
|
15
|
+
ref,
|
|
16
|
+
) {
|
|
17
|
+
return (
|
|
18
|
+
<nav aria-label={label} className={navClassName}>
|
|
19
|
+
<ul ref={ref} className={cx("oc-navmenu", className)} {...rest} />
|
|
20
|
+
</nav>
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/** `<li>` of a Navmenu. */
|
|
25
|
+
export function NavmenuItem(props: LiHTMLAttributes<HTMLLIElement>) {
|
|
26
|
+
return <li {...props} />;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface NavmenuLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
30
|
+
/** Current page (`aria-current="page"`). */
|
|
31
|
+
current?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** `<a class="oc-navmenu-link">` */
|
|
35
|
+
export const NavmenuLink = forwardRef<HTMLAnchorElement, NavmenuLinkProps>(function NavmenuLink(
|
|
36
|
+
{ current, className, ...rest },
|
|
37
|
+
ref,
|
|
38
|
+
) {
|
|
39
|
+
return <a ref={ref} className={cx("oc-navmenu-link", className)} aria-current={current ? "page" : undefined} {...rest} />;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export interface NavmenuDropdownProps extends HTMLAttributes<HTMLDivElement> {
|
|
43
|
+
/** Trigger text (button with a chevron). */
|
|
44
|
+
label: ReactNode;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** `<li>` with a caret trigger button and a `.oc-dropdown-menu[popover]` of children. */
|
|
48
|
+
export const NavmenuDropdown = forwardRef<HTMLDivElement, NavmenuDropdownProps>(function NavmenuDropdown(
|
|
49
|
+
{ label, className, id, children, ...rest },
|
|
50
|
+
ref,
|
|
51
|
+
) {
|
|
52
|
+
const auto = useId();
|
|
53
|
+
const menuId = id ?? `oc-navmenu-${auto.replace(/:/g, "")}`;
|
|
54
|
+
return (
|
|
55
|
+
<li>
|
|
56
|
+
<button className="oc-navmenu-link oc-navmenu-link--caret" type="button" {...attrs({ popovertarget: menuId })}>{label}</button>
|
|
57
|
+
<div ref={ref} className={cx("oc-dropdown-menu", className)} id={menuId} {...attrs({ popover: "" })} {...rest}>
|
|
58
|
+
{children}
|
|
59
|
+
</div>
|
|
60
|
+
</li>
|
|
61
|
+
);
|
|
62
|
+
});
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
// File: packages/react/src/components/NextTeaser.tsx | Map: props -> NextTeaser (a -> label -> name (+ mark) -> tagline)
|
|
2
|
-
import { forwardRef } from "react";
|
|
3
|
-
import type { AnchorHTMLAttributes, ReactNode } from "react";
|
|
4
|
-
import { cx } from "../utils";
|
|
5
|
-
|
|
6
|
-
export interface NextTeaserProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
7
|
-
/** Small uppercase label (default "Next product"). */
|
|
8
|
-
label?: ReactNode;
|
|
9
|
-
name: ReactNode;
|
|
10
|
-
tagline?: ReactNode;
|
|
11
|
-
/** Element before the name, usually a <ProductMark size="sm" />. */
|
|
12
|
-
mark?: ReactNode;
|
|
13
|
-
/** "Previous" link: arrow left. */
|
|
14
|
-
prev?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** `<a class="oc-next-teaser">`: "Next product" teaser card. */
|
|
18
|
-
export const NextTeaser = forwardRef<HTMLAnchorElement, NextTeaserProps>(function NextTeaser(
|
|
19
|
-
{ label = "Next product", name, tagline, mark, prev = false, className, ...rest },
|
|
20
|
-
ref,
|
|
21
|
-
) {
|
|
22
|
-
return (
|
|
23
|
-
<a ref={ref} className={cx("oc-next-teaser", prev && "oc-next-teaser--prev", className)} {...rest}>
|
|
24
|
-
{label != null && <span className="oc-next-teaser__label">{label}</span>}
|
|
25
|
-
<span className="oc-next-teaser__name">{mark}{name}</span>
|
|
26
|
-
{tagline != null && <span className="oc-next-teaser__tagline">{tagline}</span>}
|
|
27
|
-
</a>
|
|
28
|
-
);
|
|
29
|
-
});
|
|
1
|
+
// File: packages/react/src/components/NextTeaser.tsx | Map: props -> NextTeaser (a -> label -> name (+ mark) -> tagline)
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import type { AnchorHTMLAttributes, ReactNode } from "react";
|
|
4
|
+
import { cx } from "../utils";
|
|
5
|
+
|
|
6
|
+
export interface NextTeaserProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
7
|
+
/** Small uppercase label (default "Next product"). */
|
|
8
|
+
label?: ReactNode;
|
|
9
|
+
name: ReactNode;
|
|
10
|
+
tagline?: ReactNode;
|
|
11
|
+
/** Element before the name, usually a <ProductMark size="sm" />. */
|
|
12
|
+
mark?: ReactNode;
|
|
13
|
+
/** "Previous" link: arrow left. */
|
|
14
|
+
prev?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** `<a class="oc-next-teaser">`: "Next product" teaser card. */
|
|
18
|
+
export const NextTeaser = forwardRef<HTMLAnchorElement, NextTeaserProps>(function NextTeaser(
|
|
19
|
+
{ label = "Next product", name, tagline, mark, prev = false, className, ...rest },
|
|
20
|
+
ref,
|
|
21
|
+
) {
|
|
22
|
+
return (
|
|
23
|
+
<a ref={ref} className={cx("oc-next-teaser", prev && "oc-next-teaser--prev", className)} {...rest}>
|
|
24
|
+
{label != null && <span className="oc-next-teaser__label">{label}</span>}
|
|
25
|
+
<span className="oc-next-teaser__name">{mark}{name}</span>
|
|
26
|
+
{tagline != null && <span className="oc-next-teaser__tagline">{tagline}</span>}
|
|
27
|
+
</a>
|
|
28
|
+
);
|
|
29
|
+
});
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
// File: packages/react/src/components/NumberInput.tsx
|
|
2
|
-
// Map: props -> clamp helper -> component (minus button, number field, plus button)
|
|
3
|
-
// Same markup as packages/ui/snippets/number-input.html. The buttons are handled here (no page script needed).
|
|
4
|
-
"use client";
|
|
5
|
-
|
|
6
|
-
import { forwardRef } from "react";
|
|
7
|
-
import type { InputHTMLAttributes } from "react";
|
|
8
|
-
import { cx } from "../utils";
|
|
9
|
-
import { useControllable } from "../hooks";
|
|
10
|
-
|
|
11
|
-
export interface NumberInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "size" | "type" | "min" | "max" | "step"> {
|
|
12
|
-
/** Current number; null = empty field. */
|
|
13
|
-
value?: number | null;
|
|
14
|
-
defaultValue?: number | null;
|
|
15
|
-
onChange?: (value: number | null) => void;
|
|
16
|
-
min?: number;
|
|
17
|
-
max?: number;
|
|
18
|
-
step?: number;
|
|
19
|
-
size?: "sm" | "md";
|
|
20
|
-
/** Full width. */
|
|
21
|
-
block?: boolean;
|
|
22
|
-
error?: boolean;
|
|
23
|
-
decrementLabel?: string;
|
|
24
|
-
incrementLabel?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Keep a number inside min/max and drop float noise (0.1 + 0.2). */
|
|
28
|
-
function clamp(n: number, min?: number, max?: number) {
|
|
29
|
-
const rounded = Math.round(n * 1e6) / 1e6;
|
|
30
|
-
return Math.min(max ?? Infinity, Math.max(min ?? -Infinity, rounded));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** `.oc-number-input`: number field with - / + buttons. Buttons disable themselves at min / max. */
|
|
34
|
-
export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(function NumberInput(
|
|
35
|
-
{ value, defaultValue, onChange, min, max, step = 1, size = "md", block, error, decrementLabel = "Decrease", incrementLabel = "Increase", className, disabled, ...rest },
|
|
36
|
-
ref,
|
|
37
|
-
) {
|
|
38
|
-
const [current, setCurrent] = useControllable<number | null>(value, defaultValue ?? null, onChange);
|
|
39
|
-
const stepBy = (dir: 1 | -1) => setCurrent(clamp((current ?? min ?? 0) + dir * step, min, max));
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<div className={cx("oc-number-input", size === "sm" && "oc-number-input--sm", block && "oc-number-input--block", error && "oc-number-input--error", className)}>
|
|
43
|
-
<button className="oc-number-input-step" type="button" aria-label={decrementLabel} disabled={disabled || (min != null && current != null && current <= min)} onClick={() => stepBy(-1)}>
|
|
44
|
-
−
|
|
45
|
-
</button>
|
|
46
|
-
<input
|
|
47
|
-
ref={ref}
|
|
48
|
-
className="oc-number-input-field"
|
|
49
|
-
type="number"
|
|
50
|
-
min={min}
|
|
51
|
-
max={max}
|
|
52
|
-
step={step}
|
|
53
|
-
disabled={disabled}
|
|
54
|
-
aria-invalid={error ? true : undefined}
|
|
55
|
-
value={current ?? ""}
|
|
56
|
-
onChange={(e) => setCurrent(e.target.value === "" ? null : Number(e.target.value))}
|
|
57
|
-
{...rest}
|
|
58
|
-
/>
|
|
59
|
-
<button className="oc-number-input-step" type="button" aria-label={incrementLabel} disabled={disabled || (max != null && current != null && current >= max)} onClick={() => stepBy(1)}>
|
|
60
|
-
+
|
|
61
|
-
</button>
|
|
62
|
-
</div>
|
|
63
|
-
);
|
|
64
|
-
});
|
|
1
|
+
// File: packages/react/src/components/NumberInput.tsx
|
|
2
|
+
// Map: props -> clamp helper -> component (minus button, number field, plus button)
|
|
3
|
+
// Same markup as packages/ui/snippets/number-input.html. The buttons are handled here (no page script needed).
|
|
4
|
+
"use client";
|
|
5
|
+
|
|
6
|
+
import { forwardRef } from "react";
|
|
7
|
+
import type { InputHTMLAttributes } from "react";
|
|
8
|
+
import { cx } from "../utils";
|
|
9
|
+
import { useControllable } from "../hooks";
|
|
10
|
+
|
|
11
|
+
export interface NumberInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "size" | "type" | "min" | "max" | "step"> {
|
|
12
|
+
/** Current number; null = empty field. */
|
|
13
|
+
value?: number | null;
|
|
14
|
+
defaultValue?: number | null;
|
|
15
|
+
onChange?: (value: number | null) => void;
|
|
16
|
+
min?: number;
|
|
17
|
+
max?: number;
|
|
18
|
+
step?: number;
|
|
19
|
+
size?: "sm" | "md";
|
|
20
|
+
/** Full width. */
|
|
21
|
+
block?: boolean;
|
|
22
|
+
error?: boolean;
|
|
23
|
+
decrementLabel?: string;
|
|
24
|
+
incrementLabel?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Keep a number inside min/max and drop float noise (0.1 + 0.2). */
|
|
28
|
+
function clamp(n: number, min?: number, max?: number) {
|
|
29
|
+
const rounded = Math.round(n * 1e6) / 1e6;
|
|
30
|
+
return Math.min(max ?? Infinity, Math.max(min ?? -Infinity, rounded));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** `.oc-number-input`: number field with - / + buttons. Buttons disable themselves at min / max. */
|
|
34
|
+
export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(function NumberInput(
|
|
35
|
+
{ value, defaultValue, onChange, min, max, step = 1, size = "md", block, error, decrementLabel = "Decrease", incrementLabel = "Increase", className, disabled, ...rest },
|
|
36
|
+
ref,
|
|
37
|
+
) {
|
|
38
|
+
const [current, setCurrent] = useControllable<number | null>(value, defaultValue ?? null, onChange);
|
|
39
|
+
const stepBy = (dir: 1 | -1) => setCurrent(clamp((current ?? min ?? 0) + dir * step, min, max));
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className={cx("oc-number-input", size === "sm" && "oc-number-input--sm", block && "oc-number-input--block", error && "oc-number-input--error", className)}>
|
|
43
|
+
<button className="oc-number-input-step" type="button" aria-label={decrementLabel} disabled={disabled || (min != null && current != null && current <= min)} onClick={() => stepBy(-1)}>
|
|
44
|
+
−
|
|
45
|
+
</button>
|
|
46
|
+
<input
|
|
47
|
+
ref={ref}
|
|
48
|
+
className="oc-number-input-field"
|
|
49
|
+
type="number"
|
|
50
|
+
min={min}
|
|
51
|
+
max={max}
|
|
52
|
+
step={step}
|
|
53
|
+
disabled={disabled}
|
|
54
|
+
aria-invalid={error ? true : undefined}
|
|
55
|
+
value={current ?? ""}
|
|
56
|
+
onChange={(e) => setCurrent(e.target.value === "" ? null : Number(e.target.value))}
|
|
57
|
+
{...rest}
|
|
58
|
+
/>
|
|
59
|
+
<button className="oc-number-input-step" type="button" aria-label={incrementLabel} disabled={disabled || (max != null && current != null && current >= max)} onClick={() => stepBy(1)}>
|
|
60
|
+
+
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
});
|
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { forwardRef } from "react";
|
|
4
|
-
import type { CSSProperties, InputHTMLAttributes } from "react";
|
|
5
|
-
import { cx } from "../utils";
|
|
6
|
-
import { useControllable } from "../hooks";
|
|
7
|
-
|
|
8
|
-
export interface OtpInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "type" | "maxLength"> {
|
|
9
|
-
/** Code length (default 6). */
|
|
10
|
-
length?: number;
|
|
11
|
-
value?: string;
|
|
12
|
-
defaultValue?: string;
|
|
13
|
-
onChange?: (value: string) => void;
|
|
14
|
-
/** Called once when `length` characters have been entered. */
|
|
15
|
-
onComplete?: (value: string) => void;
|
|
16
|
-
/** Error state (`aria-invalid`). */
|
|
17
|
-
invalid?: boolean;
|
|
18
|
-
/** Allow letters as well as digits. */
|
|
19
|
-
alphanumeric?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** `<input class="oc-otp">`: separate cells drawn on a single real input. */
|
|
23
|
-
export const OtpInput = forwardRef<HTMLInputElement, OtpInputProps>(function OtpInput(
|
|
24
|
-
{ length = 6, value, defaultValue = "", onChange, onComplete, invalid, alphanumeric, className, style, ...rest },
|
|
25
|
-
ref,
|
|
26
|
-
) {
|
|
27
|
-
const [code, setCode] = useControllable<string>(value, defaultValue, onChange);
|
|
28
|
-
return (
|
|
29
|
-
<input
|
|
30
|
-
ref={ref}
|
|
31
|
-
className={cx("oc-otp", className)}
|
|
32
|
-
type="text"
|
|
33
|
-
inputMode={alphanumeric ? "text" : "numeric"}
|
|
34
|
-
autoComplete="one-time-code"
|
|
35
|
-
maxLength={length}
|
|
36
|
-
pattern={alphanumeric ? undefined : "[0-9]*"}
|
|
37
|
-
style={{ "--oc-otp-length": length, ...style } as CSSProperties}
|
|
38
|
-
aria-invalid={invalid ? true : undefined}
|
|
39
|
-
value={code}
|
|
40
|
-
onChange={(e) => {
|
|
41
|
-
const next = alphanumeric ? e.target.value : e.target.value.replace(/\D/g, "");
|
|
42
|
-
setCode(next);
|
|
43
|
-
if (next.length === length) onComplete?.(next);
|
|
44
|
-
}}
|
|
45
|
-
{...rest}
|
|
46
|
-
/>
|
|
47
|
-
);
|
|
48
|
-
});
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { forwardRef } from "react";
|
|
4
|
+
import type { CSSProperties, InputHTMLAttributes } from "react";
|
|
5
|
+
import { cx } from "../utils";
|
|
6
|
+
import { useControllable } from "../hooks";
|
|
7
|
+
|
|
8
|
+
export interface OtpInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "type" | "maxLength"> {
|
|
9
|
+
/** Code length (default 6). */
|
|
10
|
+
length?: number;
|
|
11
|
+
value?: string;
|
|
12
|
+
defaultValue?: string;
|
|
13
|
+
onChange?: (value: string) => void;
|
|
14
|
+
/** Called once when `length` characters have been entered. */
|
|
15
|
+
onComplete?: (value: string) => void;
|
|
16
|
+
/** Error state (`aria-invalid`). */
|
|
17
|
+
invalid?: boolean;
|
|
18
|
+
/** Allow letters as well as digits. */
|
|
19
|
+
alphanumeric?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** `<input class="oc-otp">`: separate cells drawn on a single real input. */
|
|
23
|
+
export const OtpInput = forwardRef<HTMLInputElement, OtpInputProps>(function OtpInput(
|
|
24
|
+
{ length = 6, value, defaultValue = "", onChange, onComplete, invalid, alphanumeric, className, style, ...rest },
|
|
25
|
+
ref,
|
|
26
|
+
) {
|
|
27
|
+
const [code, setCode] = useControllable<string>(value, defaultValue, onChange);
|
|
28
|
+
return (
|
|
29
|
+
<input
|
|
30
|
+
ref={ref}
|
|
31
|
+
className={cx("oc-otp", className)}
|
|
32
|
+
type="text"
|
|
33
|
+
inputMode={alphanumeric ? "text" : "numeric"}
|
|
34
|
+
autoComplete="one-time-code"
|
|
35
|
+
maxLength={length}
|
|
36
|
+
pattern={alphanumeric ? undefined : "[0-9]*"}
|
|
37
|
+
style={{ "--oc-otp-length": length, ...style } as CSSProperties}
|
|
38
|
+
aria-invalid={invalid ? true : undefined}
|
|
39
|
+
value={code}
|
|
40
|
+
onChange={(e) => {
|
|
41
|
+
const next = alphanumeric ? e.target.value : e.target.value.replace(/\D/g, "");
|
|
42
|
+
setCode(next);
|
|
43
|
+
if (next.length === length) onComplete?.(next);
|
|
44
|
+
}}
|
|
45
|
+
{...rest}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
});
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
// File: packages/react/src/components/PageHeader.tsx
|
|
2
|
-
// Map: PageHeader = header -> back row -> main (text: title, subtitle, meta | actions)
|
|
3
|
-
// The header is a size container: the actions move next to the title from a 36rem header width.
|
|
4
|
-
import { forwardRef } from "react";
|
|
5
|
-
import type { HTMLAttributes, ReactNode } from "react";
|
|
6
|
-
import { cx } from "../utils";
|
|
7
|
-
|
|
8
|
-
export interface PageHeaderProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
|
|
9
|
-
title: ReactNode;
|
|
10
|
-
subtitle?: ReactNode;
|
|
11
|
-
/** Meta row items (owner, updated, ...). */
|
|
12
|
-
meta?: ReactNode;
|
|
13
|
-
/** Back link, e.g. `<BackLink href="/projects">Projects</BackLink>`. */
|
|
14
|
-
back?: ReactNode;
|
|
15
|
-
actions?: ReactNode;
|
|
16
|
-
/** Heading level of the title (default 1). */
|
|
17
|
-
level?: 1 | 2 | 3;
|
|
18
|
-
bordered?: boolean;
|
|
19
|
-
sticky?: boolean;
|
|
20
|
-
size?: "sm";
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** `.oc-page-header`: title row of an app page. */
|
|
24
|
-
export const PageHeader = forwardRef<HTMLElement, PageHeaderProps>(function PageHeader(
|
|
25
|
-
{ title, subtitle, meta, back, actions, level = 1, bordered, sticky, size, className, ...rest },
|
|
26
|
-
ref,
|
|
27
|
-
) {
|
|
28
|
-
const Heading = `h${level}` as "h1";
|
|
29
|
-
const cls = cx("oc-page-header", bordered && "oc-page-header--bordered", sticky && "oc-page-header--sticky", size && `oc-page-header--${size}`, className);
|
|
30
|
-
return (
|
|
31
|
-
<header ref={ref} className={cls} {...rest}>
|
|
32
|
-
{back != null && <div className="oc-page-header-back">{back}</div>}
|
|
33
|
-
<div className="oc-page-header-main">
|
|
34
|
-
<div className="oc-page-header-text">
|
|
35
|
-
<Heading className="oc-page-header-title">{title}</Heading>
|
|
36
|
-
{subtitle != null && <p className="oc-page-header-subtitle">{subtitle}</p>}
|
|
37
|
-
{meta != null && <div className="oc-page-header-meta">{meta}</div>}
|
|
38
|
-
</div>
|
|
39
|
-
{actions != null && <div className="oc-page-header-actions">{actions}</div>}
|
|
40
|
-
</div>
|
|
41
|
-
</header>
|
|
42
|
-
);
|
|
43
|
-
});
|
|
1
|
+
// File: packages/react/src/components/PageHeader.tsx
|
|
2
|
+
// Map: PageHeader = header -> back row -> main (text: title, subtitle, meta | actions)
|
|
3
|
+
// The header is a size container: the actions move next to the title from a 36rem header width.
|
|
4
|
+
import { forwardRef } from "react";
|
|
5
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
6
|
+
import { cx } from "../utils";
|
|
7
|
+
|
|
8
|
+
export interface PageHeaderProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
|
|
9
|
+
title: ReactNode;
|
|
10
|
+
subtitle?: ReactNode;
|
|
11
|
+
/** Meta row items (owner, updated, ...). */
|
|
12
|
+
meta?: ReactNode;
|
|
13
|
+
/** Back link, e.g. `<BackLink href="/projects">Projects</BackLink>`. */
|
|
14
|
+
back?: ReactNode;
|
|
15
|
+
actions?: ReactNode;
|
|
16
|
+
/** Heading level of the title (default 1). */
|
|
17
|
+
level?: 1 | 2 | 3;
|
|
18
|
+
bordered?: boolean;
|
|
19
|
+
sticky?: boolean;
|
|
20
|
+
size?: "sm";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** `.oc-page-header`: title row of an app page. */
|
|
24
|
+
export const PageHeader = forwardRef<HTMLElement, PageHeaderProps>(function PageHeader(
|
|
25
|
+
{ title, subtitle, meta, back, actions, level = 1, bordered, sticky, size, className, ...rest },
|
|
26
|
+
ref,
|
|
27
|
+
) {
|
|
28
|
+
const Heading = `h${level}` as "h1";
|
|
29
|
+
const cls = cx("oc-page-header", bordered && "oc-page-header--bordered", sticky && "oc-page-header--sticky", size && `oc-page-header--${size}`, className);
|
|
30
|
+
return (
|
|
31
|
+
<header ref={ref} className={cls} {...rest}>
|
|
32
|
+
{back != null && <div className="oc-page-header-back">{back}</div>}
|
|
33
|
+
<div className="oc-page-header-main">
|
|
34
|
+
<div className="oc-page-header-text">
|
|
35
|
+
<Heading className="oc-page-header-title">{title}</Heading>
|
|
36
|
+
{subtitle != null && <p className="oc-page-header-subtitle">{subtitle}</p>}
|
|
37
|
+
{meta != null && <div className="oc-page-header-meta">{meta}</div>}
|
|
38
|
+
</div>
|
|
39
|
+
{actions != null && <div className="oc-page-header-actions">{actions}</div>}
|
|
40
|
+
</div>
|
|
41
|
+
</header>
|
|
42
|
+
);
|
|
43
|
+
});
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import { forwardRef } from "react";
|
|
2
|
-
import type { HTMLAttributes } from "react";
|
|
3
|
-
import { cx } from "../utils";
|
|
4
|
-
|
|
5
|
-
export interface PaginationProps extends Omit<HTMLAttributes<HTMLElement>, "onChange"> {
|
|
6
|
-
/** Current page (1-based). */
|
|
7
|
-
page: number;
|
|
8
|
-
pageCount: number;
|
|
9
|
-
/** Called with the new page; when set, link navigation is prevented. */
|
|
10
|
-
onPageChange?: (page: number) => void;
|
|
11
|
-
/** Builds the href of a page (default `?page=n`). */
|
|
12
|
-
hrefFor?: (page: number) => string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** `<nav class="oc-pagination">` with numbered links and `aria-current="page"`. */
|
|
16
|
-
export const Pagination = forwardRef<HTMLElement, PaginationProps>(function Pagination(
|
|
17
|
-
{ page, pageCount, onPageChange, hrefFor = (n) => `?page=${n}`, className, "aria-label": ariaLabel = "Pagination", ...rest },
|
|
18
|
-
ref,
|
|
19
|
-
) {
|
|
20
|
-
return (
|
|
21
|
-
<nav ref={ref} className={cx("oc-pagination", className)} aria-label={ariaLabel} {...rest}>
|
|
22
|
-
{Array.from({ length: pageCount }, (_, i) => i + 1).map((n) => (
|
|
23
|
-
<a
|
|
24
|
-
key={n}
|
|
25
|
-
className="oc-pagination-item"
|
|
26
|
-
href={hrefFor(n)}
|
|
27
|
-
aria-current={n === page ? "page" : undefined}
|
|
28
|
-
onClick={
|
|
29
|
-
onPageChange
|
|
30
|
-
? (e) => {
|
|
31
|
-
e.preventDefault();
|
|
32
|
-
onPageChange(n);
|
|
33
|
-
}
|
|
34
|
-
: undefined
|
|
35
|
-
}
|
|
36
|
-
>
|
|
37
|
-
{n}
|
|
38
|
-
</a>
|
|
39
|
-
))}
|
|
40
|
-
</nav>
|
|
41
|
-
);
|
|
42
|
-
});
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import type { HTMLAttributes } from "react";
|
|
3
|
+
import { cx } from "../utils";
|
|
4
|
+
|
|
5
|
+
export interface PaginationProps extends Omit<HTMLAttributes<HTMLElement>, "onChange"> {
|
|
6
|
+
/** Current page (1-based). */
|
|
7
|
+
page: number;
|
|
8
|
+
pageCount: number;
|
|
9
|
+
/** Called with the new page; when set, link navigation is prevented. */
|
|
10
|
+
onPageChange?: (page: number) => void;
|
|
11
|
+
/** Builds the href of a page (default `?page=n`). */
|
|
12
|
+
hrefFor?: (page: number) => string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** `<nav class="oc-pagination">` with numbered links and `aria-current="page"`. */
|
|
16
|
+
export const Pagination = forwardRef<HTMLElement, PaginationProps>(function Pagination(
|
|
17
|
+
{ page, pageCount, onPageChange, hrefFor = (n) => `?page=${n}`, className, "aria-label": ariaLabel = "Pagination", ...rest },
|
|
18
|
+
ref,
|
|
19
|
+
) {
|
|
20
|
+
return (
|
|
21
|
+
<nav ref={ref} className={cx("oc-pagination", className)} aria-label={ariaLabel} {...rest}>
|
|
22
|
+
{Array.from({ length: pageCount }, (_, i) => i + 1).map((n) => (
|
|
23
|
+
<a
|
|
24
|
+
key={n}
|
|
25
|
+
className="oc-pagination-item"
|
|
26
|
+
href={hrefFor(n)}
|
|
27
|
+
aria-current={n === page ? "page" : undefined}
|
|
28
|
+
onClick={
|
|
29
|
+
onPageChange
|
|
30
|
+
? (e) => {
|
|
31
|
+
e.preventDefault();
|
|
32
|
+
onPageChange(n);
|
|
33
|
+
}
|
|
34
|
+
: undefined
|
|
35
|
+
}
|
|
36
|
+
>
|
|
37
|
+
{n}
|
|
38
|
+
</a>
|
|
39
|
+
))}
|
|
40
|
+
</nav>
|
|
41
|
+
);
|
|
42
|
+
});
|