@birdycom/omniaui-react 0.12.1 → 0.12.3
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/dist/index.d.ts +129 -5
- package/dist/index.js +1302 -1182
- package/manifest.json +36 -0
- package/package.json +1 -1
- package/src/components/Alert.tsx +4 -2
- package/src/components/Button.tsx +8 -5
- package/src/components/Glass.tsx +29 -0
- package/src/components/LinkRow.tsx +34 -0
- package/src/components/LiveFrame.tsx +37 -0
- package/src/components/Progress.tsx +22 -5
- package/src/components/Prose.tsx +2 -2
- package/src/components/StatusDot.tsx +51 -0
- package/src/components/Titlebar.tsx +61 -0
- package/src/components/Watermark.tsx +35 -0
- package/src/index.ts +6 -0
package/manifest.json
CHANGED
|
@@ -258,6 +258,12 @@
|
|
|
258
258
|
"file": "src/components/FormField.tsx",
|
|
259
259
|
"snippet": "@birdycom/omniaui/snippets/form-field.html"
|
|
260
260
|
},
|
|
261
|
+
{
|
|
262
|
+
"name": "glass",
|
|
263
|
+
"kind": "component",
|
|
264
|
+
"file": "src/components/Glass.tsx",
|
|
265
|
+
"snippet": "@birdycom/omniaui/snippets/glass.html"
|
|
266
|
+
},
|
|
261
267
|
{
|
|
262
268
|
"name": "greeting",
|
|
263
269
|
"kind": "component",
|
|
@@ -318,12 +324,24 @@
|
|
|
318
324
|
"file": "src/components/LinkArrow.tsx",
|
|
319
325
|
"snippet": "@birdycom/omniaui/snippets/link-arrow.html"
|
|
320
326
|
},
|
|
327
|
+
{
|
|
328
|
+
"name": "link-row",
|
|
329
|
+
"kind": "component",
|
|
330
|
+
"file": "src/components/LinkRow.tsx",
|
|
331
|
+
"snippet": "@birdycom/omniaui/snippets/link-row.html"
|
|
332
|
+
},
|
|
321
333
|
{
|
|
322
334
|
"name": "list-toolbar",
|
|
323
335
|
"kind": "component",
|
|
324
336
|
"file": "src/components/ListToolbar.tsx",
|
|
325
337
|
"snippet": "@birdycom/omniaui/snippets/list-toolbar.html"
|
|
326
338
|
},
|
|
339
|
+
{
|
|
340
|
+
"name": "live-frame",
|
|
341
|
+
"kind": "component",
|
|
342
|
+
"file": "src/components/LiveFrame.tsx",
|
|
343
|
+
"snippet": "@birdycom/omniaui/snippets/live-frame.html"
|
|
344
|
+
},
|
|
327
345
|
{
|
|
328
346
|
"name": "marquee",
|
|
329
347
|
"kind": "component",
|
|
@@ -522,6 +540,12 @@
|
|
|
522
540
|
"file": "src/components/StatCard.tsx",
|
|
523
541
|
"snippet": "@birdycom/omniaui/snippets/stat-card.html"
|
|
524
542
|
},
|
|
543
|
+
{
|
|
544
|
+
"name": "status-dot",
|
|
545
|
+
"kind": "component",
|
|
546
|
+
"file": "src/components/StatusDot.tsx",
|
|
547
|
+
"snippet": "@birdycom/omniaui/snippets/status-dot.html"
|
|
548
|
+
},
|
|
525
549
|
{
|
|
526
550
|
"name": "stepper",
|
|
527
551
|
"kind": "component",
|
|
@@ -594,6 +618,12 @@
|
|
|
594
618
|
"file": "src/components/Timeline.tsx",
|
|
595
619
|
"snippet": "@birdycom/omniaui/snippets/timeline.html"
|
|
596
620
|
},
|
|
621
|
+
{
|
|
622
|
+
"name": "titlebar",
|
|
623
|
+
"kind": "component",
|
|
624
|
+
"file": "src/components/Titlebar.tsx",
|
|
625
|
+
"snippet": "@birdycom/omniaui/snippets/titlebar.html"
|
|
626
|
+
},
|
|
597
627
|
{
|
|
598
628
|
"name": "toast",
|
|
599
629
|
"kind": "component",
|
|
@@ -642,6 +672,12 @@
|
|
|
642
672
|
"file": "src/components/VersionBanner.tsx",
|
|
643
673
|
"snippet": "@birdycom/omniaui/snippets/version-banner.html"
|
|
644
674
|
},
|
|
675
|
+
{
|
|
676
|
+
"name": "watermark",
|
|
677
|
+
"kind": "component",
|
|
678
|
+
"file": "src/components/Watermark.tsx",
|
|
679
|
+
"snippet": "@birdycom/omniaui/snippets/watermark.html"
|
|
680
|
+
},
|
|
645
681
|
{
|
|
646
682
|
"name": "about-timeline",
|
|
647
683
|
"kind": "block",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@birdycom/omniaui-react",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.3",
|
|
4
4
|
"description": "OmniaUI for React: typed function components and page blocks that render the exact @birdycom/omniaui and @birdycom/omniaui-blocks markup.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/components/Alert.tsx
CHANGED
|
@@ -8,15 +8,17 @@ export interface AlertProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"
|
|
|
8
8
|
variant?: AlertVariant;
|
|
9
9
|
/** Heading (`oc-alert-title`). */
|
|
10
10
|
title?: ReactNode;
|
|
11
|
+
/** Compact message under a field or a form (an error, a hint). */
|
|
12
|
+
inline?: boolean;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
/** `<div class="oc-alert oc-alert--{variant}" role="status">`; children render inside `oc-alert-description`. */
|
|
14
16
|
export const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(
|
|
15
|
-
{ variant = "info", title, children, className, role = "status", ...rest },
|
|
17
|
+
{ variant = "info", title, inline, children, className, role = "status", ...rest },
|
|
16
18
|
ref,
|
|
17
19
|
) {
|
|
18
20
|
return (
|
|
19
|
-
<div ref={ref} role={role} className={cx("oc-alert", `oc-alert--${variant}`, className)} {...rest}>
|
|
21
|
+
<div ref={ref} role={role} className={cx("oc-alert", `oc-alert--${variant}`, inline && "oc-alert--inline", className)} {...rest}>
|
|
20
22
|
{title != null && <h4 className="oc-alert-title">{title}</h4>}
|
|
21
23
|
{children != null && <p className="oc-alert-description">{children}</p>}
|
|
22
24
|
</div>
|
|
@@ -14,16 +14,19 @@ interface ButtonStyleProps {
|
|
|
14
14
|
icon?: boolean;
|
|
15
15
|
/** Fully rounded pill shape. */
|
|
16
16
|
pill?: boolean;
|
|
17
|
+
/** Inside running text (with variant="link"): no padding or height, the size of the text around it. */
|
|
18
|
+
inline?: boolean;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/** The class string of a button; reuse it on your own elements (router links etc.). */
|
|
20
|
-
export function buttonClass({ variant = "default", size = "md", icon, pill, className }: ButtonStyleProps & { className?: string }) {
|
|
22
|
+
export function buttonClass({ variant = "default", size = "md", icon, pill, inline, className }: ButtonStyleProps & { className?: string }) {
|
|
21
23
|
return cx(
|
|
22
24
|
"oc-btn",
|
|
23
25
|
variant !== "default" && `oc-btn--${variant}`,
|
|
24
26
|
size !== "md" && `oc-btn--${size}`,
|
|
25
27
|
icon && "oc-btn--icon",
|
|
26
28
|
pill && "oc-btn--pill",
|
|
29
|
+
inline && "oc-btn--inline",
|
|
27
30
|
className,
|
|
28
31
|
);
|
|
29
32
|
}
|
|
@@ -32,18 +35,18 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, Bu
|
|
|
32
35
|
|
|
33
36
|
/** `<button class="oc-btn oc-btn--{variant} oc-btn--{size}">` */
|
|
34
37
|
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
35
|
-
{ variant, size, icon, pill, className, ...rest },
|
|
38
|
+
{ variant, size, icon, pill, inline, className, ...rest },
|
|
36
39
|
ref,
|
|
37
40
|
) {
|
|
38
|
-
return <button ref={ref} className={buttonClass({ variant, size, icon, pill, className })} {...rest} />;
|
|
41
|
+
return <button ref={ref} className={buttonClass({ variant, size, icon, pill, inline, className })} {...rest} />;
|
|
39
42
|
});
|
|
40
43
|
|
|
41
44
|
export interface ButtonLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement>, ButtonStyleProps {}
|
|
42
45
|
|
|
43
46
|
/** An anchor styled as a button. */
|
|
44
47
|
export const ButtonLink = forwardRef<HTMLAnchorElement, ButtonLinkProps>(function ButtonLink(
|
|
45
|
-
{ variant, size, icon, pill, className, ...rest },
|
|
48
|
+
{ variant, size, icon, pill, inline, className, ...rest },
|
|
46
49
|
ref,
|
|
47
50
|
) {
|
|
48
|
-
return <a ref={ref} className={buttonClass({ variant, size, icon, pill, className })} {...rest} />;
|
|
51
|
+
return <a ref={ref} className={buttonClass({ variant, size, icon, pill, inline, className })} {...rest} />;
|
|
49
52
|
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// File: packages/react/src/components/Glass.tsx | Map: types -> Glass (sheet: --strong, --flat) -> GlassPanel (layer: --strong)
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import type { HTMLAttributes } from "react";
|
|
4
|
+
import { cx } from "../utils";
|
|
5
|
+
|
|
6
|
+
export interface GlassProps extends HTMLAttributes<HTMLElement> {
|
|
7
|
+
/** More opaque, for text-heavy content. */
|
|
8
|
+
strong?: boolean;
|
|
9
|
+
/** Desktop window where the OS draws the blur: no own blur, no drop shadow. */
|
|
10
|
+
flat?: boolean;
|
|
11
|
+
as?: "div" | "section" | "aside" | "article" | "main";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** `.oc-glass`: see-through sheet with a light top edge; .oc-card inside becomes a glass panel. */
|
|
15
|
+
export const Glass = forwardRef<HTMLElement, GlassProps>(function Glass(
|
|
16
|
+
{ strong, flat, as: Tag = "div", className, ...rest },
|
|
17
|
+
ref,
|
|
18
|
+
) {
|
|
19
|
+
return <Tag ref={ref as React.Ref<HTMLDivElement>} className={cx("oc-glass", strong && "oc-glass--strong", flat && "oc-glass--flat", className)} {...rest} />;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export interface GlassPanelProps extends HTMLAttributes<HTMLDivElement> {
|
|
23
|
+
strong?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** `.oc-glass-panel`: a lighter layer inside a glass sheet. */
|
|
27
|
+
export const GlassPanel = forwardRef<HTMLDivElement, GlassPanelProps>(function GlassPanel({ strong, className, ...rest }, ref) {
|
|
28
|
+
return <div ref={ref} className={cx("oc-glass-panel", strong && "oc-glass-panel--strong", className)} {...rest} />;
|
|
29
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// File: packages/react/src/components/LinkRow.tsx | Map: types -> LinkRow (ul.oc-link-row -> li > a per link, or children)
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
4
|
+
import { cx } from "../utils";
|
|
5
|
+
|
|
6
|
+
export interface LinkRowLink {
|
|
7
|
+
href: string;
|
|
8
|
+
label: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface LinkRowProps extends HTMLAttributes<HTMLUListElement> {
|
|
12
|
+
/** The links; external ones get an arrow and github.com links the GitHub mark (CSS, by the href). Or pass <li> children. */
|
|
13
|
+
links?: LinkRowLink[];
|
|
14
|
+
tone?: "default" | "accent" | "muted";
|
|
15
|
+
size?: "sm" | "default" | "lg";
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const external = (href: string) => /^https?:\/\//.test(href);
|
|
20
|
+
|
|
21
|
+
/** `<ul class="oc-link-row">`: a row of links under a card, icons by the href. */
|
|
22
|
+
export const LinkRow = forwardRef<HTMLUListElement, LinkRowProps>(function LinkRow(
|
|
23
|
+
{ links, tone = "default", size = "default", className, children, ...rest },
|
|
24
|
+
ref,
|
|
25
|
+
) {
|
|
26
|
+
const classes = cx("oc-link-row", tone !== "default" && `oc-link-row--${tone}`, size !== "default" && `oc-link-row--${size}`, className);
|
|
27
|
+
return (
|
|
28
|
+
<ul ref={ref} className={classes} {...rest}>
|
|
29
|
+
{links
|
|
30
|
+
? links.map((l, i) => <li key={i}><a href={l.href} rel={external(l.href) ? "noopener" : undefined}>{l.label}</a></li>)
|
|
31
|
+
: children}
|
|
32
|
+
</ul>
|
|
33
|
+
);
|
|
34
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// File: packages/react/src/components/LiveFrame.tsx | Map: types -> LiveFrame (div.oc-live-frame + virtual size -> iframe or children)
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import type { CSSProperties, HTMLAttributes, ReactNode } from "react";
|
|
4
|
+
import { cx } from "../utils";
|
|
5
|
+
|
|
6
|
+
export interface LiveFrameProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/** Page to show in an iframe. Leave empty and pass children (an <img>, <video>, or .oc-live-frame__content). */
|
|
8
|
+
src?: string;
|
|
9
|
+
/** Accessible name of the iframe. */
|
|
10
|
+
title?: string;
|
|
11
|
+
/** Virtual size in px: the page is laid out at this size and scaled to the frame width. */
|
|
12
|
+
width?: number;
|
|
13
|
+
height?: number;
|
|
14
|
+
/** A picture of a page: clicks and scrolling pass through. */
|
|
15
|
+
static?: boolean;
|
|
16
|
+
/** Put the frame in the view slot of an OmniaUI browser frame (adds .oc-browser-frame__view). */
|
|
17
|
+
inBrowserFrame?: boolean;
|
|
18
|
+
children?: ReactNode;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** `.oc-live-frame`: a live page at a virtual size, scaled down to the frame width in pure CSS. */
|
|
22
|
+
export const LiveFrame = forwardRef<HTMLDivElement, LiveFrameProps>(function LiveFrame(
|
|
23
|
+
{ src, title = "", width = 1280, height = 720, static: isStatic = false, inBrowserFrame = false, className, style, children, ...rest },
|
|
24
|
+
ref,
|
|
25
|
+
) {
|
|
26
|
+
const size = { "--oc-live-w": width, "--oc-live-h": height } as CSSProperties;
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
ref={ref}
|
|
30
|
+
className={cx(inBrowserFrame && "oc-browser-frame__view", "oc-live-frame", isStatic && "oc-live-frame--static", className)}
|
|
31
|
+
style={{ ...size, ...style }}
|
|
32
|
+
{...rest}
|
|
33
|
+
>
|
|
34
|
+
{src ? <iframe src={src} title={title} loading="lazy" scrolling="no" tabIndex={isStatic ? -1 : undefined} /> : children}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
});
|
|
@@ -6,27 +6,44 @@ export interface ProgressProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
6
6
|
value: number;
|
|
7
7
|
/** Maximum (default 100). */
|
|
8
8
|
max?: number;
|
|
9
|
-
/** Bar color. */
|
|
9
|
+
/** Bar color. Wins over `levels`. */
|
|
10
10
|
variant?: "default" | "success" | "warning" | "danger";
|
|
11
|
+
/** Height: 4 px (xs), 6 px (sm) or 8 px (default). */
|
|
12
|
+
size?: "xs" | "sm" | "md";
|
|
13
|
+
/** Light top edge and a soft glow in the bar color. */
|
|
14
|
+
glow?: boolean;
|
|
15
|
+
/** Color by the value: warning from 70 %, danger from 90 %; or your own [warning, danger] limits in percent. */
|
|
16
|
+
levels?: boolean | [number, number];
|
|
17
|
+
/** A target or schedule point, in the unit of `value`: an upright line on the track. */
|
|
18
|
+
marker?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The bar color for `levels` at a percentage. */
|
|
22
|
+
export function progressLevel(pct: number, levels: true | [number, number]): "default" | "warning" | "danger" {
|
|
23
|
+
const [warn, danger] = levels === true ? [70, 90] : levels;
|
|
24
|
+
return pct >= danger ? "danger" : pct >= warn ? "warning" : "default";
|
|
11
25
|
}
|
|
12
26
|
|
|
13
27
|
/** `<div class="oc-progress" role="progressbar">` with an `oc-progress-bar` sized in percent. */
|
|
14
28
|
export const Progress = forwardRef<HTMLDivElement, ProgressProps>(function Progress(
|
|
15
|
-
{ value, max = 100, variant = "default", className, ...rest },
|
|
29
|
+
{ value, max = 100, variant = "default", size = "md", glow, levels, marker, className, ...rest },
|
|
16
30
|
ref,
|
|
17
31
|
) {
|
|
18
|
-
const
|
|
32
|
+
const percent = (v: number) => (max > 0 ? Math.min(100, Math.max(0, (v / max) * 100)) : 0);
|
|
33
|
+
const pct = Math.round(percent(value));
|
|
34
|
+
const tone = variant === "default" && levels ? progressLevel(pct, levels) : variant;
|
|
19
35
|
return (
|
|
20
36
|
<div
|
|
21
37
|
ref={ref}
|
|
22
|
-
className={cx("oc-progress", className)}
|
|
38
|
+
className={cx("oc-progress", size !== "md" && `oc-progress--${size}`, glow && "oc-progress--glow", className)}
|
|
23
39
|
role="progressbar"
|
|
24
40
|
aria-valuenow={value}
|
|
25
41
|
aria-valuemin={0}
|
|
26
42
|
aria-valuemax={max}
|
|
27
43
|
{...rest}
|
|
28
44
|
>
|
|
29
|
-
<div className={cx("oc-progress-bar",
|
|
45
|
+
<div className={cx("oc-progress-bar", tone !== "default" && `oc-progress-bar--${tone}`)} style={{ width: `${pct}%` }} />
|
|
46
|
+
{marker != null && <span className="oc-progress-marker" style={{ left: `${percent(marker)}%` }} />}
|
|
30
47
|
</div>
|
|
31
48
|
);
|
|
32
49
|
});
|
package/src/components/Prose.tsx
CHANGED
|
@@ -6,8 +6,8 @@ import type { HTMLAttributes, ReactNode } from "react";
|
|
|
6
6
|
import { cx } from "../utils";
|
|
7
7
|
|
|
8
8
|
export interface ProseProps extends HTMLAttributes<HTMLElement> {
|
|
9
|
-
/** All spacing scales along. */
|
|
10
|
-
size?: "sm" | "md" | "lg";
|
|
9
|
+
/** All spacing scales along; "compact" for widgets and panels (13 px, tighter, full width). */
|
|
10
|
+
size?: "compact" | "sm" | "md" | "lg";
|
|
11
11
|
/** No max width (default 68ch). */
|
|
12
12
|
full?: boolean;
|
|
13
13
|
as?: "article" | "div" | "section";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// File: packages/react/src/components/StatusDot.tsx | Map: types -> StatusDot (span.oc-status-dot) -> Status (p.oc-status: dot + text + meta)
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
4
|
+
import { cx } from "../utils";
|
|
5
|
+
|
|
6
|
+
export type StatusTone = "default" | "working" | "waiting" | "done" | "error" | "idle";
|
|
7
|
+
|
|
8
|
+
export interface StatusDotProps extends HTMLAttributes<HTMLSpanElement> {
|
|
9
|
+
tone?: StatusTone;
|
|
10
|
+
size?: "sm" | "md" | "lg";
|
|
11
|
+
/** Says the state when the dot stands alone (role="img"); without it the dot is aria-hidden. */
|
|
12
|
+
label?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** `<span class="oc-status-dot oc-status-dot--{tone}">`: a live state light; --working pulses. */
|
|
16
|
+
export const StatusDot = forwardRef<HTMLSpanElement, StatusDotProps>(function StatusDot(
|
|
17
|
+
{ tone = "default", size = "md", label, className, ...rest },
|
|
18
|
+
ref,
|
|
19
|
+
) {
|
|
20
|
+
const a11y = label ? { role: "img", "aria-label": label } : { "aria-hidden": true };
|
|
21
|
+
return (
|
|
22
|
+
<span
|
|
23
|
+
ref={ref}
|
|
24
|
+
className={cx("oc-status-dot", tone !== "default" && `oc-status-dot--${tone}`, size !== "md" && `oc-status-dot--${size}`, className)}
|
|
25
|
+
{...a11y}
|
|
26
|
+
{...rest}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export interface StatusProps extends HTMLAttributes<HTMLParagraphElement> {
|
|
32
|
+
tone?: StatusTone;
|
|
33
|
+
/** Right-aligned extra (a time, a count) in tabular numbers. */
|
|
34
|
+
meta?: ReactNode;
|
|
35
|
+
/** The state in words; cuts off with … on one line. */
|
|
36
|
+
children?: ReactNode;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** `<p class="oc-status">`: dot + one line of state text + optional meta. */
|
|
40
|
+
export const Status = forwardRef<HTMLParagraphElement, StatusProps>(function Status(
|
|
41
|
+
{ tone = "default", meta, className, children, ...rest },
|
|
42
|
+
ref,
|
|
43
|
+
) {
|
|
44
|
+
return (
|
|
45
|
+
<p ref={ref} className={cx("oc-status", className)} {...rest}>
|
|
46
|
+
<StatusDot tone={tone} />
|
|
47
|
+
<span className="oc-status__text">{children}</span>
|
|
48
|
+
{meta != null && <span className="oc-status__meta">{meta}</span>}
|
|
49
|
+
</p>
|
|
50
|
+
);
|
|
51
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// File: packages/react/src/components/Titlebar.tsx
|
|
2
|
+
// Map: types -> TitlebarWindow (column, shared scroll timeline) -> Titlebar (main: icon, title, subtitle | actions: children)
|
|
3
|
+
// -> TitlebarButton (icon button, --close) -> TitlebarBody (scrolls; the bar gets its hairline)
|
|
4
|
+
import { forwardRef } from "react";
|
|
5
|
+
import type { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from "react";
|
|
6
|
+
import { cx } from "../utils";
|
|
7
|
+
|
|
8
|
+
/** `.oc-titlebar-window`: wraps Titlebar + TitlebarBody so the bar sees the body scroll. */
|
|
9
|
+
export const TitlebarWindow = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(function TitlebarWindow({ className, ...rest }, ref) {
|
|
10
|
+
return <div ref={ref} className={cx("oc-titlebar-window", className)} {...rest} />;
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export interface TitlebarProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
|
|
14
|
+
title?: ReactNode;
|
|
15
|
+
/** Muted text after a ·, cuts off with …. */
|
|
16
|
+
subtitle?: ReactNode;
|
|
17
|
+
/** An app icon or logo before the title. */
|
|
18
|
+
icon?: ReactNode;
|
|
19
|
+
/** Hairline all the time (no scrolling body). */
|
|
20
|
+
bordered?: boolean;
|
|
21
|
+
size?: "sm" | "md";
|
|
22
|
+
/** The action buttons (TitlebarButton). */
|
|
23
|
+
children?: ReactNode;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** `<header class="oc-titlebar">`: drag area of a frameless window; the buttons stay clickable. */
|
|
27
|
+
export const Titlebar = forwardRef<HTMLElement, TitlebarProps>(function Titlebar(
|
|
28
|
+
{ title, subtitle, icon, bordered, size = "md", className, children, ...rest },
|
|
29
|
+
ref,
|
|
30
|
+
) {
|
|
31
|
+
return (
|
|
32
|
+
<header ref={ref} className={cx("oc-titlebar", bordered && "oc-titlebar--bordered", size === "sm" && "oc-titlebar--sm", className)} {...rest}>
|
|
33
|
+
<div className="oc-titlebar__main">
|
|
34
|
+
{icon != null && <span className="oc-titlebar__icon" aria-hidden="true">{icon}</span>}
|
|
35
|
+
{title != null && <span className="oc-titlebar__title">{title}</span>}
|
|
36
|
+
{subtitle != null && <span className="oc-titlebar__subtitle">{subtitle}</span>}
|
|
37
|
+
</div>
|
|
38
|
+
{children != null && <div className="oc-titlebar__actions">{children}</div>}
|
|
39
|
+
</header>
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export interface TitlebarButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
44
|
+
/** The button's name for screen readers (icon-only button). */
|
|
45
|
+
label: string;
|
|
46
|
+
/** Red on hover. */
|
|
47
|
+
close?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** `<button class="oc-titlebar__btn">`: compact icon button in the title bar. */
|
|
51
|
+
export const TitlebarButton = forwardRef<HTMLButtonElement, TitlebarButtonProps>(function TitlebarButton(
|
|
52
|
+
{ label, close, className, type = "button", ...rest },
|
|
53
|
+
ref,
|
|
54
|
+
) {
|
|
55
|
+
return <button ref={ref} type={type} aria-label={label} title={label} className={cx("oc-titlebar__btn", close && "oc-titlebar__btn--close", className)} {...rest} />;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/** `.oc-titlebar-body`: the part of the window that scrolls. */
|
|
59
|
+
export const TitlebarBody = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(function TitlebarBody({ className, ...rest }, ref) {
|
|
60
|
+
return <div ref={ref} className={cx("oc-titlebar-body", className)} {...rest} />;
|
|
61
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// File: packages/react/src/components/Watermark.tsx | Map: types -> Watermark (span.oc-watermark + modifiers, text once or tiled)
|
|
2
|
+
import { forwardRef, Fragment } from "react";
|
|
3
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
4
|
+
import { cx } from "../utils";
|
|
5
|
+
|
|
6
|
+
export type WatermarkVariant = "diagonal" | "straight" | "tile" | "corner" | "corner-top";
|
|
7
|
+
export type WatermarkTone = "default" | "light" | "primary" | "destructive";
|
|
8
|
+
|
|
9
|
+
export interface WatermarkProps extends HTMLAttributes<HTMLSpanElement> {
|
|
10
|
+
/** The text ("Concept", "Draft"), or a logo (<img>) or icon (<svg>). */
|
|
11
|
+
text?: ReactNode;
|
|
12
|
+
variant?: WatermarkVariant;
|
|
13
|
+
tone?: WatermarkTone;
|
|
14
|
+
/** How often the text repeats with variant="tile". */
|
|
15
|
+
repeat?: number;
|
|
16
|
+
/** Turn an <img> logo into a flat gray stamp. */
|
|
17
|
+
mono?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* `<span class="oc-watermark">`: light text over its positioned parent (put it last inside an element with
|
|
22
|
+
* `className="relative"`). Click-through and aria-hidden: say "draft" in the content itself when it matters.
|
|
23
|
+
*/
|
|
24
|
+
export const Watermark = forwardRef<HTMLSpanElement, WatermarkProps>(function Watermark(
|
|
25
|
+
{ text = "Concept", variant = "diagonal", tone = "default", repeat = 12, mono = false, className, ...rest },
|
|
26
|
+
ref,
|
|
27
|
+
) {
|
|
28
|
+
const classes = cx("oc-watermark", variant !== "diagonal" && `oc-watermark--${variant}`, tone !== "default" && `oc-watermark--${tone}`, mono && "oc-watermark--mono", className);
|
|
29
|
+
const count = variant === "tile" ? Math.max(1, repeat) : 1;
|
|
30
|
+
return (
|
|
31
|
+
<span ref={ref} className={classes} aria-hidden="true" {...rest}>
|
|
32
|
+
{Array.from({ length: count }, (_, i) => (typeof text === "string" || typeof text === "number" ? <span key={i}>{text}</span> : <Fragment key={i}>{text}</Fragment>))}
|
|
33
|
+
</span>
|
|
34
|
+
);
|
|
35
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -44,6 +44,7 @@ export * from "./components/FactList";
|
|
|
44
44
|
export * from "./components/FilterCounts";
|
|
45
45
|
export * from "./components/FlipCard";
|
|
46
46
|
export * from "./components/FormField";
|
|
47
|
+
export * from "./components/Glass";
|
|
47
48
|
export * from "./components/Greeting";
|
|
48
49
|
export * from "./components/Heading";
|
|
49
50
|
export * from "./components/Icon";
|
|
@@ -54,7 +55,9 @@ export * from "./components/LanguageSelect";
|
|
|
54
55
|
export * from "./components/LauncherCard";
|
|
55
56
|
export * from "./components/Lightbox";
|
|
56
57
|
export * from "./components/LinkArrow";
|
|
58
|
+
export * from "./components/LinkRow";
|
|
57
59
|
export * from "./components/ListToolbar";
|
|
60
|
+
export * from "./components/LiveFrame";
|
|
58
61
|
export * from "./components/Marquee";
|
|
59
62
|
export * from "./components/MediaCard";
|
|
60
63
|
export * from "./components/MediaRow";
|
|
@@ -88,6 +91,7 @@ export * from "./components/SortableRow";
|
|
|
88
91
|
export * from "./components/StackCard";
|
|
89
92
|
export * from "./components/StackList";
|
|
90
93
|
export * from "./components/StatCard";
|
|
94
|
+
export * from "./components/StatusDot";
|
|
91
95
|
export * from "./components/Stepper";
|
|
92
96
|
export * from "./components/Switch";
|
|
93
97
|
export * from "./components/Table";
|
|
@@ -100,6 +104,7 @@ export * from "./components/Textarea";
|
|
|
100
104
|
export * from "./components/ThemeSwitch";
|
|
101
105
|
export * from "./components/TimeInput";
|
|
102
106
|
export * from "./components/Timeline";
|
|
107
|
+
export * from "./components/Titlebar";
|
|
103
108
|
export * from "./components/Toast";
|
|
104
109
|
export * from "./components/ToggleField";
|
|
105
110
|
export * from "./components/Tooltip";
|
|
@@ -108,6 +113,7 @@ export * from "./components/Tree";
|
|
|
108
113
|
export * from "./components/UpgradeBanner";
|
|
109
114
|
export * from "./components/User";
|
|
110
115
|
export * from "./components/VersionBanner";
|
|
116
|
+
export * from "./components/Watermark";
|
|
111
117
|
export * from "./blocks/BlockAboutTimeline";
|
|
112
118
|
export * from "./blocks/BlockAppHeader";
|
|
113
119
|
export * from "./blocks/BlockAppShell";
|