@momoi-labs/kiso-react 0.3.0 → 0.4.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 +31 -4
- package/dist/app-shell.d.ts +25 -1
- package/dist/app-shell.js +13 -2
- package/dist/form-field.d.ts +15 -5
- package/dist/form-field.js +19 -6
- package/dist/toast.d.ts +9 -1
- package/dist/toast.js +28 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Import the stylesheet once, before application overrides:
|
|
|
11
11
|
|
|
12
12
|
```tsx
|
|
13
13
|
import '@momoi-labs/kiso-react/styles.css';
|
|
14
|
-
import { Button, FormField } from '@momoi-labs/kiso-react';
|
|
14
|
+
import { Button, FormField, Textarea } from '@momoi-labs/kiso-react';
|
|
15
15
|
|
|
16
16
|
export function ApplicationForm() {
|
|
17
17
|
return (
|
|
@@ -23,6 +23,15 @@ export function ApplicationForm() {
|
|
|
23
23
|
}
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
FormField renders an Input by default. Pass one control as its child when the
|
|
27
|
+
field uses another input, and add `error` for wired validation feedback:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
<FormField label="Compose file" hint="Docker Compose YAML." error={error}>
|
|
31
|
+
<Textarea rows={12} />
|
|
32
|
+
</FormField>
|
|
33
|
+
```
|
|
34
|
+
|
|
26
35
|
`@momoi-labs/kiso` is a regular dependency. The stylesheet imports its tokens
|
|
27
36
|
and component layer; consumers do not need to import those again. React and
|
|
28
37
|
React DOM are peer dependencies supplied by the application. No Tailwind setup
|
|
@@ -45,7 +54,9 @@ Data and structure
|
|
|
45
54
|
- EmptyState, EmptyStateIcon, EmptyStateTitle, EmptyStateDescription, EmptyStateActions.
|
|
46
55
|
- Header, PageHeader, PageHeaderTitle, PageHeaderDescription.
|
|
47
56
|
- Sidebar, SidebarHeader, SidebarBody, SidebarFooter.
|
|
48
|
-
- AppShell and AppShellMain, the
|
|
57
|
+
- AppShell and AppShellMain, the low-level columns of a console.
|
|
58
|
+
- ApplicationShell, the standard brand, navigation, header, footer, and page
|
|
59
|
+
arrangement.
|
|
49
60
|
- Split, Pane, and Splitter, a list-detail layout with a resizable divider.
|
|
50
61
|
- LogView, LogViewLine, LogViewTime, and LogViewLevel.
|
|
51
62
|
- Stat, StatHeader, StatLabel, StatValue, StatFoot, and StatDelta.
|
|
@@ -69,8 +80,10 @@ Feedback
|
|
|
69
80
|
|
|
70
81
|
- Alert, AlertContent, AlertTitle, AlertDescription, and `alertVariants`.
|
|
71
82
|
- Spinner, Skeleton, and their variants.
|
|
83
|
+
- Toasts and `useToast`, for notifications raised from descendant event
|
|
84
|
+
handlers.
|
|
72
85
|
- ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription,
|
|
73
|
-
ToastContent, ToastAction, ToastClose.
|
|
86
|
+
ToastContent, ToastAction, ToastClose, for controlled composition.
|
|
74
87
|
|
|
75
88
|
Overlays
|
|
76
89
|
|
|
@@ -88,7 +101,21 @@ Button variants are `default`, `primary`, `destructive`, and `ghost`; sizes are
|
|
|
88
101
|
`warning`, and `danger`. Alert and Toast variants follow their contracts:
|
|
89
102
|
`info` / `success` / `warning` / `error`, and `neutral` / `success` / `warning`
|
|
90
103
|
/ `error`. Native props and refs pass through to the underlying control.
|
|
91
|
-
FormField adds `label` and optional `
|
|
104
|
+
FormField adds `label`, optional `hint`, and optional `error` to Input's props.
|
|
105
|
+
With one child, it wires those parts to that control instead of rendering an
|
|
106
|
+
Input. Use `fieldClassName` to style the wrapper.
|
|
107
|
+
|
|
108
|
+
Wrap an application in Toasts and call `useToast()` below it. The returned
|
|
109
|
+
function takes a variant, title, and optional body:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
const notify = useToast();
|
|
113
|
+
notify('error', `Could not stop ${app.name}`, details);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
ApplicationShell takes navigation groups as data. Destinations keep routing in
|
|
117
|
+
the product through `{ href, active, onClick? }`; the package owns the repeated
|
|
118
|
+
Sidebar, Navigation, Header, and main arrangement.
|
|
92
119
|
|
|
93
120
|
Navigation, Sidebar, and Link know nothing about routing. Mark the current
|
|
94
121
|
destination with `active`, and pass `asChild` to render a router's own link
|
package/dist/app-shell.d.ts
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
declare function AppShell({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
3
3
|
declare function AppShellMain({ className, ...props }: React.ComponentProps<"main">): React.JSX.Element;
|
|
4
|
-
|
|
4
|
+
type ApplicationShellDestination = {
|
|
5
|
+
href: string;
|
|
6
|
+
active?: boolean;
|
|
7
|
+
onClick?: () => void;
|
|
8
|
+
label: React.ReactNode;
|
|
9
|
+
leading?: React.ReactNode;
|
|
10
|
+
trailing?: React.ReactNode;
|
|
11
|
+
};
|
|
12
|
+
type ApplicationShellGroup = {
|
|
13
|
+
label?: string;
|
|
14
|
+
destinations: readonly ApplicationShellDestination[];
|
|
15
|
+
empty?: React.ReactNode;
|
|
16
|
+
};
|
|
17
|
+
type ApplicationShellProps = Omit<React.ComponentProps<typeof AppShell>, "children"> & {
|
|
18
|
+
brand: React.ReactNode;
|
|
19
|
+
primaryAction?: React.ReactNode;
|
|
20
|
+
navigation: readonly ApplicationShellGroup[];
|
|
21
|
+
navigationLabel?: string;
|
|
22
|
+
footer?: React.ReactNode;
|
|
23
|
+
header?: React.ReactNode;
|
|
24
|
+
children: React.ReactNode;
|
|
25
|
+
};
|
|
26
|
+
declare function ApplicationShell({ brand, primaryAction, navigation, navigationLabel, footer, header, children, ...props }: ApplicationShellProps): React.JSX.Element;
|
|
27
|
+
export { AppShell, AppShellMain, ApplicationShell };
|
|
28
|
+
export type { ApplicationShellDestination, ApplicationShellGroup, ApplicationShellProps, };
|
package/dist/app-shell.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { clsx as cn } from "clsx";
|
|
3
|
+
import { Header } from "./header.js";
|
|
4
|
+
import { Navigation, NavigationGroup, NavigationItem, NavigationLink, NavigationList, } from "./navigation.js";
|
|
5
|
+
import { Sidebar, SidebarBody, SidebarFooter, SidebarHeader } from "./sidebar.js";
|
|
3
6
|
function AppShell({ className, ...props }) {
|
|
4
7
|
return (_jsx("div", { "data-slot": "app-shell", className: cn("app-shell", className), ...props }));
|
|
5
8
|
}
|
|
@@ -8,4 +11,12 @@ function AppShell({ className, ...props }) {
|
|
|
8
11
|
function AppShellMain({ className, ...props }) {
|
|
9
12
|
return (_jsx("main", { "data-slot": "app-shell-main", className: cn("grow", className), ...props }));
|
|
10
13
|
}
|
|
11
|
-
|
|
14
|
+
function ApplicationShell({ brand, primaryAction, navigation, navigationLabel = "Primary", footer, header, children, ...props }) {
|
|
15
|
+
return (_jsxs(AppShell, { ...props, children: [_jsxs(Sidebar, { children: [_jsxs(SidebarHeader, { children: [brand, primaryAction] }), _jsx(SidebarBody, { children: _jsx(Navigation, { "aria-label": navigationLabel, children: navigation.map((group, groupIndex) => (_jsx(NavigationGroup, { label: group.label, children: group.destinations.length > 0 ? (_jsx(NavigationList, { children: group.destinations.map((destination, destinationIndex) => (_jsx(NavigationItem, { children: _jsxs(NavigationLink, { href: destination.href, active: destination.active, onClick: (event) => {
|
|
16
|
+
if (!destination.onClick)
|
|
17
|
+
return;
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
destination.onClick();
|
|
20
|
+
}, children: [destination.leading, _jsx("span", { className: "grow truncate", children: destination.label }), destination.trailing] }) }, `${destination.href}-${destinationIndex}`))) })) : group.empty }, group.label ?? groupIndex))) }) }), footer && _jsx(SidebarFooter, { children: footer })] }), _jsxs(AppShellMain, { children: [header && _jsx(Header, { children: header }), children] })] }));
|
|
21
|
+
}
|
|
22
|
+
export { AppShell, AppShellMain, ApplicationShell };
|
package/dist/form-field.d.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
import { type ComponentProps } from "react";
|
|
1
|
+
import { type ComponentProps, type ReactElement, type ReactNode } from "react";
|
|
2
2
|
import { Input } from "./input.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
type FormControlProps = {
|
|
4
|
+
id?: string;
|
|
5
|
+
"aria-describedby"?: string;
|
|
6
|
+
"aria-invalid"?: ComponentProps<"input">["aria-invalid"];
|
|
7
|
+
};
|
|
8
|
+
type FormFieldProps = Omit<ComponentProps<typeof Input>, "children"> & {
|
|
9
|
+
label: ReactNode;
|
|
10
|
+
hint?: ReactNode;
|
|
11
|
+
error?: ReactNode;
|
|
12
|
+
children?: ReactElement<FormControlProps>;
|
|
13
|
+
fieldClassName?: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function FormField({ label, hint, error, children, fieldClassName, id: providedId, "aria-describedby": describedBy, "aria-invalid": invalid, ...props }: FormFieldProps): import("react").JSX.Element;
|
|
16
|
+
export type { FormFieldProps };
|
package/dist/form-field.js
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useId } from "react";
|
|
3
|
+
import { cloneElement, useId, } from "react";
|
|
4
4
|
import { Input } from "./input.js";
|
|
5
5
|
import { Label } from "./label.js";
|
|
6
|
-
|
|
6
|
+
import { ValidationMessage } from "./validation-message.js";
|
|
7
|
+
function mergeDescriptions(...descriptions) {
|
|
8
|
+
const ids = descriptions.flatMap((description) => description?.split(/\s+/).filter(Boolean) ?? []);
|
|
9
|
+
return ids.length > 0 ? [...new Set(ids)].join(" ") : undefined;
|
|
10
|
+
}
|
|
11
|
+
export function FormField({ label, hint, error, children, fieldClassName, id: providedId, "aria-describedby": describedBy, "aria-invalid": invalid, ...props }) {
|
|
7
12
|
const generatedId = useId();
|
|
8
|
-
const id = providedId ?? generatedId;
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
const id = providedId ?? children?.props.id ?? generatedId;
|
|
14
|
+
const hintId = hint ? `${id}-help` : undefined;
|
|
15
|
+
const errorId = error ? `${id}-error` : undefined;
|
|
16
|
+
const description = mergeDescriptions(describedBy, children?.props["aria-describedby"], hintId, errorId);
|
|
17
|
+
const controlProps = {
|
|
18
|
+
id,
|
|
19
|
+
"aria-describedby": description,
|
|
20
|
+
"aria-invalid": error ? true : children?.props["aria-invalid"] ?? invalid,
|
|
21
|
+
};
|
|
22
|
+
return (_jsxs("div", { className: fieldClassName ? `field ${fieldClassName}` : "field", children: [_jsx(Label, { htmlFor: id, children: label }), children
|
|
23
|
+
? cloneElement(children, controlProps)
|
|
24
|
+
: _jsx(Input, { ...props, ...controlProps }), hint && (_jsx("small", { className: "field-hint", id: hintId, children: hint })), error && _jsx(ValidationMessage, { id: errorId, children: error })] }));
|
|
12
25
|
}
|
package/dist/toast.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { type VariantProps } from "class-variance-authority";
|
|
3
3
|
import { Toast as ToastPrimitive } from "radix-ui";
|
|
4
|
+
type ToastVariant = "neutral" | "success" | "warning" | "error";
|
|
5
|
+
type ToastNotify = (variant: ToastVariant, title: React.ReactNode, body?: React.ReactNode) => void;
|
|
4
6
|
declare const toastVariants: (props?: ({
|
|
5
7
|
variant?: "error" | "neutral" | "success" | "warning" | null | undefined;
|
|
6
8
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
@@ -12,4 +14,10 @@ declare function ToastDescription({ className, ...props }: React.ComponentProps<
|
|
|
12
14
|
declare function ToastContent({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
13
15
|
declare function ToastAction({ ...props }: React.ComponentProps<typeof ToastPrimitive.Action>): React.JSX.Element;
|
|
14
16
|
declare function ToastClose({ ...props }: React.ComponentProps<typeof ToastPrimitive.Close>): React.JSX.Element;
|
|
15
|
-
|
|
17
|
+
declare function useToast(): ToastNotify;
|
|
18
|
+
declare function Toasts({ children, duration, }: {
|
|
19
|
+
children: React.ReactNode;
|
|
20
|
+
duration?: number;
|
|
21
|
+
}): React.JSX.Element;
|
|
22
|
+
export { ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription, ToastContent, ToastAction, ToastClose, Toasts, useToast, toastVariants, };
|
|
23
|
+
export type { ToastNotify, ToastVariant };
|
package/dist/toast.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// Adapted from shadcn/ui new-york-v4. See ../SHADCN-LICENSE.
|
|
2
2
|
"use client";
|
|
3
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
+
import * as React from "react";
|
|
4
5
|
import { cva } from "class-variance-authority";
|
|
5
6
|
import { clsx as cn } from "clsx";
|
|
6
7
|
import { Toast as ToastPrimitive } from "radix-ui";
|
|
8
|
+
import { Button } from "./button.js";
|
|
9
|
+
const ToastContext = React.createContext(null);
|
|
7
10
|
const toastVariants = cva("toast", {
|
|
8
11
|
variants: {
|
|
9
12
|
variant: {
|
|
@@ -40,4 +43,27 @@ function ToastAction({ ...props }) {
|
|
|
40
43
|
function ToastClose({ ...props }) {
|
|
41
44
|
return _jsx(ToastPrimitive.Close, { "data-slot": "toast-close", ...props });
|
|
42
45
|
}
|
|
43
|
-
|
|
46
|
+
function useToast() {
|
|
47
|
+
const notify = React.useContext(ToastContext);
|
|
48
|
+
if (!notify)
|
|
49
|
+
throw new Error("useToast must be used within Toasts");
|
|
50
|
+
return notify;
|
|
51
|
+
}
|
|
52
|
+
function Toasts({ children, duration = 6000, }) {
|
|
53
|
+
const [notices, setNotices] = React.useState([]);
|
|
54
|
+
const nextId = React.useRef(0);
|
|
55
|
+
const notify = React.useCallback((variant, title, body) => {
|
|
56
|
+
const id = nextId.current++;
|
|
57
|
+
setNotices((current) => [...current, { id, variant, title, body }]);
|
|
58
|
+
}, []);
|
|
59
|
+
const dismiss = React.useCallback((id) => {
|
|
60
|
+
setNotices((current) => current.filter((notice) => notice.id !== id));
|
|
61
|
+
}, []);
|
|
62
|
+
return (_jsx(ToastContext.Provider, { value: notify, children: _jsxs(ToastProvider, { duration: duration, children: [children, notices.map((notice) => (_jsxs(Toast, { variant: notice.variant, onOpenChange: (open) => {
|
|
63
|
+
if (!open)
|
|
64
|
+
dismiss(notice.id);
|
|
65
|
+
}, children: [_jsxs(ToastContent, { children: [_jsx(ToastTitle, { children: notice.title }), typeof notice.body === "string" || typeof notice.body === "number"
|
|
66
|
+
? _jsx(ToastDescription, { children: notice.body })
|
|
67
|
+
: notice.body] }), _jsx(ToastClose, { asChild: true, children: _jsx(Button, { size: "xs", variant: "ghost", children: "Dismiss" }) })] }, notice.id))), _jsx(ToastViewport, {})] }) }));
|
|
68
|
+
}
|
|
69
|
+
export { ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription, ToastContent, ToastAction, ToastClose, Toasts, useToast, toastVariants, };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@momoi-labs/kiso-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Kiso React components built with shadcn/ui and Radix",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"prepack": "npm run build"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@momoi-labs/kiso": "^0.
|
|
35
|
+
"@momoi-labs/kiso": "^0.7.0",
|
|
36
36
|
"class-variance-authority": "0.7.1",
|
|
37
37
|
"clsx": "2.1.1",
|
|
38
38
|
"radix-ui": "1.6.7"
|