@rackbops/ui-react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ import type { ButtonHTMLAttributes } from "react";
2
+ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
3
+ /** Visual emphasis. Maps to .rb-btn--{variant}. */
4
+ variant?: "default" | "primary" | "accent" | "danger" | "ghost";
5
+ }
6
+ export declare function Button({ variant, type, className, ...rest }: ButtonProps): import("react").JSX.Element;
package/dist/Button.js ADDED
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from "./cx.js";
3
+ export function Button({ variant = "default", type = "button", className, ...rest }) {
4
+ // Default type="button" so a Button inside a <form> doesn't submit it on
5
+ // click; pass type="submit" explicitly when that's what you want.
6
+ return (_jsx("button", { type: type, className: cx("rb-btn", variant !== "default" && `rb-btn--${variant}`, className), ...rest }));
7
+ }
package/dist/Card.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { HTMLAttributes } from "react";
2
+ export interface CardProps extends HTMLAttributes<HTMLDivElement> {
3
+ /** Raised elevation (.rb-card--raised); themes that don't define it fall back. */
4
+ raised?: boolean;
5
+ }
6
+ export declare function Card({ raised, className, ...rest }: CardProps): import("react").JSX.Element;
package/dist/Card.js ADDED
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from "./cx.js";
3
+ export function Card({ raised, className, ...rest }) {
4
+ return _jsx("div", { className: cx("rb-card", raised && "rb-card--raised", className), ...rest });
5
+ }
@@ -0,0 +1,11 @@
1
+ import { type ReactNode } from "react";
2
+ export interface DialogProps {
3
+ open: boolean;
4
+ onClose?: () => void;
5
+ title?: ReactNode;
6
+ actions?: ReactNode;
7
+ children?: ReactNode;
8
+ className?: string;
9
+ }
10
+ /** A native <dialog> driven by the `open` prop (showModal/close). */
11
+ export declare function Dialog({ open, onClose, title, actions, children, className }: DialogProps): import("react").JSX.Element;
package/dist/Dialog.js ADDED
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useRef } from "react";
3
+ import { cx } from "./cx.js";
4
+ /** A native <dialog> driven by the `open` prop (showModal/close). */
5
+ export function Dialog({ open, onClose, title, actions, children, className }) {
6
+ const ref = useRef(null);
7
+ const titleId = useId();
8
+ useEffect(() => {
9
+ const el = ref.current;
10
+ if (!el)
11
+ return;
12
+ if (open && !el.open)
13
+ el.showModal();
14
+ else if (!open && el.open)
15
+ el.close();
16
+ }, [open]);
17
+ return (_jsxs("dialog", { ref: ref, className: cx("rb-dialog", className), onClose: onClose, "aria-labelledby": title !== undefined ? titleId : undefined, children: [_jsxs("div", { className: "rb-dialog__body", children: [title !== undefined && (_jsx("h2", { id: titleId, className: "rb-dialog__title", children: title })), children] }), actions !== undefined && _jsx("div", { className: "rb-dialog__actions", children: actions })] }));
18
+ }
@@ -0,0 +1,29 @@
1
+ import type { HTMLAttributes } from "react";
2
+ export interface LinkUrl {
3
+ label: string;
4
+ url: string;
5
+ }
6
+ export interface LinkCategory {
7
+ id: string;
8
+ label: string;
9
+ }
10
+ export interface LinkItem {
11
+ /** Also used as the React list key -- app/site names are unique. */
12
+ name: string;
13
+ urls: LinkUrl[];
14
+ description?: string;
15
+ /** Matches a LinkCategory.id; anything unlisted falls into an "Other" group. */
16
+ category: string;
17
+ host?: string;
18
+ monitored?: boolean;
19
+ }
20
+ export interface LinksIndexProps extends HTMLAttributes<HTMLDivElement> {
21
+ categories: LinkCategory[];
22
+ links: LinkItem[];
23
+ }
24
+ /**
25
+ * A generic, data-driven grouped index of apps/sites. Iterates `categories` in order, each a group
26
+ * of `rb-card`s; links whose `category` is not listed collect into a trailing "Other" group. No
27
+ * router assumed -- external URLs open in a new tab. Themed by the consumer's `data-rb-style`.
28
+ */
29
+ export declare function LinksIndex({ categories, links, className, ...rest }: LinksIndexProps): import("react").JSX.Element;
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Card } from "./Card.js";
3
+ import { Badge } from "./feedback.js";
4
+ // Structural inline layout only -- the card/badge surfaces + colour come from @rackbops/styles
5
+ // (rb-card/rb-badge + tokens), so this adds no @rackbops/styles classes and the theme contract is
6
+ // untouched. (Bare-tag heading typography still follows each theme's own base rules.)
7
+ const gridStyle = {
8
+ display: "grid",
9
+ gridTemplateColumns: "repeat(auto-fill, minmax(16rem, 1fr))",
10
+ gap: "var(--rb-space-4)",
11
+ };
12
+ // Reset the url list structurally (no bullets/indent) so it renders the same under every theme --
13
+ // the themes don't all reset a bare `ul`.
14
+ const listStyle = { listStyle: "none", margin: 0, padding: 0 };
15
+ // External = an http(s) or protocol-relative (`//host`) url -> opens in a new tab; anything else
16
+ // (relative, mailto:, tel:, ...) stays a plain in-page link.
17
+ const isExternal = (url) => /^(https?:)?\/\//i.test(url);
18
+ function LinkCard({ link }) {
19
+ return (_jsxs(Card, { children: [_jsxs("h3", { children: [link.name, link.monitored ? (_jsxs(_Fragment, { children: [" ", _jsx(Badge, { variant: "success", children: "monitored" })] })) : null] }), link.description ? _jsx("p", { children: link.description }) : null, _jsx("ul", { style: listStyle, children: link.urls.map((u) => (_jsx("li", { children: isExternal(u.url) ? (_jsx("a", { href: u.url, target: "_blank", rel: "noreferrer noopener", children: u.label })) : (_jsx("a", { href: u.url, children: u.label })) }, u.url))) }), link.host ? _jsx("p", { children: link.host }) : null] }));
20
+ }
21
+ function Group({ label, items }) {
22
+ return (_jsxs("section", { children: [_jsx("h2", { children: label }), _jsx("div", { style: gridStyle, children: items.map((link) => (_jsx(LinkCard, { link: link }, link.name))) })] }));
23
+ }
24
+ /**
25
+ * A generic, data-driven grouped index of apps/sites. Iterates `categories` in order, each a group
26
+ * of `rb-card`s; links whose `category` is not listed collect into a trailing "Other" group. No
27
+ * router assumed -- external URLs open in a new tab. Themed by the consumer's `data-rb-style`.
28
+ */
29
+ export function LinksIndex({ categories, links, className, ...rest }) {
30
+ const known = new Set(categories.map((c) => c.id));
31
+ const other = links.filter((l) => !known.has(l.category));
32
+ return (_jsxs("div", { className: className, ...rest, children: [categories.map((cat) => {
33
+ const items = links.filter((l) => l.category === cat.id);
34
+ return items.length > 0 ? _jsx(Group, { label: cat.label, items: items }, cat.id) : null;
35
+ }), other.length > 0 ? _jsx(Group, { label: "Other", items: other }) : null] }));
36
+ }
@@ -0,0 +1,6 @@
1
+ import type { AnchorHTMLAttributes } from "react";
2
+ export interface NavLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
3
+ /** Marks the active route (.rb-link--active). */
4
+ active?: boolean;
5
+ }
6
+ export declare function NavLink({ active, className, ...rest }: NavLinkProps): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx } from "./cx.js";
3
+ export function NavLink({ active, className, ...rest }) {
4
+ return (_jsx("a", { "aria-current": active ? "page" : undefined, className: cx("rb-link", active && "rb-link--active", className), ...rest }));
5
+ }
package/dist/Tabs.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { type ReactNode } from "react";
2
+ export interface TabItem {
3
+ id: string;
4
+ label: ReactNode;
5
+ content: ReactNode;
6
+ }
7
+ export interface TabsProps {
8
+ items: TabItem[];
9
+ /** Initially active tab id; defaults to the first item. */
10
+ defaultId?: string;
11
+ className?: string;
12
+ }
13
+ export declare function Tabs({ items, defaultId, className }: TabsProps): import("react").JSX.Element;
package/dist/Tabs.js ADDED
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useId, useRef, useState } from "react";
3
+ import { cx } from "./cx.js";
4
+ export function Tabs({ items, defaultId, className }) {
5
+ const base = useId();
6
+ const [active, setActive] = useState(defaultId ?? items[0]?.id);
7
+ // Fall back to the first tab if `active` names no item, so exactly one tab is
8
+ // always selected (a defaultId that matches nothing still highlights a tab).
9
+ const activeId = items.some((t) => t.id === active) ? active : items[0]?.id;
10
+ const tabRefs = useRef([]);
11
+ const onKeyDown = (e) => {
12
+ const i = items.findIndex((t) => t.id === activeId);
13
+ let next = i;
14
+ if (e.key === "ArrowRight" || e.key === "ArrowDown")
15
+ next = (i + 1) % items.length;
16
+ else if (e.key === "ArrowLeft" || e.key === "ArrowUp")
17
+ next = (i - 1 + items.length) % items.length;
18
+ else if (e.key === "Home")
19
+ next = 0;
20
+ else if (e.key === "End")
21
+ next = items.length - 1;
22
+ else
23
+ return;
24
+ e.preventDefault();
25
+ setActive(items[next]?.id);
26
+ // Move focus with the selection — the whole point of the roving tabindex.
27
+ tabRefs.current[next]?.focus();
28
+ };
29
+ return (_jsxs("div", { className: className, children: [_jsx("div", { className: "rb-tabs", role: "tablist", children: items.map((t, idx) => {
30
+ const selected = t.id === activeId;
31
+ return (_jsx("button", { ref: (el) => {
32
+ tabRefs.current[idx] = el;
33
+ }, type: "button", role: "tab", id: `${base}-tab-${t.id}`, "aria-selected": selected, "aria-controls": `${base}-panel-${t.id}`, tabIndex: selected ? 0 : -1, className: cx("rb-tab", selected && "rb-tab--active"), onClick: () => setActive(t.id), onKeyDown: onKeyDown, children: t.label }, t.id));
34
+ }) }), items.map((t) => {
35
+ const selected = t.id === activeId;
36
+ return (_jsx("div", { className: "rb-tabpanel", role: "tabpanel", id: `${base}-panel-${t.id}`, "aria-labelledby": `${base}-tab-${t.id}`, hidden: !selected, tabIndex: 0, children: t.content }, t.id));
37
+ })] }));
38
+ }
package/dist/cx.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function cx(...parts: Array<string | false | null | undefined>): string;
package/dist/cx.js ADDED
@@ -0,0 +1,3 @@
1
+ export function cx(...parts) {
2
+ return parts.filter(Boolean).join(" ");
3
+ }
@@ -0,0 +1,14 @@
1
+ import type { HTMLAttributes, ProgressHTMLAttributes, ReactNode } from "react";
2
+ export type SemanticVariant = "info" | "success" | "warning" | "danger";
3
+ export interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
4
+ variant?: SemanticVariant;
5
+ }
6
+ export declare function Badge({ variant, className, ...rest }: BadgeProps): import("react").JSX.Element;
7
+ export interface AlertProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
8
+ variant?: SemanticVariant;
9
+ title?: ReactNode;
10
+ }
11
+ export declare function Alert({ variant, title, className, children, ...rest }: AlertProps): import("react").JSX.Element;
12
+ export type ProgressProps = ProgressHTMLAttributes<HTMLProgressElement>;
13
+ export declare function Progress({ className, ...rest }: ProgressProps): import("react").JSX.Element;
14
+ export declare function Spinner({ className, ...rest }: HTMLAttributes<HTMLSpanElement>): import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from "./cx.js";
3
+ export function Badge({ variant, className, ...rest }) {
4
+ return (_jsx("span", { className: cx("rb-badge", variant && `rb-badge--${variant}`, className), ...rest }));
5
+ }
6
+ export function Alert({ variant, title, className, children, ...rest }) {
7
+ return (_jsxs("div", { role: "alert", className: cx("rb-alert", variant && `rb-alert--${variant}`, className), ...rest, children: [title !== undefined && _jsx("div", { className: "rb-alert__title", children: title }), children] }));
8
+ }
9
+ export function Progress({ className, ...rest }) {
10
+ return _jsx("progress", { className: cx("rb-progress", className), ...rest });
11
+ }
12
+ export function Spinner({ className, ...rest }) {
13
+ return (_jsx("span", { role: "status", "aria-label": "Loading", className: cx("rb-spinner", className), ...rest }));
14
+ }
package/dist/form.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import type { HTMLAttributes, InputHTMLAttributes, LabelHTMLAttributes, ReactNode, SelectHTMLAttributes, TextareaHTMLAttributes } from "react";
2
+ export type InputProps = InputHTMLAttributes<HTMLInputElement>;
3
+ export declare function Input({ className, ...rest }: InputProps): import("react").JSX.Element;
4
+ export type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement>;
5
+ export declare function Textarea({ className, ...rest }: TextareaProps): import("react").JSX.Element;
6
+ export type SelectProps = SelectHTMLAttributes<HTMLSelectElement>;
7
+ export declare function Select({ className, ...rest }: SelectProps): import("react").JSX.Element;
8
+ export type LabelProps = LabelHTMLAttributes<HTMLLabelElement>;
9
+ export declare function Label({ className, ...rest }: LabelProps): import("react").JSX.Element;
10
+ export declare function Field({ className, ...rest }: HTMLAttributes<HTMLDivElement>): import("react").JSX.Element;
11
+ export interface ChoiceProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type"> {
12
+ /** Optional label text; when set the control is wrapped in a .rb-choice row. */
13
+ label?: ReactNode;
14
+ }
15
+ export declare function Checkbox({ label, className, ...rest }: ChoiceProps): import("react").JSX.Element;
16
+ export declare function Radio({ label, className, ...rest }: ChoiceProps): import("react").JSX.Element;
17
+ export declare function Switch({ label, className, ...rest }: ChoiceProps): import("react").JSX.Element;
package/dist/form.js ADDED
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cx } from "./cx.js";
3
+ export function Input({ className, ...rest }) {
4
+ return _jsx("input", { className: cx("rb-input", className), ...rest });
5
+ }
6
+ export function Textarea({ className, ...rest }) {
7
+ return _jsx("textarea", { className: cx("rb-textarea", className), ...rest });
8
+ }
9
+ export function Select({ className, ...rest }) {
10
+ return _jsx("select", { className: cx("rb-select", className), ...rest });
11
+ }
12
+ export function Label({ className, ...rest }) {
13
+ return _jsx("label", { className: cx("rb-label", className), ...rest });
14
+ }
15
+ export function Field({ className, ...rest }) {
16
+ return _jsx("div", { className: cx("rb-field", className), ...rest });
17
+ }
18
+ /** A checkbox/radio/switch control, optionally wrapped with its label. */
19
+ function ChoiceControl({ label, control }) {
20
+ if (label === undefined)
21
+ return _jsx(_Fragment, { children: control });
22
+ return (_jsxs("label", { className: "rb-choice", children: [control, label] }));
23
+ }
24
+ export function Checkbox({ label, className, ...rest }) {
25
+ return (_jsx(ChoiceControl, { label: label, control: _jsx("input", { type: "checkbox", className: cx("rb-checkbox", className), ...rest }) }));
26
+ }
27
+ export function Radio({ label, className, ...rest }) {
28
+ return (_jsx(ChoiceControl, { label: label, control: _jsx("input", { type: "radio", className: cx("rb-radio", className), ...rest }) }));
29
+ }
30
+ export function Switch({ label, className, ...rest }) {
31
+ return (_jsx(ChoiceControl, { label: label, control: _jsx("input", { type: "checkbox", role: "switch", className: cx("rb-switch", className), ...rest }) }));
32
+ }
@@ -0,0 +1,9 @@
1
+ export { Button, type ButtonProps } from "./Button.js";
2
+ export { Card, type CardProps } from "./Card.js";
3
+ export { NavLink, type NavLinkProps } from "./NavLink.js";
4
+ export { Input, Textarea, Select, Label, Field, Checkbox, Radio, Switch, type InputProps, type TextareaProps, type SelectProps, type LabelProps, type ChoiceProps, } from "./form.js";
5
+ export { Badge, Alert, Progress, Spinner, type BadgeProps, type AlertProps, type ProgressProps, type SemanticVariant, } from "./feedback.js";
6
+ export { Dialog, type DialogProps } from "./Dialog.js";
7
+ export { Tabs, type TabsProps, type TabItem } from "./Tabs.js";
8
+ export { LinksIndex, type LinksIndexProps, type LinkItem, type LinkCategory, type LinkUrl, } from "./LinksIndex.js";
9
+ export { cx } from "./cx.js";
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { Button } from "./Button.js";
2
+ export { Card } from "./Card.js";
3
+ export { NavLink } from "./NavLink.js";
4
+ export { Input, Textarea, Select, Label, Field, Checkbox, Radio, Switch, } from "./form.js";
5
+ export { Badge, Alert, Progress, Spinner, } from "./feedback.js";
6
+ export { Dialog } from "./Dialog.js";
7
+ export { Tabs } from "./Tabs.js";
8
+ export { LinksIndex, } from "./LinksIndex.js";
9
+ export { cx } from "./cx.js";
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@rackbops/ui-react",
3
+ "version": "0.1.0",
4
+ "description": "React components styled by @rackbops/styles rackbops themes.",
5
+ "license": "UNLICENSED",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/roshne/rackbops-ui-ux-std-lib.git",
9
+ "directory": "components/react"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "type": "module",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ }
23
+ },
24
+ "scripts": {
25
+ "build": "tsc -p tsconfig.build.json",
26
+ "test": "tsc -p tsconfig.json --noEmit && node --import tsx --test \"src/**/*.test.tsx\""
27
+ },
28
+ "peerDependencies": {
29
+ "react": ">=18"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^22.0.0",
33
+ "@types/react": "^19.0.0",
34
+ "@types/react-dom": "^19.0.0",
35
+ "react": "^19.0.0",
36
+ "react-dom": "^19.0.0",
37
+ "tsx": "^4.19.0",
38
+ "typescript": "^5.7.0"
39
+ }
40
+ }