@ratio-ui/core 0.1.1 → 0.2.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.
- package/README.md +2 -0
- package/dist/catalogue.json +1719 -43
- package/dist/components/app-shell/AppShell.d.ts +22 -0
- package/dist/components/app-shell/AppShell.js +39 -0
- package/dist/components/app-shell/context.js +5 -0
- package/dist/components/app-shell/index.d.ts +2 -0
- package/dist/components/app-shell/index.js +1 -0
- package/dist/components/avatar/Avatar.d.ts +22 -0
- package/dist/components/avatar/Avatar.js +38 -0
- package/dist/components/avatar/index.d.ts +2 -0
- package/dist/components/avatar/index.js +1 -0
- package/dist/components/button/Button.d.ts +37 -0
- package/dist/components/button/Button.js +41 -0
- package/dist/components/button/index.d.ts +2 -0
- package/dist/components/button/index.js +1 -0
- package/dist/components/card/Card.d.ts +33 -0
- package/dist/components/card/Card.js +53 -0
- package/dist/components/card/index.d.ts +2 -0
- package/dist/components/card/index.js +1 -0
- package/dist/components/checkbox/Checkbox.d.ts +10 -0
- package/dist/components/checkbox/Checkbox.js +28 -0
- package/dist/components/checkbox/index.d.ts +2 -0
- package/dist/components/checkbox/index.js +1 -0
- package/dist/components/data-table/DataTable.d.ts +65 -0
- package/dist/components/data-table/DataTable.js +141 -0
- package/dist/components/data-table/index.d.ts +2 -0
- package/dist/components/data-table/index.js +1 -0
- package/dist/components/definition/Definition.d.ts +14 -0
- package/dist/components/definition/Definition.js +29 -0
- package/dist/components/definition/index.d.ts +2 -0
- package/dist/components/definition/index.js +1 -0
- package/dist/components/dialog/Dialog.d.ts +44 -0
- package/dist/components/dialog/Dialog.js +79 -0
- package/dist/components/dialog/index.d.ts +2 -0
- package/dist/components/dialog/index.js +1 -0
- package/dist/components/dropdown-menu/DropdownMenu.d.ts +73 -0
- package/dist/components/dropdown-menu/DropdownMenu.js +116 -0
- package/dist/components/dropdown-menu/index.d.ts +2 -0
- package/dist/components/dropdown-menu/index.js +1 -0
- package/dist/components/empty-state/EmptyState.d.ts +15 -0
- package/dist/components/empty-state/EmptyState.js +30 -0
- package/dist/components/empty-state/index.d.ts +2 -0
- package/dist/components/empty-state/index.js +1 -0
- package/dist/components/filter-tabs/FilterTabs.d.ts +28 -0
- package/dist/components/filter-tabs/FilterTabs.js +37 -0
- package/dist/components/filter-tabs/index.d.ts +2 -0
- package/dist/components/filter-tabs/index.js +1 -0
- package/dist/components/global-search/GlobalSearch.d.ts +51 -0
- package/dist/components/global-search/GlobalSearch.js +144 -0
- package/dist/components/global-search/index.d.ts +2 -0
- package/dist/components/global-search/index.js +1 -0
- package/dist/components/icon/registry.d.ts +3 -2
- package/dist/components/icon/registry.js +53 -1
- package/dist/components/input/Input.d.ts +19 -0
- package/dist/components/input/Input.js +26 -0
- package/dist/components/input/index.d.ts +2 -0
- package/dist/components/input/index.js +1 -0
- package/dist/components/input-group/InputGroup.d.ts +42 -0
- package/dist/components/input-group/InputGroup.js +83 -0
- package/dist/components/input-group/index.d.ts +2 -0
- package/dist/components/input-group/index.js +1 -0
- package/dist/components/kbd/Kbd.d.ts +17 -0
- package/dist/components/kbd/Kbd.js +21 -0
- package/dist/components/kbd/index.d.ts +2 -0
- package/dist/components/kbd/index.js +1 -0
- package/dist/components/link-provider/LinkProvider.d.ts +29 -0
- package/dist/components/link-provider/LinkProvider.js +15 -0
- package/dist/components/link-provider/RatioLink.js +18 -0
- package/dist/components/link-provider/context.js +4 -0
- package/dist/components/link-provider/index.d.ts +2 -0
- package/dist/components/link-provider/index.js +1 -0
- package/dist/components/metric-strip/MetricStrip.d.ts +23 -0
- package/dist/components/metric-strip/MetricStrip.js +39 -0
- package/dist/components/metric-strip/index.d.ts +2 -0
- package/dist/components/metric-strip/index.js +1 -0
- package/dist/components/page-header/PageHeader.d.ts +37 -0
- package/dist/components/page-header/PageHeader.js +62 -0
- package/dist/components/page-header/index.d.ts +2 -0
- package/dist/components/page-header/index.js +1 -0
- package/dist/components/pagination/Pagination.d.ts +18 -0
- package/dist/components/pagination/Pagination.js +59 -0
- package/dist/components/pagination/index.d.ts +2 -0
- package/dist/components/pagination/index.js +1 -0
- package/dist/components/popover/Popover.d.ts +27 -0
- package/dist/components/popover/Popover.js +47 -0
- package/dist/components/popover/index.d.ts +2 -0
- package/dist/components/popover/index.js +1 -0
- package/dist/components/search-field/SearchField.d.ts +23 -0
- package/dist/components/search-field/SearchField.js +66 -0
- package/dist/components/search-field/index.d.ts +2 -0
- package/dist/components/search-field/index.js +1 -0
- package/dist/components/side-nav/SideNav.d.ts +64 -0
- package/dist/components/side-nav/SideNav.js +194 -0
- package/dist/components/side-nav/index.d.ts +2 -0
- package/dist/components/side-nav/index.js +1 -0
- package/dist/components/skeleton/Skeleton.d.ts +13 -0
- package/dist/components/skeleton/Skeleton.js +21 -0
- package/dist/components/skeleton/index.d.ts +2 -0
- package/dist/components/skeleton/index.js +1 -0
- package/dist/components/spinner/Spinner.d.ts +11 -0
- package/dist/components/spinner/Spinner.js +16 -0
- package/dist/components/spinner/index.d.ts +2 -0
- package/dist/components/spinner/index.js +1 -0
- package/dist/components/status-pill/StatusPill.d.ts +14 -0
- package/dist/components/status-pill/StatusPill.js +19 -0
- package/dist/components/status-pill/index.d.ts +2 -0
- package/dist/components/status-pill/index.js +1 -0
- package/dist/components/table/Table.d.ts +46 -0
- package/dist/components/table/Table.js +87 -0
- package/dist/components/table/index.d.ts +2 -0
- package/dist/components/table/index.js +1 -0
- package/dist/components/table-loader/TableLoader.d.ts +31 -0
- package/dist/components/table-loader/TableLoader.js +35 -0
- package/dist/components/table-loader/index.d.ts +2 -0
- package/dist/components/table-loader/index.js +1 -0
- package/dist/components/tabs/Tabs.d.ts +23 -0
- package/dist/components/tabs/Tabs.js +41 -0
- package/dist/components/tabs/index.d.ts +2 -0
- package/dist/components/tabs/index.js +1 -0
- package/dist/components/toaster/Toaster.d.ts +32 -0
- package/dist/components/toaster/Toaster.js +89 -0
- package/dist/components/toaster/index.d.ts +2 -0
- package/dist/components/toaster/index.js +1 -0
- package/dist/components/tooltip/Tooltip.d.ts +18 -0
- package/dist/components/tooltip/Tooltip.js +35 -0
- package/dist/components/tooltip/index.d.ts +2 -0
- package/dist/components/tooltip/index.js +1 -0
- package/dist/components/top-bar/StoreSwitcher.d.ts +26 -0
- package/dist/components/top-bar/StoreSwitcher.js +48 -0
- package/dist/components/top-bar/TopBar.d.ts +24 -0
- package/dist/components/top-bar/TopBar.js +53 -0
- package/dist/components/top-bar/index.d.ts +3 -0
- package/dist/components/top-bar/index.js +3 -0
- package/dist/index.d.ts +65 -1
- package/dist/index.js +35 -2
- package/dist/lib/cn.js +32 -27
- package/dist/lib/format.d.ts +31 -0
- package/dist/lib/format.js +65 -0
- package/dist/styles.css +1 -1
- package/package.json +128 -3
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { JSX, ReactNode } from "react";
|
|
2
|
+
/** The props of AppShell. */
|
|
3
|
+
export type AppShellProps = {
|
|
4
|
+
/** The side navigation, for example SideNav. It sits at the left of the top bar and the content. */
|
|
5
|
+
sideNav?: ReactNode;
|
|
6
|
+
/** The top bar, for example TopBar. It spans the width beside the side nav. */
|
|
7
|
+
topBar?: ReactNode;
|
|
8
|
+
/** Whether the side navigation drawer is open on a phone. Set it with onNavOpenChange to control the drawer. */
|
|
9
|
+
navOpen?: boolean;
|
|
10
|
+
/** Whether the drawer starts open on a phone when navOpen is not set. Defaults to false. */
|
|
11
|
+
defaultNavOpen?: boolean;
|
|
12
|
+
/** Called when the drawer asks to open or close on a phone: the menu button in TopBar, Escape, or choosing a row. */
|
|
13
|
+
onNavOpenChange?: (open: boolean) => void;
|
|
14
|
+
/** The page content, centred in a column that is 1152px wide at most. */
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The frame of every page: the side nav at the left, the top bar above and the content below it. It fills the height of its
|
|
19
|
+
* container, so the app gives it a height and the content scrolls beside the side nav and under the top bar. The parts are slots, so the shell does not depend on their components.
|
|
20
|
+
* On a phone the side nav is a drawer: the menu button in TopBar opens it, and Escape or choosing a row closes it.
|
|
21
|
+
*/
|
|
22
|
+
export declare function AppShell({ sideNav, topBar, navOpen, defaultNavOpen, onNavOpenChange, children }: AppShellProps): JSX.Element;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ShellNavContext } from "./context.js";
|
|
2
|
+
import { useId, useState } from "react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
// src/components/app-shell/AppShell.tsx
|
|
5
|
+
/**
|
|
6
|
+
* The frame of every page: the side nav at the left, the top bar above and the content below it. It fills the height of its
|
|
7
|
+
* container, so the app gives it a height and the content scrolls beside the side nav and under the top bar. The parts are slots, so the shell does not depend on their components.
|
|
8
|
+
* On a phone the side nav is a drawer: the menu button in TopBar opens it, and Escape or choosing a row closes it.
|
|
9
|
+
*/
|
|
10
|
+
function AppShell({ sideNav, topBar, navOpen, defaultNavOpen = false, onNavOpenChange, children }) {
|
|
11
|
+
const [inner, setInner] = useState(defaultNavOpen);
|
|
12
|
+
const id = useId();
|
|
13
|
+
const open = navOpen ?? inner;
|
|
14
|
+
const setOpen = (next) => {
|
|
15
|
+
if (navOpen === void 0) setInner(next);
|
|
16
|
+
onNavOpenChange?.(next);
|
|
17
|
+
};
|
|
18
|
+
return /* @__PURE__ */ jsx(ShellNavContext, {
|
|
19
|
+
value: sideNav ? {
|
|
20
|
+
open,
|
|
21
|
+
setOpen,
|
|
22
|
+
id
|
|
23
|
+
} : void 0,
|
|
24
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
25
|
+
className: "rt:flex rt:h-full rt:bg-page rt:font-sans rt:text-foreground",
|
|
26
|
+
children: [sideNav, /* @__PURE__ */ jsxs("div", {
|
|
27
|
+
className: "rt:flex rt:min-w-0 rt:flex-1 rt:flex-col",
|
|
28
|
+
children: [topBar, /* @__PURE__ */ jsx("main", {
|
|
29
|
+
className: "rt:min-h-0 rt:flex-1 rt:overflow-y-auto rt:px-3 rt:py-6 rt:md:px-6",
|
|
30
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
31
|
+
className: "rt:mx-auto rt:w-full rt:max-w-content",
|
|
32
|
+
children
|
|
33
|
+
})
|
|
34
|
+
})]
|
|
35
|
+
})]
|
|
36
|
+
})
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export { AppShell };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AppShell } from "./AppShell.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { JSX, Ref } from "react";
|
|
2
|
+
/** The size of an avatar: sm is 24px, default 32px and lg 40px. */
|
|
3
|
+
export type AvatarSize = 'sm' | 'default' | 'lg';
|
|
4
|
+
/** The shape of an avatar: round for a person, square for a store. */
|
|
5
|
+
export type AvatarShape = 'round' | 'square';
|
|
6
|
+
/** The props of Avatar. */
|
|
7
|
+
export type AvatarProps = {
|
|
8
|
+
/** The full name of the person or store. It is the accessible label, and its first letters are the initials. */
|
|
9
|
+
name: string;
|
|
10
|
+
/** The address of a picture. The initials show until it loads and when it fails. */
|
|
11
|
+
src?: string;
|
|
12
|
+
/** Round for a person, square for a store. Defaults to round. */
|
|
13
|
+
shape?: AvatarShape;
|
|
14
|
+
/** Defaults to default, which is 32px. */
|
|
15
|
+
size?: AvatarSize;
|
|
16
|
+
/** Receives the rendered element. */
|
|
17
|
+
ref?: Ref<HTMLSpanElement>;
|
|
18
|
+
};
|
|
19
|
+
/** The first letters of the first two words of a name, in capitals: "My Store" gives MS, and an empty name gives nothing. */
|
|
20
|
+
export declare function getInitials(name: string): string;
|
|
21
|
+
/** A picture or the initials of a person or a store: round for people, square for stores. */
|
|
22
|
+
export declare function Avatar({ name, src, shape, size, ref }: AvatarProps): JSX.Element;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn.js";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Avatar } from "@base-ui/react/avatar";
|
|
4
|
+
// src/components/avatar/Avatar.tsx
|
|
5
|
+
/** The first letters of the first two words of a name, in capitals: "My Store" gives MS, and an empty name gives nothing. */
|
|
6
|
+
function getInitials(name) {
|
|
7
|
+
return name.trim().split(/\s+/).filter(Boolean).slice(0, 2).map((word) => Array.from(word)[0]?.toUpperCase() ?? "").join("");
|
|
8
|
+
}
|
|
9
|
+
const SIZE_CLASSES = {
|
|
10
|
+
sm: "rt:size-6 rt:text-c2",
|
|
11
|
+
default: "rt:size-8 rt:text-c2",
|
|
12
|
+
lg: "rt:size-10 rt:text-b2"
|
|
13
|
+
};
|
|
14
|
+
const SHAPE_CLASSES = {
|
|
15
|
+
round: "rt:rounded-3xl",
|
|
16
|
+
square: "rt:rounded-md"
|
|
17
|
+
};
|
|
18
|
+
/** A picture or the initials of a person or a store: round for people, square for stores. */
|
|
19
|
+
function Avatar$1({ name, src, shape = "round", size = "default", ref }) {
|
|
20
|
+
const radius = SHAPE_CLASSES[shape];
|
|
21
|
+
return /* @__PURE__ */ jsxs(Avatar.Root, {
|
|
22
|
+
ref,
|
|
23
|
+
role: "img",
|
|
24
|
+
"aria-label": name,
|
|
25
|
+
"data-shape": shape,
|
|
26
|
+
"data-size": size,
|
|
27
|
+
className: cn("rt:relative rt:inline-flex rt:shrink-0 rt:overflow-hidden rt:select-none", SIZE_CLASSES[size], radius),
|
|
28
|
+
children: [src !== void 0 && /* @__PURE__ */ jsx(Avatar.Image, {
|
|
29
|
+
src,
|
|
30
|
+
alt: "",
|
|
31
|
+
className: "rt:aspect-square rt:size-full rt:object-cover"
|
|
32
|
+
}), /* @__PURE__ */ jsx(Avatar.Fallback, {
|
|
33
|
+
className: cn("rt:flex rt:size-full rt:items-center rt:justify-center rt:bg-muted rt:text-foreground-subdued", radius),
|
|
34
|
+
children: getInitials(name)
|
|
35
|
+
})]
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export { Avatar$1 as Avatar, getInitials };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Avatar, getInitials } from "./Avatar.js";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { JSX, Ref } from "react";
|
|
2
|
+
import { Button } from "@base-ui/react/button";
|
|
3
|
+
/**
|
|
4
|
+
* How a button looks. main is the filled accent action, one per view. secondary is a ring on a transparent fill. ghost is
|
|
5
|
+
* text only with a fill on hover, link is accent text with an underline on hover. destructive is for actions that delete
|
|
6
|
+
* or remove, and needs a confirm step after it.
|
|
7
|
+
*/
|
|
8
|
+
export type ButtonVariant = 'main' | 'secondary' | 'ghost' | 'link' | 'destructive';
|
|
9
|
+
interface ButtonBaseProps extends Omit<Button.Props, 'focusableWhenDisabled' | 'className' | 'style' | 'render' | 'children' | 'aria-label'> {
|
|
10
|
+
/** The look of the button. Defaults to main. */
|
|
11
|
+
variant?: ButtonVariant;
|
|
12
|
+
/** Gives the button back to the caller's ref. */
|
|
13
|
+
ref?: Ref<HTMLButtonElement>;
|
|
14
|
+
}
|
|
15
|
+
interface LabelledButtonProps extends ButtonBaseProps {
|
|
16
|
+
/** Draws a 28px square for one icon. Needs aria-label, because there is no text to name the button. */
|
|
17
|
+
iconOnly?: false;
|
|
18
|
+
/** The label, optionally with an Icon beside it. */
|
|
19
|
+
children?: React.ReactNode;
|
|
20
|
+
}
|
|
21
|
+
interface IconButtonProps extends ButtonBaseProps {
|
|
22
|
+
/** Draws a 28px square for one icon. Needs aria-label, because there is no text to name the button. */
|
|
23
|
+
iconOnly: true;
|
|
24
|
+
/** The accessible name of an icon-only button. */
|
|
25
|
+
'aria-label': string;
|
|
26
|
+
/** One Icon. */
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
}
|
|
29
|
+
/** Props of Button: a labelled button, or an icon-only one that has to carry an aria-label. */
|
|
30
|
+
export type ButtonProps = LabelledButtonProps | IconButtonProps;
|
|
31
|
+
/**
|
|
32
|
+
* The design system's 28px button in five looks: main, secondary, ghost, link and destructive. Hover and pressed change
|
|
33
|
+
* the fill only, disabled is 50% opacity and the colours fade in 120ms. Put an Icon beside the label, or use iconOnly with
|
|
34
|
+
* an aria-label. It has no size prop: 28px is the only height.
|
|
35
|
+
*/
|
|
36
|
+
declare function Button$1({ variant, iconOnly, type, ...props }: ButtonProps): JSX.Element;
|
|
37
|
+
export { Button$1 as Button };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn.js";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Button } from "@base-ui/react/button";
|
|
4
|
+
import { cva } from "class-variance-authority";
|
|
5
|
+
// src/components/button/Button.tsx
|
|
6
|
+
const buttonVariants = cva("rt:inline-flex rt:h-7 rt:shrink-0 rt:items-center rt:justify-center rt:rounded-md rt:border-0 rt:text-btn rt:whitespace-nowrap rt:select-none rt:cursor-pointer rt:transition-colors rt:focus-visible:focus-ring rt:disabled:pointer-events-none rt:disabled:opacity-50 rt:data-disabled:pointer-events-none rt:data-disabled:opacity-50", {
|
|
7
|
+
variants: {
|
|
8
|
+
variant: {
|
|
9
|
+
main: "rt:gap-1.5 rt:bg-primary rt:text-primary-foreground rt:hover:bg-primary-hover rt:active:bg-primary-hover",
|
|
10
|
+
secondary: "rt:gap-1 rt:bg-secondary rt:text-secondary-foreground rt:shadow-ring-strong rt:forced-outline rt:hover:bg-primary-tint rt:active:bg-primary-tint",
|
|
11
|
+
ghost: "rt:gap-1 rt:bg-transparent rt:text-secondary-foreground rt:hover:bg-muted rt:active:bg-muted",
|
|
12
|
+
link: "rt:gap-1 rt:bg-transparent rt:text-primary rt:hover:underline rt:active:underline",
|
|
13
|
+
destructive: "rt:gap-1 rt:bg-destructive-fill rt:text-destructive-foreground rt:shadow-ring-destructive rt:forced-outline rt:hover:bg-destructive-hover rt:active:bg-destructive-hover"
|
|
14
|
+
},
|
|
15
|
+
iconOnly: {
|
|
16
|
+
false: "rt:px-2 rt:py-1",
|
|
17
|
+
true: "rt:w-7 rt:p-0"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
defaultVariants: {
|
|
21
|
+
variant: "main",
|
|
22
|
+
iconOnly: false
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* The design system's 28px button in five looks: main, secondary, ghost, link and destructive. Hover and pressed change
|
|
27
|
+
* the fill only, disabled is 50% opacity and the colours fade in 120ms. Put an Icon beside the label, or use iconOnly with
|
|
28
|
+
* an aria-label. It has no size prop: 28px is the only height.
|
|
29
|
+
*/
|
|
30
|
+
function Button$1({ variant = "main", iconOnly = false, type = "button", ...props }) {
|
|
31
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
32
|
+
"data-slot": "button",
|
|
33
|
+
type,
|
|
34
|
+
className: cn(buttonVariants({
|
|
35
|
+
variant,
|
|
36
|
+
iconOnly
|
|
37
|
+
})),
|
|
38
|
+
...props
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export { Button$1 as Button };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Button } from "./Button.js";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { HTMLAttributes, JSX } from "react";
|
|
2
|
+
type DivProps = Omit<HTMLAttributes<HTMLDivElement>, 'className' | 'style'>;
|
|
3
|
+
/** The props of Card: the HTML attributes of its div, except className and style. */
|
|
4
|
+
export type CardProps = DivProps;
|
|
5
|
+
/** The props of CardHeader: the HTML attributes of its div, except className and style. */
|
|
6
|
+
export type CardHeaderProps = DivProps;
|
|
7
|
+
/** The props of CardTitle: the HTML attributes of its heading, except className and style, with the heading level. */
|
|
8
|
+
export type CardTitleProps = Omit<HTMLAttributes<HTMLHeadingElement>, 'className' | 'style'> & {
|
|
9
|
+
/** The heading element. Defaults to h3. */
|
|
10
|
+
as?: 'h2' | 'h3' | 'h4' | 'div';
|
|
11
|
+
};
|
|
12
|
+
/** The props of CardDescription: the HTML attributes of its paragraph, except className and style. */
|
|
13
|
+
export type CardDescriptionProps = Omit<HTMLAttributes<HTMLParagraphElement>, 'className' | 'style'>;
|
|
14
|
+
/** The props of CardContent: the HTML attributes of its div, except className and style. */
|
|
15
|
+
export type CardContentProps = DivProps;
|
|
16
|
+
/** The props of CardFooter: the HTML attributes of its div, except className and style. */
|
|
17
|
+
export type CardFooterProps = DivProps;
|
|
18
|
+
/**
|
|
19
|
+
* A container for a panel of content: a 1px hairline border on white, no shadow and 24px of padding. The design system
|
|
20
|
+
* has no cards, so this is only the hairline container.
|
|
21
|
+
*/
|
|
22
|
+
export declare function Card(props: CardProps): JSX.Element;
|
|
23
|
+
/** The top of a card: its title and description, stacked. */
|
|
24
|
+
export declare function CardHeader(props: CardHeaderProps): JSX.Element;
|
|
25
|
+
/** The title of a card, set in the B4 text style. */
|
|
26
|
+
export declare function CardTitle({ as: Tag, ...rest }: CardTitleProps): JSX.Element;
|
|
27
|
+
/** A line under the title that says what the card holds. */
|
|
28
|
+
export declare function CardDescription(props: CardDescriptionProps): JSX.Element;
|
|
29
|
+
/** The body of a card. */
|
|
30
|
+
export declare function CardContent(props: CardContentProps): JSX.Element;
|
|
31
|
+
/** The foot of a card, a row for its actions. */
|
|
32
|
+
export declare function CardFooter(props: CardFooterProps): JSX.Element;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
// src/components/card/Card.tsx
|
|
3
|
+
/**
|
|
4
|
+
* A container for a panel of content: a 1px hairline border on white, no shadow and 24px of padding. The design system
|
|
5
|
+
* has no cards, so this is only the hairline container.
|
|
6
|
+
*/
|
|
7
|
+
function Card(props) {
|
|
8
|
+
return /* @__PURE__ */ jsx("div", {
|
|
9
|
+
"data-slot": "card",
|
|
10
|
+
...props,
|
|
11
|
+
className: "rt:flex rt:flex-col rt:gap-6 rt:rounded-lg rt:border rt:border-border rt:bg-background rt:p-6"
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/** The top of a card: its title and description, stacked. */
|
|
15
|
+
function CardHeader(props) {
|
|
16
|
+
return /* @__PURE__ */ jsx("div", {
|
|
17
|
+
"data-slot": "card-header",
|
|
18
|
+
...props,
|
|
19
|
+
className: "rt:flex rt:flex-col rt:gap-1"
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** The title of a card, set in the B4 text style. */
|
|
23
|
+
function CardTitle({ as: Tag = "h3", ...rest }) {
|
|
24
|
+
return /* @__PURE__ */ jsx(Tag, {
|
|
25
|
+
"data-slot": "card-title",
|
|
26
|
+
...rest,
|
|
27
|
+
className: "rt:text-b4 rt:text-foreground"
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** A line under the title that says what the card holds. */
|
|
31
|
+
function CardDescription(props) {
|
|
32
|
+
return /* @__PURE__ */ jsx("p", {
|
|
33
|
+
"data-slot": "card-description",
|
|
34
|
+
...props,
|
|
35
|
+
className: "rt:text-b3 rt:text-foreground-subdued"
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/** The body of a card. */
|
|
39
|
+
function CardContent(props) {
|
|
40
|
+
return /* @__PURE__ */ jsx("div", {
|
|
41
|
+
"data-slot": "card-content",
|
|
42
|
+
...props
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/** The foot of a card, a row for its actions. */
|
|
46
|
+
function CardFooter(props) {
|
|
47
|
+
return /* @__PURE__ */ jsx("div", {
|
|
48
|
+
"data-slot": "card-footer",
|
|
49
|
+
...props,
|
|
50
|
+
className: "rt:flex rt:items-center rt:gap-2"
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { Card, CardContent, CardContentProps, CardDescription, CardDescriptionProps, CardFooter, CardFooterProps, CardHeader, CardHeaderProps, CardProps, CardTitle, CardTitleProps } from "./Card.js";
|
|
2
|
+
export { Card, CardContent, type CardContentProps, CardDescription, type CardDescriptionProps, CardFooter, type CardFooterProps, CardHeader, type CardHeaderProps, type CardProps, CardTitle, type CardTitleProps };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./Card.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { JSX } from "react";
|
|
2
|
+
import { Checkbox } from "@base-ui/react/checkbox";
|
|
3
|
+
/** The props of Checkbox: Base UI's checkbox props, except className, style and render. Give it an aria-label or a label that points to it. */
|
|
4
|
+
export type CheckboxProps = Omit<Checkbox.Root.Props, 'className' | 'style' | 'render'>;
|
|
5
|
+
/**
|
|
6
|
+
* A 14px box with a 4px radius, drawn with a hairline ring and filled in the primary colour when checked. Set
|
|
7
|
+
* indeterminate for a select-all that stands for some rows. An invisible 24px area around the box takes the tap.
|
|
8
|
+
*/
|
|
9
|
+
declare function Checkbox$1({ ...props }: CheckboxProps): JSX.Element;
|
|
10
|
+
export { Checkbox$1 as Checkbox };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn.js";
|
|
2
|
+
import { Icon } from "../icon/Icon.js";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
import { Checkbox } from "@base-ui/react/checkbox";
|
|
5
|
+
// src/components/checkbox/Checkbox.tsx
|
|
6
|
+
/**
|
|
7
|
+
* A 14px box with a 4px radius, drawn with a hairline ring and filled in the primary colour when checked. Set
|
|
8
|
+
* indeterminate for a select-all that stands for some rows. An invisible 24px area around the box takes the tap.
|
|
9
|
+
*/
|
|
10
|
+
function Checkbox$1({ ...props }) {
|
|
11
|
+
return /* @__PURE__ */ jsx(Checkbox.Root, {
|
|
12
|
+
"data-slot": "checkbox",
|
|
13
|
+
className: cn("rt:peer rt:relative rt:flex rt:size-3.5 rt:shrink-0 rt:cursor-pointer rt:items-center rt:justify-center rt:rounded-sm rt:bg-background rt:shadow-ring-hairline rt:transition-colors rt:outline-none", "rt:after:absolute rt:after:-inset-1.25", "rt:hover:bg-primary-tint rt:data-checked:bg-primary rt:data-checked:shadow-none rt:data-checked:hover:bg-primary-hover", "rt:data-indeterminate:bg-primary rt:data-indeterminate:shadow-none rt:data-indeterminate:hover:bg-primary-hover", "rt:focus-visible:focus-ring rt:forced-outline", "rt:aria-invalid:shadow-ring-invalid rt:aria-invalid:data-checked:shadow-ring-invalid rt:aria-invalid:data-indeterminate:shadow-ring-invalid", "rt:data-disabled:cursor-not-allowed rt:data-disabled:opacity-50", "rt:disabled:cursor-not-allowed rt:disabled:opacity-50"),
|
|
14
|
+
...props,
|
|
15
|
+
children: /* @__PURE__ */ jsx(Checkbox.Indicator, {
|
|
16
|
+
"data-slot": "checkbox-indicator",
|
|
17
|
+
className: "rt:flex rt:items-center rt:justify-center rt:text-primary-foreground",
|
|
18
|
+
children: props.indeterminate ? /* @__PURE__ */ jsx("span", {
|
|
19
|
+
"data-slot": "checkbox-dash",
|
|
20
|
+
className: "rt:block rt:h-px rt:w-2 rt:rounded-sm rt:bg-primary-foreground"
|
|
21
|
+
}) : /* @__PURE__ */ jsx(Icon, {
|
|
22
|
+
name: "check",
|
|
23
|
+
size: 8
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export { Checkbox$1 as Checkbox };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Checkbox } from "./Checkbox.js";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { TableProps } from "../table/Table.js";
|
|
2
|
+
import "../table/index.js";
|
|
3
|
+
import { JSX, ReactNode } from "react";
|
|
4
|
+
import { RowData } from "@tanstack/react-table";
|
|
5
|
+
/** The sort the parent has applied: the id of a column and whether it runs from high to low. */
|
|
6
|
+
export type DataTableSort = {
|
|
7
|
+
id: string;
|
|
8
|
+
desc: boolean;
|
|
9
|
+
};
|
|
10
|
+
/** One column of a DataTable. */
|
|
11
|
+
export type DataTableColumn<T> = {
|
|
12
|
+
/** A stable key for the column, also the id passed to `onSortChange`. */
|
|
13
|
+
id: string;
|
|
14
|
+
/** The heading text. */
|
|
15
|
+
header: string;
|
|
16
|
+
/** Draws the cell for one row. */
|
|
17
|
+
cell: (row: T) => ReactNode;
|
|
18
|
+
/** Aligns the heading and cells to the right with tabular figures. Defaults to false. */
|
|
19
|
+
numeric?: boolean;
|
|
20
|
+
/** Cuts a long value off with an ellipsis so it stays on one line. Defaults to false. */
|
|
21
|
+
truncate?: boolean;
|
|
22
|
+
/** Lets the heading ask the parent to sort by this column. Defaults to false. */
|
|
23
|
+
sortable?: boolean;
|
|
24
|
+
/** Keeps the column on a phone; a column that is not essential is hidden there, and with none marked the first stays. Defaults to false. */
|
|
25
|
+
essential?: boolean;
|
|
26
|
+
};
|
|
27
|
+
/** The props of DataTable: the attributes of Table, plus the columns, the rows and the state the parent owns. */
|
|
28
|
+
export type DataTableProps<T> = Omit<TableProps, 'children'> & {
|
|
29
|
+
/** The columns, in order. */
|
|
30
|
+
columns: DataTableColumn<T>[];
|
|
31
|
+
/** The rows of the page on show, already sorted by the parent. */
|
|
32
|
+
data: T[];
|
|
33
|
+
/** A stable id for a row. */
|
|
34
|
+
getRowId: (row: T) => string;
|
|
35
|
+
/** Ids of the selected rows. Pass it with `onSelectedIdsChange` to show a checkbox column. */
|
|
36
|
+
selectedIds?: string[];
|
|
37
|
+
/** Called with the ids that are selected after a change. */
|
|
38
|
+
onSelectedIdsChange?: (ids: string[]) => void;
|
|
39
|
+
/** The label of one row's checkbox: "RYZE - 2mg", which the table prefixes with "Select ". Defaults to "Select row N". */
|
|
40
|
+
getRowLabel?: (row: T) => string;
|
|
41
|
+
/** The sort the parent applied, or null for none. */
|
|
42
|
+
sort?: DataTableSort | null;
|
|
43
|
+
/** Called with the next sort, or null to clear it. The parent sorts and passes new data. */
|
|
44
|
+
onSortChange?: (sort: DataTableSort | null) => void;
|
|
45
|
+
/** The page on show, counting from 1. Pass with `pageSize`, `total` and `onPageChange` to show the footer. */
|
|
46
|
+
page?: number;
|
|
47
|
+
/** How many results one page holds. */
|
|
48
|
+
pageSize?: number;
|
|
49
|
+
/** How many results there are in all, across every page. */
|
|
50
|
+
total?: number;
|
|
51
|
+
/** Called with the page to show. */
|
|
52
|
+
onPageChange?: (page: number) => void;
|
|
53
|
+
/** Where a row leads. The first data cell of the row becomes a link to it. On a router, LinkProvider draws it with the app's own link. */
|
|
54
|
+
rowHref?: (row: T) => string;
|
|
55
|
+
/** Shown in place of the rows when there are none and the table is not loading. Usually an EmptyState. */
|
|
56
|
+
empty?: ReactNode;
|
|
57
|
+
/** Shown in place of the whole table while loading, usually a TableLoader. A falsy value shows the table. */
|
|
58
|
+
loading?: ReactNode;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* A data table on Table with TanStack Table v9: a selection column, server-side sorting and paging, alignment,
|
|
62
|
+
* one-line truncation, row links, and empty and loading slots. The parent owns the state (sort, page, selection) and
|
|
63
|
+
* passes it in. On phones it keeps the columns marked essential and hides the rest.
|
|
64
|
+
*/
|
|
65
|
+
export declare function DataTable<T extends RowData>({ columns: allColumns, data, getRowId, selectedIds, onSelectedIdsChange, getRowLabel, sort, onSortChange, page, pageSize, total, onPageChange, rowHref, empty, loading, ...rest }: DataTableProps<T>): JSX.Element;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Checkbox } from "../checkbox/Checkbox.js";
|
|
2
|
+
import { RatioLink } from "../link-provider/RatioLink.js";
|
|
3
|
+
import { Pagination } from "../pagination/Pagination.js";
|
|
4
|
+
import { Table, TableBody, TableBox, TableCell, TableHead, TableHeader, TableRow } from "../table/Table.js";
|
|
5
|
+
import { useMemo, useSyncExternalStore } from "react";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { FlexRender, rowSelectionFeature, rowSortingFeature, tableFeatures, useTable } from "@tanstack/react-table";
|
|
8
|
+
// src/components/data-table/DataTable.tsx
|
|
9
|
+
const features = tableFeatures({
|
|
10
|
+
rowSortingFeature,
|
|
11
|
+
rowSelectionFeature
|
|
12
|
+
});
|
|
13
|
+
const PHONE_QUERY = "(max-width: 39.99rem)";
|
|
14
|
+
function subscribePhone(notify) {
|
|
15
|
+
const list = window.matchMedia(PHONE_QUERY);
|
|
16
|
+
list.addEventListener("change", notify);
|
|
17
|
+
return () => list.removeEventListener("change", notify);
|
|
18
|
+
}
|
|
19
|
+
function usePhone() {
|
|
20
|
+
return useSyncExternalStore(subscribePhone, () => window.matchMedia(PHONE_QUERY).matches, () => false);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A data table on Table with TanStack Table v9: a selection column, server-side sorting and paging, alignment,
|
|
24
|
+
* one-line truncation, row links, and empty and loading slots. The parent owns the state (sort, page, selection) and
|
|
25
|
+
* passes it in. On phones it keeps the columns marked essential and hides the rest.
|
|
26
|
+
*/
|
|
27
|
+
function DataTable({ columns: allColumns, data, getRowId, selectedIds, onSelectedIdsChange, getRowLabel, sort = null, onSortChange, page, pageSize, total, onPageChange, rowHref, empty, loading, ...rest }) {
|
|
28
|
+
const phone = usePhone();
|
|
29
|
+
const columns = useMemo(() => {
|
|
30
|
+
if (!phone) return allColumns;
|
|
31
|
+
const essential = allColumns.filter((c) => c.essential);
|
|
32
|
+
return essential.length > 0 ? essential : allColumns.slice(0, 1);
|
|
33
|
+
}, [phone, allColumns]);
|
|
34
|
+
const selectable = selectedIds !== void 0;
|
|
35
|
+
const defs = useMemo(() => columns.map((c) => ({
|
|
36
|
+
id: c.id,
|
|
37
|
+
header: c.header,
|
|
38
|
+
accessorFn: () => null,
|
|
39
|
+
cell: ({ row }) => c.cell(row.original),
|
|
40
|
+
enableSorting: c.sortable === true,
|
|
41
|
+
sortDescFirst: false
|
|
42
|
+
})), [columns]);
|
|
43
|
+
const sorting = sort ? [sort] : [];
|
|
44
|
+
const rowSelection = useMemo(() => Object.fromEntries((selectedIds ?? []).map((id) => [id, true])), [selectedIds]);
|
|
45
|
+
const table = useTable({
|
|
46
|
+
features,
|
|
47
|
+
columns: defs,
|
|
48
|
+
data,
|
|
49
|
+
getRowId,
|
|
50
|
+
manualSorting: true,
|
|
51
|
+
enableRowSelection: selectable,
|
|
52
|
+
state: {
|
|
53
|
+
sorting,
|
|
54
|
+
rowSelection
|
|
55
|
+
},
|
|
56
|
+
onSortingChange: (updater) => {
|
|
57
|
+
const next = typeof updater === "function" ? updater(sorting) : updater;
|
|
58
|
+
onSortChange?.(next[0] ? {
|
|
59
|
+
id: next[0].id,
|
|
60
|
+
desc: next[0].desc
|
|
61
|
+
} : null);
|
|
62
|
+
},
|
|
63
|
+
onRowSelectionChange: (updater) => {
|
|
64
|
+
const next = typeof updater === "function" ? updater(rowSelection) : updater;
|
|
65
|
+
onSelectedIdsChange?.(Object.keys(next).filter((id) => next[id]));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
const rows = table.getRowModel().rows;
|
|
69
|
+
const colSpan = columns.length + (selectable ? 1 : 0);
|
|
70
|
+
const hasFooter = page !== void 0 && pageSize !== void 0 && total !== void 0 && onPageChange !== void 0;
|
|
71
|
+
const byId = new Map(columns.map((c) => [c.id, c]));
|
|
72
|
+
if (loading) return /* @__PURE__ */ jsx(Fragment, { children: loading });
|
|
73
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
74
|
+
"data-slot": "data-table",
|
|
75
|
+
className: "rt:flex rt:w-full rt:flex-col rt:gap-2",
|
|
76
|
+
children: [/* @__PURE__ */ jsx(TableBox, { children: /* @__PURE__ */ jsxs(Table, {
|
|
77
|
+
...rest,
|
|
78
|
+
children: [/* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [selectable ? /* @__PURE__ */ jsx(TableHead, { children: /* @__PURE__ */ jsx(Checkbox, {
|
|
79
|
+
"aria-label": "Select all rows",
|
|
80
|
+
checked: table.getIsAllRowsSelected() && rows.length > 0,
|
|
81
|
+
indeterminate: rows.some((r) => r.getIsSelected()) && !table.getIsAllRowsSelected(),
|
|
82
|
+
onCheckedChange: () => table.toggleAllRowsSelected()
|
|
83
|
+
}) }) : null, table.getHeaderGroups()[0]?.headers.map((h) => {
|
|
84
|
+
const c = byId.get(h.column.id);
|
|
85
|
+
if (!c) return null;
|
|
86
|
+
const sorted = h.column.getIsSorted();
|
|
87
|
+
return /* @__PURE__ */ jsx(TableHead, {
|
|
88
|
+
numeric: c.numeric,
|
|
89
|
+
"aria-sort": sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : void 0,
|
|
90
|
+
children: c.sortable ? /* @__PURE__ */ jsxs("button", {
|
|
91
|
+
type: "button",
|
|
92
|
+
"aria-label": `Sort by ${c.header}`,
|
|
93
|
+
className: "rt:inline-flex rt:cursor-pointer rt:items-center rt:gap-1 rt:uppercase rt:focus-visible:focus-ring",
|
|
94
|
+
onClick: () => h.column.toggleSorting(),
|
|
95
|
+
children: [/* @__PURE__ */ jsx(FlexRender, { header: h }), /* @__PURE__ */ jsx("span", {
|
|
96
|
+
"aria-hidden": "true",
|
|
97
|
+
children: sorted === "asc" ? "↑" : sorted === "desc" ? "↓" : null
|
|
98
|
+
})]
|
|
99
|
+
}) : /* @__PURE__ */ jsx(FlexRender, { header: h })
|
|
100
|
+
}, h.id);
|
|
101
|
+
})] }) }), /* @__PURE__ */ jsx(TableBody, { children: rows.length === 0 ? /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
|
|
102
|
+
colSpan,
|
|
103
|
+
children: empty
|
|
104
|
+
}) }) : rows.map((row, index) => /* @__PURE__ */ jsxs(TableRow, {
|
|
105
|
+
selected: row.getIsSelected(),
|
|
106
|
+
children: [selectable ? /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Checkbox, {
|
|
107
|
+
"aria-label": getRowLabel ? `Select ${getRowLabel(row.original)}` : `Select row ${index + 1}`,
|
|
108
|
+
checked: row.getIsSelected(),
|
|
109
|
+
onCheckedChange: () => row.toggleSelected()
|
|
110
|
+
}) }) : null, row.getAllCells().map((cell, i) => {
|
|
111
|
+
const c = byId.get(cell.column.id);
|
|
112
|
+
if (!c) return null;
|
|
113
|
+
const content = /* @__PURE__ */ jsx(FlexRender, { cell });
|
|
114
|
+
const linked = rowHref && i === 0 ? /* @__PURE__ */ jsx(RatioLink, {
|
|
115
|
+
href: rowHref(row.original),
|
|
116
|
+
className: "rt:focus-visible:focus-ring",
|
|
117
|
+
children: content
|
|
118
|
+
}) : content;
|
|
119
|
+
return /* @__PURE__ */ jsx(TableCell, {
|
|
120
|
+
numeric: c.numeric,
|
|
121
|
+
"data-numeric": c.numeric ? "true" : void 0,
|
|
122
|
+
children: c.truncate ? /* @__PURE__ */ jsx("span", {
|
|
123
|
+
className: "rt:block rt:max-w-64 rt:truncate",
|
|
124
|
+
children: linked
|
|
125
|
+
}) : linked
|
|
126
|
+
}, cell.id);
|
|
127
|
+
})]
|
|
128
|
+
}, row.id)) })]
|
|
129
|
+
}) }), hasFooter ? /* @__PURE__ */ jsx("div", {
|
|
130
|
+
"data-slot": "data-table-pagination",
|
|
131
|
+
className: "rt:rounded-lg rt:border rt:border-border rt:bg-background rt:px-2",
|
|
132
|
+
children: /* @__PURE__ */ jsx(Pagination, {
|
|
133
|
+
page,
|
|
134
|
+
pageSize,
|
|
135
|
+
total,
|
|
136
|
+
onPageChange
|
|
137
|
+
})
|
|
138
|
+
}) : null]
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
export { DataTable };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DataTable } from "./DataTable.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
/** The props of Definition. */
|
|
3
|
+
export type DefinitionProps = {
|
|
4
|
+
/** The term, shown in the text with a dotted underline. */
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
/** The explanation shown when the term is opened. Keep it to a sentence. */
|
|
7
|
+
definition: ReactNode;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* A term that explains itself. It is a span with the button role and a dotted underline, so it takes the font of the text around it, so keyboard users reach it with Tab and open it with
|
|
11
|
+
* Enter or Space, touch users open it with a tap, and mouse users with hover or click. It does not use the browser's own
|
|
12
|
+
* title tooltip, which keyboard and touch users cannot open. Escape closes it.
|
|
13
|
+
*/
|
|
14
|
+
export declare function Definition({ children, definition }: DefinitionProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Popover } from "@base-ui/react/popover";
|
|
3
|
+
// src/components/definition/Definition.tsx
|
|
4
|
+
/**
|
|
5
|
+
* A term that explains itself. It is a span with the button role and a dotted underline, so it takes the font of the text around it, so keyboard users reach it with Tab and open it with
|
|
6
|
+
* Enter or Space, touch users open it with a tap, and mouse users with hover or click. It does not use the browser's own
|
|
7
|
+
* title tooltip, which keyboard and touch users cannot open. Escape closes it.
|
|
8
|
+
*/
|
|
9
|
+
function Definition({ children, definition }) {
|
|
10
|
+
return /* @__PURE__ */ jsxs(Popover.Root, { children: [/* @__PURE__ */ jsx(Popover.Trigger, {
|
|
11
|
+
openOnHover: true,
|
|
12
|
+
nativeButton: false,
|
|
13
|
+
render: /* @__PURE__ */ jsx("span", {}),
|
|
14
|
+
tabIndex: 0,
|
|
15
|
+
delay: 100,
|
|
16
|
+
className: "rt:cursor-help rt:rounded-sm rt:bg-transparent rt:p-0 rt:text-inherit rt:underline rt:decoration-dotted rt:decoration-1 rt:underline-offset-4 rt:focus-visible:focus-ring",
|
|
17
|
+
children
|
|
18
|
+
}), /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
|
|
19
|
+
side: "top",
|
|
20
|
+
sideOffset: 4,
|
|
21
|
+
className: "rt:isolate rt:z-50",
|
|
22
|
+
children: /* @__PURE__ */ jsx(Popover.Popup, {
|
|
23
|
+
"aria-label": "Definition",
|
|
24
|
+
className: "rt:max-w-72 rt:rounded-md rt:border rt:border-border rt:bg-popover rt:px-3 rt:py-1.5 rt:text-b1 rt:text-popover-foreground rt:outline-hidden",
|
|
25
|
+
children: definition
|
|
26
|
+
})
|
|
27
|
+
}) })] });
|
|
28
|
+
}
|
|
29
|
+
export { Definition };
|