@lo-ink/ui 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.
- package/dist/AppIcon.d.ts +10 -0
- package/dist/AppIcon.d.ts.map +1 -0
- package/dist/AppIcon.js +5 -0
- package/dist/Button.d.ts +12 -0
- package/dist/Button.d.ts.map +1 -0
- package/dist/Button.js +6 -0
- package/dist/EmptyState.d.ts +9 -0
- package/dist/EmptyState.d.ts.map +1 -0
- package/dist/EmptyState.js +5 -0
- package/dist/LICENSE +21 -0
- package/dist/Layout.d.ts +9 -0
- package/dist/Layout.d.ts.map +1 -0
- package/dist/Layout.js +14 -0
- package/dist/List.d.ts +18 -0
- package/dist/List.d.ts.map +1 -0
- package/dist/List.js +13 -0
- package/dist/SelectionControl.d.ts +17 -0
- package/dist/SelectionControl.d.ts.map +1 -0
- package/dist/SelectionControl.js +22 -0
- package/dist/TextField.d.ts +9 -0
- package/dist/TextField.d.ts.map +1 -0
- package/dist/TextField.js +13 -0
- package/dist/Typography.d.ts +8 -0
- package/dist/Typography.d.ts.map +1 -0
- package/dist/Typography.js +8 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/styles.css +560 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +3 -0
- package/package.json +42 -0
- package/src/AppIcon.tsx +33 -0
- package/src/Button.tsx +62 -0
- package/src/EmptyState.tsx +36 -0
- package/src/Layout.tsx +32 -0
- package/src/List.tsx +115 -0
- package/src/SelectionControl.tsx +128 -0
- package/src/TextField.tsx +67 -0
- package/src/Typography.tsx +33 -0
- package/src/index.ts +12 -0
- package/src/styles.css +560 -0
- package/src/utils.ts +5 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
import { classes } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
export interface EmptyStateProps extends Omit<
|
|
5
|
+
HTMLAttributes<HTMLDivElement>,
|
|
6
|
+
"title"
|
|
7
|
+
> {
|
|
8
|
+
title: ReactNode;
|
|
9
|
+
description?: ReactNode;
|
|
10
|
+
icon?: ReactNode;
|
|
11
|
+
action?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function EmptyState({
|
|
15
|
+
title,
|
|
16
|
+
description,
|
|
17
|
+
icon,
|
|
18
|
+
action,
|
|
19
|
+
className,
|
|
20
|
+
...props
|
|
21
|
+
}: EmptyStateProps) {
|
|
22
|
+
return (
|
|
23
|
+
<div {...props} className={classes("lo-ui-empty-state", className)}>
|
|
24
|
+
{icon && (
|
|
25
|
+
<div className="lo-ui-empty-state__icon" aria-hidden="true">
|
|
26
|
+
{icon}
|
|
27
|
+
</div>
|
|
28
|
+
)}
|
|
29
|
+
<h2 className="lo-ui-empty-state__title">{title}</h2>
|
|
30
|
+
{description && (
|
|
31
|
+
<p className="lo-ui-empty-state__description">{description}</p>
|
|
32
|
+
)}
|
|
33
|
+
{action && <div className="lo-ui-empty-state__action">{action}</div>}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
package/src/Layout.tsx
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { CSSProperties, HTMLAttributes } from "react";
|
|
2
|
+
import { classes } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
type Gap = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10;
|
|
5
|
+
type LayoutProps = HTMLAttributes<HTMLDivElement> & { gap?: Gap };
|
|
6
|
+
|
|
7
|
+
function layoutStyle(gap: Gap, style?: CSSProperties): CSSProperties {
|
|
8
|
+
return {
|
|
9
|
+
"--lo-layout-gap": `var(--lo-space-${gap})`,
|
|
10
|
+
...style,
|
|
11
|
+
} as CSSProperties;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function Stack({ gap = 4, className, style, ...props }: LayoutProps) {
|
|
15
|
+
return (
|
|
16
|
+
<div
|
|
17
|
+
{...props}
|
|
18
|
+
className={classes("lo-ui-stack", className)}
|
|
19
|
+
style={layoutStyle(gap, style)}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function Inline({ gap = 2, className, style, ...props }: LayoutProps) {
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
{...props}
|
|
28
|
+
className={classes("lo-ui-inline", className)}
|
|
29
|
+
style={layoutStyle(gap, style)}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
package/src/List.tsx
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { forwardRef, useId, type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import { classes } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
export interface ListProps extends HTMLAttributes<HTMLUListElement> {
|
|
5
|
+
label?: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function List({
|
|
9
|
+
label,
|
|
10
|
+
children,
|
|
11
|
+
className,
|
|
12
|
+
"aria-label": ariaLabel,
|
|
13
|
+
"aria-labelledby": ariaLabelledBy,
|
|
14
|
+
...props
|
|
15
|
+
}: ListProps) {
|
|
16
|
+
const generatedId = useId();
|
|
17
|
+
const labelId = label ? `${generatedId}-label` : undefined;
|
|
18
|
+
const effectiveLabelledBy =
|
|
19
|
+
ariaLabelledBy ?? (ariaLabel === undefined ? labelId : undefined);
|
|
20
|
+
return (
|
|
21
|
+
<section className="lo-ui-list-section">
|
|
22
|
+
{label && (
|
|
23
|
+
<h2 className="lo-ui-list-label" id={labelId}>
|
|
24
|
+
{label}
|
|
25
|
+
</h2>
|
|
26
|
+
)}
|
|
27
|
+
<ul
|
|
28
|
+
{...props}
|
|
29
|
+
aria-label={ariaLabel}
|
|
30
|
+
aria-labelledby={effectiveLabelledBy}
|
|
31
|
+
className={classes("lo-ui-list", className)}
|
|
32
|
+
>
|
|
33
|
+
{children}
|
|
34
|
+
</ul>
|
|
35
|
+
</section>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CellProps extends Omit<
|
|
40
|
+
HTMLAttributes<HTMLLIElement>,
|
|
41
|
+
"title"
|
|
42
|
+
> {
|
|
43
|
+
title: ReactNode;
|
|
44
|
+
subtitle?: ReactNode;
|
|
45
|
+
leading?: ReactNode;
|
|
46
|
+
/** Noninteractive metadata included in the row action and its accessible name. */
|
|
47
|
+
trailing?: ReactNode;
|
|
48
|
+
/** An independently operable control rendered beside the row action. */
|
|
49
|
+
trailingAction?: ReactNode;
|
|
50
|
+
onPress?: () => void;
|
|
51
|
+
disabled?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const Cell = forwardRef<HTMLLIElement, CellProps>(function Cell(
|
|
55
|
+
{
|
|
56
|
+
title,
|
|
57
|
+
subtitle,
|
|
58
|
+
leading,
|
|
59
|
+
trailing,
|
|
60
|
+
trailingAction,
|
|
61
|
+
onPress,
|
|
62
|
+
disabled = false,
|
|
63
|
+
className,
|
|
64
|
+
...props
|
|
65
|
+
},
|
|
66
|
+
ref,
|
|
67
|
+
) {
|
|
68
|
+
const content = (
|
|
69
|
+
<>
|
|
70
|
+
{leading && <span className="lo-ui-cell__leading">{leading}</span>}
|
|
71
|
+
<span className="lo-ui-cell__body">
|
|
72
|
+
<span className="lo-ui-cell__title">{title}</span>
|
|
73
|
+
{subtitle && <span className="lo-ui-cell__subtitle">{subtitle}</span>}
|
|
74
|
+
</span>
|
|
75
|
+
{trailing && <span className="lo-ui-cell__trailing">{trailing}</span>}
|
|
76
|
+
{onPress && !trailing && !trailingAction && (
|
|
77
|
+
<svg
|
|
78
|
+
className="lo-ui-cell__chevron"
|
|
79
|
+
viewBox="0 0 20 20"
|
|
80
|
+
aria-hidden="true"
|
|
81
|
+
>
|
|
82
|
+
<path d="m7.5 4 6 6-6 6" />
|
|
83
|
+
</svg>
|
|
84
|
+
)}
|
|
85
|
+
</>
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<li {...props} ref={ref} className={classes("lo-ui-cell", className)}>
|
|
90
|
+
{onPress || trailingAction ? (
|
|
91
|
+
<div className="lo-ui-cell__actions">
|
|
92
|
+
{onPress ? (
|
|
93
|
+
<button
|
|
94
|
+
className="lo-ui-cell__control"
|
|
95
|
+
type="button"
|
|
96
|
+
onClick={onPress}
|
|
97
|
+
disabled={disabled}
|
|
98
|
+
>
|
|
99
|
+
{content}
|
|
100
|
+
</button>
|
|
101
|
+
) : (
|
|
102
|
+
<div className="lo-ui-cell__content">{content}</div>
|
|
103
|
+
)}
|
|
104
|
+
{trailingAction && (
|
|
105
|
+
<span className="lo-ui-cell__trailing-action">
|
|
106
|
+
{trailingAction}
|
|
107
|
+
</span>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
) : (
|
|
111
|
+
<div className="lo-ui-cell__content">{content}</div>
|
|
112
|
+
)}
|
|
113
|
+
</li>
|
|
114
|
+
);
|
|
115
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useId,
|
|
4
|
+
type InputHTMLAttributes,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
} from "react";
|
|
7
|
+
import { classes } from "./utils.js";
|
|
8
|
+
|
|
9
|
+
export type SelectionControlProps = Omit<
|
|
10
|
+
InputHTMLAttributes<HTMLInputElement>,
|
|
11
|
+
"type"
|
|
12
|
+
> & {
|
|
13
|
+
label: ReactNode;
|
|
14
|
+
description?: ReactNode;
|
|
15
|
+
labelHidden?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function SelectionControl({
|
|
19
|
+
control,
|
|
20
|
+
label,
|
|
21
|
+
description,
|
|
22
|
+
labelHidden = false,
|
|
23
|
+
id,
|
|
24
|
+
}: {
|
|
25
|
+
control: ReactNode;
|
|
26
|
+
label: ReactNode;
|
|
27
|
+
description?: ReactNode;
|
|
28
|
+
labelHidden?: boolean;
|
|
29
|
+
id: string;
|
|
30
|
+
}) {
|
|
31
|
+
return (
|
|
32
|
+
<label
|
|
33
|
+
className={classes(
|
|
34
|
+
"lo-ui-selection",
|
|
35
|
+
labelHidden && "lo-ui-selection--control-only",
|
|
36
|
+
)}
|
|
37
|
+
htmlFor={id}
|
|
38
|
+
>
|
|
39
|
+
<span className="lo-ui-selection__copy">
|
|
40
|
+
<span className="lo-ui-selection__label" id={`${id}-label`}>
|
|
41
|
+
{label}
|
|
42
|
+
</span>
|
|
43
|
+
{description && (
|
|
44
|
+
<span
|
|
45
|
+
className="lo-ui-selection__description"
|
|
46
|
+
id={`${id}-description`}
|
|
47
|
+
>
|
|
48
|
+
{description}
|
|
49
|
+
</span>
|
|
50
|
+
)}
|
|
51
|
+
</span>
|
|
52
|
+
{control}
|
|
53
|
+
</label>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const Switch = forwardRef<HTMLInputElement, SelectionControlProps>(
|
|
58
|
+
function Switch(
|
|
59
|
+
{ label, description, labelHidden, id: providedId, className, ...props },
|
|
60
|
+
ref,
|
|
61
|
+
) {
|
|
62
|
+
const generatedId = useId();
|
|
63
|
+
const id = providedId ?? generatedId;
|
|
64
|
+
return (
|
|
65
|
+
<SelectionControl
|
|
66
|
+
id={id}
|
|
67
|
+
label={label}
|
|
68
|
+
description={description}
|
|
69
|
+
labelHidden={labelHidden}
|
|
70
|
+
control={
|
|
71
|
+
<input
|
|
72
|
+
{...props}
|
|
73
|
+
ref={ref}
|
|
74
|
+
id={id}
|
|
75
|
+
type="checkbox"
|
|
76
|
+
role="switch"
|
|
77
|
+
aria-labelledby={
|
|
78
|
+
props["aria-labelledby"] ??
|
|
79
|
+
(props["aria-label"] ? undefined : `${id}-label`)
|
|
80
|
+
}
|
|
81
|
+
aria-describedby={
|
|
82
|
+
[props["aria-describedby"], description && `${id}-description`]
|
|
83
|
+
.filter(Boolean)
|
|
84
|
+
.join(" ") || undefined
|
|
85
|
+
}
|
|
86
|
+
className={classes("lo-ui-switch", className)}
|
|
87
|
+
/>
|
|
88
|
+
}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
export const Checkbox = forwardRef<HTMLInputElement, SelectionControlProps>(
|
|
95
|
+
function Checkbox(
|
|
96
|
+
{ label, description, labelHidden, id: providedId, className, ...props },
|
|
97
|
+
ref,
|
|
98
|
+
) {
|
|
99
|
+
const generatedId = useId();
|
|
100
|
+
const id = providedId ?? generatedId;
|
|
101
|
+
return (
|
|
102
|
+
<SelectionControl
|
|
103
|
+
id={id}
|
|
104
|
+
label={label}
|
|
105
|
+
description={description}
|
|
106
|
+
labelHidden={labelHidden}
|
|
107
|
+
control={
|
|
108
|
+
<input
|
|
109
|
+
{...props}
|
|
110
|
+
ref={ref}
|
|
111
|
+
id={id}
|
|
112
|
+
type="checkbox"
|
|
113
|
+
aria-labelledby={
|
|
114
|
+
props["aria-labelledby"] ??
|
|
115
|
+
(props["aria-label"] ? undefined : `${id}-label`)
|
|
116
|
+
}
|
|
117
|
+
aria-describedby={
|
|
118
|
+
[props["aria-describedby"], description && `${id}-description`]
|
|
119
|
+
.filter(Boolean)
|
|
120
|
+
.join(" ") || undefined
|
|
121
|
+
}
|
|
122
|
+
className={classes("lo-ui-checkbox", className)}
|
|
123
|
+
/>
|
|
124
|
+
}
|
|
125
|
+
/>
|
|
126
|
+
);
|
|
127
|
+
},
|
|
128
|
+
);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useId,
|
|
4
|
+
type InputHTMLAttributes,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
} from "react";
|
|
7
|
+
import { classes } from "./utils.js";
|
|
8
|
+
|
|
9
|
+
export interface TextFieldProps extends Omit<
|
|
10
|
+
InputHTMLAttributes<HTMLInputElement>,
|
|
11
|
+
"size"
|
|
12
|
+
> {
|
|
13
|
+
label: ReactNode;
|
|
14
|
+
description?: ReactNode;
|
|
15
|
+
error?: ReactNode;
|
|
16
|
+
inputClassName?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
|
|
20
|
+
function TextField(
|
|
21
|
+
{
|
|
22
|
+
label,
|
|
23
|
+
description,
|
|
24
|
+
error,
|
|
25
|
+
id: providedId,
|
|
26
|
+
className,
|
|
27
|
+
inputClassName,
|
|
28
|
+
"aria-describedby": ariaDescribedBy,
|
|
29
|
+
...props
|
|
30
|
+
},
|
|
31
|
+
ref,
|
|
32
|
+
) {
|
|
33
|
+
const generatedId = useId();
|
|
34
|
+
const id = providedId ?? generatedId;
|
|
35
|
+
const descriptionId = description ? `${id}-description` : undefined;
|
|
36
|
+
const errorId = error ? `${id}-error` : undefined;
|
|
37
|
+
const describedBy = [ariaDescribedBy, descriptionId, errorId]
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.join(" ");
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className={classes("lo-ui-field", className)}>
|
|
43
|
+
<label className="lo-ui-field__label" htmlFor={id}>
|
|
44
|
+
{label}
|
|
45
|
+
</label>
|
|
46
|
+
<input
|
|
47
|
+
{...props}
|
|
48
|
+
ref={ref}
|
|
49
|
+
id={id}
|
|
50
|
+
aria-invalid={error ? true : props["aria-invalid"]}
|
|
51
|
+
aria-describedby={describedBy || undefined}
|
|
52
|
+
className={classes("lo-ui-field__input", inputClassName)}
|
|
53
|
+
/>
|
|
54
|
+
{description && (
|
|
55
|
+
<div className="lo-ui-field__description" id={descriptionId}>
|
|
56
|
+
{description}
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
59
|
+
{error && (
|
|
60
|
+
<div className="lo-ui-field__error" id={errorId}>
|
|
61
|
+
{error}
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "react";
|
|
2
|
+
import { classes } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
export interface TextProps extends HTMLAttributes<HTMLParagraphElement> {
|
|
5
|
+
tone?: "primary" | "secondary" | "danger";
|
|
6
|
+
size?: "caption" | "label" | "body";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function Text({
|
|
10
|
+
tone = "primary",
|
|
11
|
+
size = "body",
|
|
12
|
+
className,
|
|
13
|
+
...props
|
|
14
|
+
}: TextProps) {
|
|
15
|
+
return (
|
|
16
|
+
<p
|
|
17
|
+
{...props}
|
|
18
|
+
className={classes(
|
|
19
|
+
"lo-ui-text",
|
|
20
|
+
`lo-ui-text--${tone}`,
|
|
21
|
+
`lo-ui-text--${size}`,
|
|
22
|
+
className,
|
|
23
|
+
)}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function Heading({
|
|
29
|
+
className,
|
|
30
|
+
...props
|
|
31
|
+
}: HTMLAttributes<HTMLHeadingElement>) {
|
|
32
|
+
return <h1 {...props} className={classes("lo-ui-heading", className)} />;
|
|
33
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { AppIcon, type AppIconProps } from "./AppIcon.js";
|
|
2
|
+
export { Button, type ButtonProps } from "./Button.js";
|
|
3
|
+
export { EmptyState, type EmptyStateProps } from "./EmptyState.js";
|
|
4
|
+
export { Inline, Stack } from "./Layout.js";
|
|
5
|
+
export { Cell, List, type CellProps, type ListProps } from "./List.js";
|
|
6
|
+
export {
|
|
7
|
+
Checkbox,
|
|
8
|
+
Switch,
|
|
9
|
+
type SelectionControlProps,
|
|
10
|
+
} from "./SelectionControl.js";
|
|
11
|
+
export { TextField, type TextFieldProps } from "./TextField.js";
|
|
12
|
+
export { Heading, Text, type TextProps } from "./Typography.js";
|