@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,63 +1,63 @@
|
|
|
1
|
-
// File: packages/react/src/components/Greeting.tsx
|
|
2
|
-
// Map: greetingTimeOfDay() helper -> Greeting = header -> icon -> body (eyebrow, title + name, date) -> actions
|
|
3
|
-
// The time of day is computed from `date` (default: now). Morning 05-12, afternoon 12-18, evening otherwise.
|
|
4
|
-
import { forwardRef } from "react";
|
|
5
|
-
import type { HTMLAttributes, ReactNode } from "react";
|
|
6
|
-
import { cx } from "../utils";
|
|
7
|
-
|
|
8
|
-
export type GreetingTimeOfDay = "morning" | "afternoon" | "evening";
|
|
9
|
-
|
|
10
|
-
/** Pick the time-of-day modifier for a date. */
|
|
11
|
-
export function greetingTimeOfDay(date: Date = new Date()): GreetingTimeOfDay {
|
|
12
|
-
const h = date.getHours();
|
|
13
|
-
if (h >= 5 && h < 12) return "morning";
|
|
14
|
-
if (h >= 12 && h < 18) return "afternoon";
|
|
15
|
-
return "evening";
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const WORDS: Record<GreetingTimeOfDay, string> = { morning: "Good morning", afternoon: "Good afternoon", evening: "Good evening" };
|
|
19
|
-
|
|
20
|
-
export interface GreetingProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
|
|
21
|
-
name?: ReactNode;
|
|
22
|
-
/** The moment to greet for (default: now). Also shown as the date. */
|
|
23
|
-
date?: Date;
|
|
24
|
-
/** Force a time of day instead of computing it. */
|
|
25
|
-
timeOfDay?: GreetingTimeOfDay;
|
|
26
|
-
/** Translated greetings, e.g. `{ morning: "Goedemorgen", ... }`. */
|
|
27
|
-
words?: Partial<Record<GreetingTimeOfDay, string>>;
|
|
28
|
-
/** Locale for the date text (default: the browser locale). */
|
|
29
|
-
locale?: string;
|
|
30
|
-
/** Hide the date line. */
|
|
31
|
-
hideDate?: boolean;
|
|
32
|
-
eyebrow?: ReactNode;
|
|
33
|
-
icon?: ReactNode;
|
|
34
|
-
actions?: ReactNode;
|
|
35
|
-
size?: "lg";
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** `.oc-greeting`: dashboard greeting with date and actions. */
|
|
39
|
-
export const Greeting = forwardRef<HTMLElement, GreetingProps>(function Greeting(
|
|
40
|
-
{ name, date, timeOfDay, words, locale, hideDate, eyebrow, icon, actions, size, className, ...rest },
|
|
41
|
-
ref,
|
|
42
|
-
) {
|
|
43
|
-
const when = date ?? new Date();
|
|
44
|
-
const tod = timeOfDay ?? greetingTimeOfDay(when);
|
|
45
|
-
const word = words?.[tod] ?? WORDS[tod];
|
|
46
|
-
const dateText = new Intl.DateTimeFormat(locale, { weekday: "long", day: "numeric", month: "long", year: "numeric" }).format(when);
|
|
47
|
-
// datetime needs YYYY-MM-DD in local time (toISOString would use UTC).
|
|
48
|
-
const iso = `${when.getFullYear()}-${String(when.getMonth() + 1).padStart(2, "0")}-${String(when.getDate()).padStart(2, "0")}`;
|
|
49
|
-
return (
|
|
50
|
-
<header ref={ref} className={cx("oc-greeting", `oc-greeting--${tod}`, size && `oc-greeting--${size}`, className)} {...rest}>
|
|
51
|
-
{icon != null && <span className="oc-greeting-icon" aria-hidden="true">{icon}</span>}
|
|
52
|
-
<div className="oc-greeting-body">
|
|
53
|
-
{eyebrow != null && <p className="oc-greeting-eyebrow">{eyebrow}</p>}
|
|
54
|
-
<h2 className="oc-greeting-title">
|
|
55
|
-
{word}
|
|
56
|
-
{name != null && <>, <span className="oc-greeting-name">{name}</span></>}
|
|
57
|
-
</h2>
|
|
58
|
-
{!hideDate && <time className="oc-greeting-date" dateTime={iso}>{dateText}</time>}
|
|
59
|
-
</div>
|
|
60
|
-
{actions != null && <div className="oc-greeting-actions">{actions}</div>}
|
|
61
|
-
</header>
|
|
62
|
-
);
|
|
63
|
-
});
|
|
1
|
+
// File: packages/react/src/components/Greeting.tsx
|
|
2
|
+
// Map: greetingTimeOfDay() helper -> Greeting = header -> icon -> body (eyebrow, title + name, date) -> actions
|
|
3
|
+
// The time of day is computed from `date` (default: now). Morning 05-12, afternoon 12-18, evening otherwise.
|
|
4
|
+
import { forwardRef } from "react";
|
|
5
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
6
|
+
import { cx } from "../utils";
|
|
7
|
+
|
|
8
|
+
export type GreetingTimeOfDay = "morning" | "afternoon" | "evening";
|
|
9
|
+
|
|
10
|
+
/** Pick the time-of-day modifier for a date. */
|
|
11
|
+
export function greetingTimeOfDay(date: Date = new Date()): GreetingTimeOfDay {
|
|
12
|
+
const h = date.getHours();
|
|
13
|
+
if (h >= 5 && h < 12) return "morning";
|
|
14
|
+
if (h >= 12 && h < 18) return "afternoon";
|
|
15
|
+
return "evening";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const WORDS: Record<GreetingTimeOfDay, string> = { morning: "Good morning", afternoon: "Good afternoon", evening: "Good evening" };
|
|
19
|
+
|
|
20
|
+
export interface GreetingProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
|
|
21
|
+
name?: ReactNode;
|
|
22
|
+
/** The moment to greet for (default: now). Also shown as the date. */
|
|
23
|
+
date?: Date;
|
|
24
|
+
/** Force a time of day instead of computing it. */
|
|
25
|
+
timeOfDay?: GreetingTimeOfDay;
|
|
26
|
+
/** Translated greetings, e.g. `{ morning: "Goedemorgen", ... }`. */
|
|
27
|
+
words?: Partial<Record<GreetingTimeOfDay, string>>;
|
|
28
|
+
/** Locale for the date text (default: the browser locale). */
|
|
29
|
+
locale?: string;
|
|
30
|
+
/** Hide the date line. */
|
|
31
|
+
hideDate?: boolean;
|
|
32
|
+
eyebrow?: ReactNode;
|
|
33
|
+
icon?: ReactNode;
|
|
34
|
+
actions?: ReactNode;
|
|
35
|
+
size?: "lg";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** `.oc-greeting`: dashboard greeting with date and actions. */
|
|
39
|
+
export const Greeting = forwardRef<HTMLElement, GreetingProps>(function Greeting(
|
|
40
|
+
{ name, date, timeOfDay, words, locale, hideDate, eyebrow, icon, actions, size, className, ...rest },
|
|
41
|
+
ref,
|
|
42
|
+
) {
|
|
43
|
+
const when = date ?? new Date();
|
|
44
|
+
const tod = timeOfDay ?? greetingTimeOfDay(when);
|
|
45
|
+
const word = words?.[tod] ?? WORDS[tod];
|
|
46
|
+
const dateText = new Intl.DateTimeFormat(locale, { weekday: "long", day: "numeric", month: "long", year: "numeric" }).format(when);
|
|
47
|
+
// datetime needs YYYY-MM-DD in local time (toISOString would use UTC).
|
|
48
|
+
const iso = `${when.getFullYear()}-${String(when.getMonth() + 1).padStart(2, "0")}-${String(when.getDate()).padStart(2, "0")}`;
|
|
49
|
+
return (
|
|
50
|
+
<header ref={ref} className={cx("oc-greeting", `oc-greeting--${tod}`, size && `oc-greeting--${size}`, className)} {...rest}>
|
|
51
|
+
{icon != null && <span className="oc-greeting-icon" aria-hidden="true">{icon}</span>}
|
|
52
|
+
<div className="oc-greeting-body">
|
|
53
|
+
{eyebrow != null && <p className="oc-greeting-eyebrow">{eyebrow}</p>}
|
|
54
|
+
<h2 className="oc-greeting-title">
|
|
55
|
+
{word}
|
|
56
|
+
{name != null && <>, <span className="oc-greeting-name">{name}</span></>}
|
|
57
|
+
</h2>
|
|
58
|
+
{!hideDate && <time className="oc-greeting-date" dateTime={iso}>{dateText}</time>}
|
|
59
|
+
</div>
|
|
60
|
+
{actions != null && <div className="oc-greeting-actions">{actions}</div>}
|
|
61
|
+
</header>
|
|
62
|
+
);
|
|
63
|
+
});
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import { forwardRef } from "react";
|
|
2
|
-
import type { HTMLAttributes, ReactNode } from "react";
|
|
3
|
-
import { cx } from "../utils";
|
|
4
|
-
|
|
5
|
-
export interface HeadingProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
6
|
-
title: ReactNode;
|
|
7
|
-
description?: ReactNode;
|
|
8
|
-
/** Heading element level (default 2). */
|
|
9
|
-
level?: 1 | 2 | 3 | 4;
|
|
10
|
-
/** `sm` = section heading. */
|
|
11
|
-
size?: "md" | "sm";
|
|
12
|
-
titleId?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** `<div class="oc-heading">` title + muted description. */
|
|
16
|
-
export const Heading = forwardRef<HTMLDivElement, HeadingProps>(function Heading(
|
|
17
|
-
{ title, description, level = 2, size = "md", titleId, className, ...rest },
|
|
18
|
-
ref,
|
|
19
|
-
) {
|
|
20
|
-
const Tag = `h${level}` as "h2";
|
|
21
|
-
return (
|
|
22
|
-
<div ref={ref} className={cx("oc-heading", size === "sm" && "oc-heading--sm", className)} {...rest}>
|
|
23
|
-
<Tag className="oc-heading-title" id={titleId}>{title}</Tag>
|
|
24
|
-
{description != null && <p className="oc-heading-description">{description}</p>}
|
|
25
|
-
</div>
|
|
26
|
-
);
|
|
27
|
-
});
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
3
|
+
import { cx } from "../utils";
|
|
4
|
+
|
|
5
|
+
export interface HeadingProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
6
|
+
title: ReactNode;
|
|
7
|
+
description?: ReactNode;
|
|
8
|
+
/** Heading element level (default 2). */
|
|
9
|
+
level?: 1 | 2 | 3 | 4;
|
|
10
|
+
/** `sm` = section heading. */
|
|
11
|
+
size?: "md" | "sm";
|
|
12
|
+
titleId?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** `<div class="oc-heading">` title + muted description. */
|
|
16
|
+
export const Heading = forwardRef<HTMLDivElement, HeadingProps>(function Heading(
|
|
17
|
+
{ title, description, level = 2, size = "md", titleId, className, ...rest },
|
|
18
|
+
ref,
|
|
19
|
+
) {
|
|
20
|
+
const Tag = `h${level}` as "h2";
|
|
21
|
+
return (
|
|
22
|
+
<div ref={ref} className={cx("oc-heading", size === "sm" && "oc-heading--sm", className)} {...rest}>
|
|
23
|
+
<Tag className="oc-heading-title" id={titleId}>{title}</Tag>
|
|
24
|
+
{description != null && <p className="oc-heading-description">{description}</p>}
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
});
|
package/src/components/Icon.tsx
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import type { SVGProps } from "react";
|
|
2
|
-
|
|
3
|
-
export interface IconProps extends Omit<SVGProps<SVGSVGElement>, "d"> {
|
|
4
|
-
/** SVG path data (24x24 viewBox, stroke icon). Use `children` for several shapes. */
|
|
5
|
-
d?: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/** Inline 24x24 stroke icon (`currentColor`), the shape used by the snippets. */
|
|
9
|
-
export function Icon({ d, children, className, ...rest }: IconProps) {
|
|
10
|
-
return (
|
|
11
|
-
<svg
|
|
12
|
-
className={className}
|
|
13
|
-
viewBox="0 0 24 24"
|
|
14
|
-
fill="none"
|
|
15
|
-
stroke="currentColor"
|
|
16
|
-
strokeWidth={2}
|
|
17
|
-
strokeLinecap="round"
|
|
18
|
-
strokeLinejoin="round"
|
|
19
|
-
aria-hidden="true"
|
|
20
|
-
{...rest}
|
|
21
|
-
>
|
|
22
|
-
{d ? <path d={d} /> : children}
|
|
23
|
-
</svg>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
1
|
+
import type { SVGProps } from "react";
|
|
2
|
+
|
|
3
|
+
export interface IconProps extends Omit<SVGProps<SVGSVGElement>, "d"> {
|
|
4
|
+
/** SVG path data (24x24 viewBox, stroke icon). Use `children` for several shapes. */
|
|
5
|
+
d?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Inline 24x24 stroke icon (`currentColor`), the shape used by the snippets. */
|
|
9
|
+
export function Icon({ d, children, className, ...rest }: IconProps) {
|
|
10
|
+
return (
|
|
11
|
+
<svg
|
|
12
|
+
className={className}
|
|
13
|
+
viewBox="0 0 24 24"
|
|
14
|
+
fill="none"
|
|
15
|
+
stroke="currentColor"
|
|
16
|
+
strokeWidth={2}
|
|
17
|
+
strokeLinecap="round"
|
|
18
|
+
strokeLinejoin="round"
|
|
19
|
+
aria-hidden="true"
|
|
20
|
+
{...rest}
|
|
21
|
+
>
|
|
22
|
+
{d ? <path d={d} /> : children}
|
|
23
|
+
</svg>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
// File: packages/react/src/components/ImageZoom.tsx
|
|
2
|
-
// Map: props -> ImageZoom (toolbar of radio levels -> viewport -> canvas with the image)
|
|
3
|
-
// Same markup as packages/ui/snippets/image-zoom.html. The chosen level is also written to --oc-zoom,
|
|
4
|
-
// so any number works (the CSS-only :has() rules cover 1, 1.5, 2, 3 and 4).
|
|
5
|
-
"use client";
|
|
6
|
-
|
|
7
|
-
import { forwardRef, useId, useState } from "react";
|
|
8
|
-
import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
|
|
9
|
-
import { cx } from "../utils";
|
|
10
|
-
|
|
11
|
-
export interface ImageZoomProps extends HTMLAttributes<HTMLDivElement> {
|
|
12
|
-
src?: string;
|
|
13
|
-
alt?: string;
|
|
14
|
-
/** Zoom levels for the toolbar. Empty array = no toolbar. Default [1, 1.5, 2, 3]. */
|
|
15
|
-
levels?: number[];
|
|
16
|
-
/** Starting level. Default 1. */
|
|
17
|
-
defaultZoom?: number;
|
|
18
|
-
/** Window ratio: "square", "photo" or a CSS ratio such as "21 / 9". Default 16 / 9. */
|
|
19
|
-
ratio?: "square" | "photo" | string;
|
|
20
|
-
/** Replaces the <img> inside the canvas (svg, video, placeholder art). */
|
|
21
|
-
media?: ReactNode;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** `.oc-image-zoom`: zoom by making the canvas wider than the viewport; pan by scrolling. */
|
|
25
|
-
export const ImageZoom = forwardRef<HTMLDivElement, ImageZoomProps>(function ImageZoom(
|
|
26
|
-
{ src, alt = "", levels = [1, 1.5, 2, 3], defaultZoom = 1, ratio, media, className, style, ...rest },
|
|
27
|
-
ref,
|
|
28
|
-
) {
|
|
29
|
-
const name = `oc-zoom-${useId().replace(/:/g, "")}`;
|
|
30
|
-
const [zoom, setZoom] = useState(defaultZoom);
|
|
31
|
-
const preset = ratio === "square" || ratio === "photo" ? ratio : undefined;
|
|
32
|
-
const vars = { "--oc-zoom": String(zoom), ...(ratio && !preset ? { "--oc-image-zoom-ratio": ratio } : {}) } as CSSProperties;
|
|
33
|
-
return (
|
|
34
|
-
<div ref={ref} className={cx("oc-image-zoom", preset && `oc-image-zoom--${preset}`, className)} style={{ ...vars, ...style }} {...rest}>
|
|
35
|
-
{levels.length > 0 && (
|
|
36
|
-
<div className="oc-image-zoom__toolbar" role="radiogroup" aria-label="Zoom level">
|
|
37
|
-
<span className="oc-image-zoom__label">Zoom</span>
|
|
38
|
-
{levels.map((l) => (
|
|
39
|
-
<label key={l} className="oc-image-zoom__level">
|
|
40
|
-
<input type="radio" name={name} value={String(l)} checked={zoom === l} onChange={() => setZoom(l)} />
|
|
41
|
-
{Math.round(l * 100)}%
|
|
42
|
-
</label>
|
|
43
|
-
))}
|
|
44
|
-
</div>
|
|
45
|
-
)}
|
|
46
|
-
<div className="oc-image-zoom__viewport" tabIndex={0} role="region" aria-label={alt ? `${alt}, scroll to pan` : "Scroll to pan"}>
|
|
47
|
-
<div className="oc-image-zoom__canvas">{media ?? (src ? <img src={src} alt={alt} /> : <div className="oc-media-art" role="img" aria-label={alt} />)}</div>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
);
|
|
51
|
-
});
|
|
1
|
+
// File: packages/react/src/components/ImageZoom.tsx
|
|
2
|
+
// Map: props -> ImageZoom (toolbar of radio levels -> viewport -> canvas with the image)
|
|
3
|
+
// Same markup as packages/ui/snippets/image-zoom.html. The chosen level is also written to --oc-zoom,
|
|
4
|
+
// so any number works (the CSS-only :has() rules cover 1, 1.5, 2, 3 and 4).
|
|
5
|
+
"use client";
|
|
6
|
+
|
|
7
|
+
import { forwardRef, useId, useState } from "react";
|
|
8
|
+
import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
|
|
9
|
+
import { cx } from "../utils";
|
|
10
|
+
|
|
11
|
+
export interface ImageZoomProps extends HTMLAttributes<HTMLDivElement> {
|
|
12
|
+
src?: string;
|
|
13
|
+
alt?: string;
|
|
14
|
+
/** Zoom levels for the toolbar. Empty array = no toolbar. Default [1, 1.5, 2, 3]. */
|
|
15
|
+
levels?: number[];
|
|
16
|
+
/** Starting level. Default 1. */
|
|
17
|
+
defaultZoom?: number;
|
|
18
|
+
/** Window ratio: "square", "photo" or a CSS ratio such as "21 / 9". Default 16 / 9. */
|
|
19
|
+
ratio?: "square" | "photo" | string;
|
|
20
|
+
/** Replaces the <img> inside the canvas (svg, video, placeholder art). */
|
|
21
|
+
media?: ReactNode;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** `.oc-image-zoom`: zoom by making the canvas wider than the viewport; pan by scrolling. */
|
|
25
|
+
export const ImageZoom = forwardRef<HTMLDivElement, ImageZoomProps>(function ImageZoom(
|
|
26
|
+
{ src, alt = "", levels = [1, 1.5, 2, 3], defaultZoom = 1, ratio, media, className, style, ...rest },
|
|
27
|
+
ref,
|
|
28
|
+
) {
|
|
29
|
+
const name = `oc-zoom-${useId().replace(/:/g, "")}`;
|
|
30
|
+
const [zoom, setZoom] = useState(defaultZoom);
|
|
31
|
+
const preset = ratio === "square" || ratio === "photo" ? ratio : undefined;
|
|
32
|
+
const vars = { "--oc-zoom": String(zoom), ...(ratio && !preset ? { "--oc-image-zoom-ratio": ratio } : {}) } as CSSProperties;
|
|
33
|
+
return (
|
|
34
|
+
<div ref={ref} className={cx("oc-image-zoom", preset && `oc-image-zoom--${preset}`, className)} style={{ ...vars, ...style }} {...rest}>
|
|
35
|
+
{levels.length > 0 && (
|
|
36
|
+
<div className="oc-image-zoom__toolbar" role="radiogroup" aria-label="Zoom level">
|
|
37
|
+
<span className="oc-image-zoom__label">Zoom</span>
|
|
38
|
+
{levels.map((l) => (
|
|
39
|
+
<label key={l} className="oc-image-zoom__level">
|
|
40
|
+
<input type="radio" name={name} value={String(l)} checked={zoom === l} onChange={() => setZoom(l)} />
|
|
41
|
+
{Math.round(l * 100)}%
|
|
42
|
+
</label>
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
)}
|
|
46
|
+
<div className="oc-image-zoom__viewport" tabIndex={0} role="region" aria-label={alt ? `${alt}, scroll to pan` : "Scroll to pan"}>
|
|
47
|
+
<div className="oc-image-zoom__canvas">{media ?? (src ? <img src={src} alt={alt} /> : <div className="oc-media-art" role="img" aria-label={alt} />)}</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
});
|
package/src/components/Input.tsx
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { forwardRef } from "react";
|
|
2
|
-
import type { InputHTMLAttributes } from "react";
|
|
3
|
-
import { cx } from "../utils";
|
|
4
|
-
|
|
5
|
-
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
6
|
-
/** Error state: adds `oc-input--error` and `aria-invalid`. */
|
|
7
|
-
error?: boolean;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/** `<input class="oc-input">` */
|
|
11
|
-
export const Input = forwardRef<HTMLInputElement, InputProps>(function Input({ error, className, type = "text", ...rest }, ref) {
|
|
12
|
-
return (
|
|
13
|
-
<input
|
|
14
|
-
ref={ref}
|
|
15
|
-
type={type}
|
|
16
|
-
className={cx("oc-input", error && "oc-input--error", className)}
|
|
17
|
-
aria-invalid={error ? true : rest["aria-invalid"]}
|
|
18
|
-
{...rest}
|
|
19
|
-
/>
|
|
20
|
-
);
|
|
21
|
-
});
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import type { InputHTMLAttributes } from "react";
|
|
3
|
+
import { cx } from "../utils";
|
|
4
|
+
|
|
5
|
+
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
6
|
+
/** Error state: adds `oc-input--error` and `aria-invalid`. */
|
|
7
|
+
error?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** `<input class="oc-input">` */
|
|
11
|
+
export const Input = forwardRef<HTMLInputElement, InputProps>(function Input({ error, className, type = "text", ...rest }, ref) {
|
|
12
|
+
return (
|
|
13
|
+
<input
|
|
14
|
+
ref={ref}
|
|
15
|
+
type={type}
|
|
16
|
+
className={cx("oc-input", error && "oc-input--error", className)}
|
|
17
|
+
aria-invalid={error ? true : rest["aria-invalid"]}
|
|
18
|
+
{...rest}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
});
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { forwardRef } from "react";
|
|
2
|
-
import type { HTMLAttributes, ReactNode } from "react";
|
|
3
|
-
import { cx } from "../utils";
|
|
4
|
-
|
|
5
|
-
export interface InputGroupProps extends Omit<HTMLAttributes<HTMLDivElement>, "prefix"> {
|
|
6
|
-
/** Addon before the input (currency sign...). */
|
|
7
|
-
prefix?: ReactNode;
|
|
8
|
-
/** Addon after the input (%, unit...). */
|
|
9
|
-
suffix?: ReactNode;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/** `<div class="oc-input-group">` with `oc-input-addon` spans around the child `<Input>`. */
|
|
13
|
-
export const InputGroup = forwardRef<HTMLDivElement, InputGroupProps>(function InputGroup(
|
|
14
|
-
{ prefix, suffix, className, children, ...rest },
|
|
15
|
-
ref,
|
|
16
|
-
) {
|
|
17
|
-
return (
|
|
18
|
-
<div ref={ref} className={cx("oc-input-group", className)} {...rest}>
|
|
19
|
-
{prefix != null && <span className="oc-input-addon">{prefix}</span>}
|
|
20
|
-
{children}
|
|
21
|
-
{suffix != null && <span className="oc-input-addon">{suffix}</span>}
|
|
22
|
-
</div>
|
|
23
|
-
);
|
|
24
|
-
});
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
3
|
+
import { cx } from "../utils";
|
|
4
|
+
|
|
5
|
+
export interface InputGroupProps extends Omit<HTMLAttributes<HTMLDivElement>, "prefix"> {
|
|
6
|
+
/** Addon before the input (currency sign...). */
|
|
7
|
+
prefix?: ReactNode;
|
|
8
|
+
/** Addon after the input (%, unit...). */
|
|
9
|
+
suffix?: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** `<div class="oc-input-group">` with `oc-input-addon` spans around the child `<Input>`. */
|
|
13
|
+
export const InputGroup = forwardRef<HTMLDivElement, InputGroupProps>(function InputGroup(
|
|
14
|
+
{ prefix, suffix, className, children, ...rest },
|
|
15
|
+
ref,
|
|
16
|
+
) {
|
|
17
|
+
return (
|
|
18
|
+
<div ref={ref} className={cx("oc-input-group", className)} {...rest}>
|
|
19
|
+
{prefix != null && <span className="oc-input-addon">{prefix}</span>}
|
|
20
|
+
{children}
|
|
21
|
+
{suffix != null && <span className="oc-input-addon">{suffix}</span>}
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
});
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
// File: packages/react/src/components/LanguageSelect.tsx
|
|
2
|
-
// Map: types -> globe icon -> component (label pill: icon + native select)
|
|
3
|
-
// Same markup as packages/ui/snippets/language-select.html. The app decides what "change" does.
|
|
4
|
-
"use client";
|
|
5
|
-
|
|
6
|
-
import { forwardRef } from "react";
|
|
7
|
-
import type { ReactNode, SelectHTMLAttributes } from "react";
|
|
8
|
-
import { cx } from "../utils";
|
|
9
|
-
import { useControllable } from "../hooks";
|
|
10
|
-
|
|
11
|
-
export interface LanguageSelectOption {
|
|
12
|
-
value: string;
|
|
13
|
-
label: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface LanguageSelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, "value" | "defaultValue" | "onChange"> {
|
|
17
|
-
options: LanguageSelectOption[];
|
|
18
|
-
value?: string;
|
|
19
|
-
defaultValue?: string;
|
|
20
|
-
onChange?: (value: string) => void;
|
|
21
|
-
/** Currency look (symbol in primary color). */
|
|
22
|
-
currency?: boolean;
|
|
23
|
-
/** No frame until hover / focus. */
|
|
24
|
-
ghost?: boolean;
|
|
25
|
-
/** Replaces the globe icon (e.g. "€"). */
|
|
26
|
-
icon?: ReactNode;
|
|
27
|
-
/** Class for the wrapping label (`className` goes to the select). */
|
|
28
|
-
labelClassName?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Small globe drawn with currentColor. */
|
|
32
|
-
const Globe = () => (
|
|
33
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
34
|
-
<circle cx="12" cy="12" r="9" />
|
|
35
|
-
<path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18" />
|
|
36
|
-
</svg>
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
/** `.oc-language-select`: native select in a pill with a leading icon. */
|
|
40
|
-
export const LanguageSelect = forwardRef<HTMLSelectElement, LanguageSelectProps>(function LanguageSelect(
|
|
41
|
-
{ options, value, defaultValue, onChange, currency, ghost, icon, labelClassName, className, ...rest },
|
|
42
|
-
ref,
|
|
43
|
-
) {
|
|
44
|
-
const [current, setCurrent] = useControllable<string>(value, defaultValue ?? options[0]?.value ?? "", onChange);
|
|
45
|
-
return (
|
|
46
|
-
<label className={cx("oc-language-select", ghost && "oc-language-select--ghost", currency && "oc-language-select--currency", labelClassName)}>
|
|
47
|
-
<span className="oc-language-select-icon" aria-hidden="true">{icon ?? (currency ? "\u20AC" : <Globe />)}</span>
|
|
48
|
-
<select
|
|
49
|
-
ref={ref}
|
|
50
|
-
className={cx("oc-language-select-control", className)}
|
|
51
|
-
aria-label={currency ? "Currency" : "Language"}
|
|
52
|
-
value={current}
|
|
53
|
-
onChange={(e) => setCurrent(e.target.value)}
|
|
54
|
-
{...rest}
|
|
55
|
-
>
|
|
56
|
-
{options.map((o) => (
|
|
57
|
-
<option key={o.value} value={o.value}>
|
|
58
|
-
{o.label}
|
|
59
|
-
</option>
|
|
60
|
-
))}
|
|
61
|
-
</select>
|
|
62
|
-
</label>
|
|
63
|
-
);
|
|
64
|
-
});
|
|
1
|
+
// File: packages/react/src/components/LanguageSelect.tsx
|
|
2
|
+
// Map: types -> globe icon -> component (label pill: icon + native select)
|
|
3
|
+
// Same markup as packages/ui/snippets/language-select.html. The app decides what "change" does.
|
|
4
|
+
"use client";
|
|
5
|
+
|
|
6
|
+
import { forwardRef } from "react";
|
|
7
|
+
import type { ReactNode, SelectHTMLAttributes } from "react";
|
|
8
|
+
import { cx } from "../utils";
|
|
9
|
+
import { useControllable } from "../hooks";
|
|
10
|
+
|
|
11
|
+
export interface LanguageSelectOption {
|
|
12
|
+
value: string;
|
|
13
|
+
label: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface LanguageSelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, "value" | "defaultValue" | "onChange"> {
|
|
17
|
+
options: LanguageSelectOption[];
|
|
18
|
+
value?: string;
|
|
19
|
+
defaultValue?: string;
|
|
20
|
+
onChange?: (value: string) => void;
|
|
21
|
+
/** Currency look (symbol in primary color). */
|
|
22
|
+
currency?: boolean;
|
|
23
|
+
/** No frame until hover / focus. */
|
|
24
|
+
ghost?: boolean;
|
|
25
|
+
/** Replaces the globe icon (e.g. "€"). */
|
|
26
|
+
icon?: ReactNode;
|
|
27
|
+
/** Class for the wrapping label (`className` goes to the select). */
|
|
28
|
+
labelClassName?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Small globe drawn with currentColor. */
|
|
32
|
+
const Globe = () => (
|
|
33
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
34
|
+
<circle cx="12" cy="12" r="9" />
|
|
35
|
+
<path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18" />
|
|
36
|
+
</svg>
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
/** `.oc-language-select`: native select in a pill with a leading icon. */
|
|
40
|
+
export const LanguageSelect = forwardRef<HTMLSelectElement, LanguageSelectProps>(function LanguageSelect(
|
|
41
|
+
{ options, value, defaultValue, onChange, currency, ghost, icon, labelClassName, className, ...rest },
|
|
42
|
+
ref,
|
|
43
|
+
) {
|
|
44
|
+
const [current, setCurrent] = useControllable<string>(value, defaultValue ?? options[0]?.value ?? "", onChange);
|
|
45
|
+
return (
|
|
46
|
+
<label className={cx("oc-language-select", ghost && "oc-language-select--ghost", currency && "oc-language-select--currency", labelClassName)}>
|
|
47
|
+
<span className="oc-language-select-icon" aria-hidden="true">{icon ?? (currency ? "\u20AC" : <Globe />)}</span>
|
|
48
|
+
<select
|
|
49
|
+
ref={ref}
|
|
50
|
+
className={cx("oc-language-select-control", className)}
|
|
51
|
+
aria-label={currency ? "Currency" : "Language"}
|
|
52
|
+
value={current}
|
|
53
|
+
onChange={(e) => setCurrent(e.target.value)}
|
|
54
|
+
{...rest}
|
|
55
|
+
>
|
|
56
|
+
{options.map((o) => (
|
|
57
|
+
<option key={o.value} value={o.value}>
|
|
58
|
+
{o.label}
|
|
59
|
+
</option>
|
|
60
|
+
))}
|
|
61
|
+
</select>
|
|
62
|
+
</label>
|
|
63
|
+
);
|
|
64
|
+
});
|