shelving 1.209.0 → 1.210.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/markup/rule/link.js +3 -3
- package/package.json +2 -2
- package/ui/block/Card.d.ts +1 -1
- package/ui/block/Card.js +5 -5
- package/ui/block/Card.tsx +5 -6
- package/ui/docs/DirectoryPage.js +1 -4
- package/ui/docs/DirectoryPage.tsx +1 -5
- package/ui/form/ArrayInput.d.ts +1 -1
- package/ui/form/ArrayInput.js +2 -2
- package/ui/form/ArrayInput.tsx +4 -3
- package/ui/form/Button.d.ts +1 -1
- package/ui/form/Button.js +4 -11
- package/ui/form/Button.tsx +3 -14
- package/ui/form/ButtonInput.d.ts +1 -1
- package/ui/form/ButtonInput.js +5 -15
- package/ui/form/ButtonInput.tsx +7 -24
- package/ui/form/CheckboxInput.js +2 -2
- package/ui/form/CheckboxInput.tsx +2 -2
- package/ui/form/Clickable.d.ts +9 -2
- package/ui/form/Clickable.js +12 -7
- package/ui/form/Clickable.tsx +18 -14
- package/ui/form/DictionaryInput.d.ts +1 -1
- package/ui/form/DictionaryInput.js +2 -2
- package/ui/form/DictionaryInput.tsx +4 -3
- package/ui/form/Input.d.ts +8 -8
- package/ui/form/Input.js +10 -23
- package/ui/form/Input.tsx +10 -34
- package/ui/form/OutputInput.d.ts +7 -0
- package/ui/form/OutputInput.js +8 -0
- package/ui/form/OutputInput.tsx +17 -0
- package/ui/form/RadioInput.js +2 -2
- package/ui/form/RadioInput.tsx +2 -2
- package/ui/form/index.d.ts +1 -0
- package/ui/form/index.js +1 -0
- package/ui/form/index.ts +1 -0
- package/ui/inline/Link.js +3 -2
- package/ui/inline/Link.tsx +2 -2
- package/ui/menu/MenuItem.d.ts +4 -6
- package/ui/menu/MenuItem.js +8 -9
- package/ui/menu/MenuItem.tsx +11 -14
- package/ui/misc/Color.d.ts +2 -2
- package/ui/misc/Color.tsx +2 -2
- package/ui/misc/Markup.d.ts +3 -2
- package/ui/misc/Markup.js +6 -2
- package/ui/misc/Markup.tsx +7 -3
- package/ui/misc/Status.d.ts +2 -2
- package/ui/misc/Status.tsx +2 -2
- package/ui/misc/Tag.d.ts +2 -3
- package/ui/misc/Tag.js +8 -5
- package/ui/misc/Tag.tsx +11 -12
- package/ui/page/HTML.js +1 -1
- package/ui/page/HTML.tsx +1 -1
- package/ui/page/Head.js +17 -22
- package/ui/page/Head.tsx +14 -20
- package/ui/router/Navigation.js +2 -2
- package/ui/router/Navigation.tsx +2 -2
- package/ui/router/Router.js +1 -1
- package/ui/router/Router.tsx +1 -1
- package/ui/util/css.d.ts +13 -9
- package/ui/util/css.js +4 -12
- package/ui/util/css.ts +21 -20
- package/ui/util/meta.d.ts +37 -12
- package/ui/util/meta.js +52 -3
- package/ui/util/meta.ts +101 -15
- package/util/link.d.ts +6 -6
- package/util/link.js +17 -16
- package/util/transform.d.ts +4 -2
- package/util/transform.js +6 -1
- package/util/url.d.ts +3 -3
- package/util/url.js +11 -7
package/markup/rule/link.js
CHANGED
|
@@ -8,9 +8,9 @@ import { createMarkupRule } from "../util/rule.js";
|
|
|
8
8
|
/** Render `<a href="">` if the link is a valid one, or `<a>` (with no `href`) if it isn't. */
|
|
9
9
|
function renderLinkMarkupRule({ groups: { title, href: unsafeHref } }, options, key) {
|
|
10
10
|
const { url, root, schemes = HTTP_SCHEMES, rel } = options;
|
|
11
|
-
const
|
|
12
|
-
const href =
|
|
13
|
-
const children = title ? renderMarkup(title, options, "link") :
|
|
11
|
+
const link = getLink(unsafeHref, url, root);
|
|
12
|
+
const href = link && schemes.includes(link.protocol) ? link?.href : undefined;
|
|
13
|
+
const children = title ? renderMarkup(title, options, "link") : link ? formatURI(link) : "";
|
|
14
14
|
return {
|
|
15
15
|
key,
|
|
16
16
|
$$typeof: REACT_ELEMENT_TYPE,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shelving",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.210.0",
|
|
4
4
|
"author": "Dave Houlbrooke <dave@shax.com>",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"build": "bun run --sequential build:*",
|
|
77
77
|
"build:0:setup": "rm -rf ./dist && mkdir -p ./dist",
|
|
78
78
|
"build:1:copy": "cp package.json dist/package.json && cp LICENSE.md dist/LICENSE.md && cp README.md dist/README.md && cp .npmignore dist/.npmignore && cp -r modules/ui dist/ui",
|
|
79
|
-
"build:2:emit": "tsgo",
|
|
79
|
+
"build:2:emit": "tsgo -p tsconfig.build.json",
|
|
80
80
|
"build:3:syntax": "bun run ./dist/index.js",
|
|
81
81
|
"build:4:unit": "bun test ./dist/**/*.test.js --concurrent --only-failures --bail"
|
|
82
82
|
},
|
package/ui/block/Card.d.ts
CHANGED
|
@@ -15,4 +15,4 @@ export interface CardProps extends ClickableProps {
|
|
|
15
15
|
* @example <Card><Heading>Static</Heading></Card>
|
|
16
16
|
* @example <Card href="/foo" title="Open foo"><Heading>Clickable</Heading></Card>
|
|
17
17
|
*/
|
|
18
|
-
export declare function Card({ children,
|
|
18
|
+
export declare function Card({ children, href, onClick, title, ...props }: CardProps): ReactElement;
|
package/ui/block/Card.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { Clickable } from "../form/Clickable.js";
|
|
3
3
|
import { getModuleClass } from "../util/css.js";
|
|
4
|
-
import
|
|
4
|
+
import CARD_CSS from "./Card.module.css";
|
|
5
5
|
/**
|
|
6
6
|
* Cards are boxed areas for content to sit within — rendered as `<article>` since each card represents a self-contained piece of content.
|
|
7
7
|
* - When `href` or `onClick` is set the card becomes navigable: a stretched overlay `<a>` / `<button>` covers the entire card while the children render normally inside.
|
|
@@ -10,7 +10,7 @@ import styles from "./Card.module.css";
|
|
|
10
10
|
* @example <Card><Heading>Static</Heading></Card>
|
|
11
11
|
* @example <Card href="/foo" title="Open foo"><Heading>Clickable</Heading></Card>
|
|
12
12
|
*/
|
|
13
|
-
export function Card({ children,
|
|
14
|
-
const overlay = href || onClick
|
|
15
|
-
return (_jsxs("article", { className: getModuleClass(
|
|
13
|
+
export function Card({ children, href, onClick, title = "Open", ...props }) {
|
|
14
|
+
const overlay = (href || onClick) && _jsx(Clickable, { title: title, href: href, onClick: onClick, ...props, className: CARD_CSS.overlay });
|
|
15
|
+
return (_jsxs("article", { className: getModuleClass(CARD_CSS, "card", props), children: [overlay, overlay ? _jsx("div", { children: children }) : children] }));
|
|
16
16
|
}
|
package/ui/block/Card.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactElement, ReactNode } from "react";
|
|
2
|
-
import { type ClickableProps
|
|
2
|
+
import { Clickable, type ClickableProps } from "../form/Clickable.js";
|
|
3
3
|
import { getModuleClass } from "../util/css.js";
|
|
4
|
-
import
|
|
4
|
+
import CARD_CSS from "./Card.module.css";
|
|
5
5
|
|
|
6
6
|
export interface CardProps extends ClickableProps {
|
|
7
7
|
children?: ReactNode;
|
|
@@ -21,11 +21,10 @@ export interface CardProps extends ClickableProps {
|
|
|
21
21
|
* @example <Card><Heading>Static</Heading></Card>
|
|
22
22
|
* @example <Card href="/foo" title="Open foo"><Heading>Clickable</Heading></Card>
|
|
23
23
|
*/
|
|
24
|
-
export function Card({ children,
|
|
25
|
-
const overlay =
|
|
26
|
-
href || onClick ? getClickable({ disabled, href, onClick, title, target, download, children: title ?? "Open" }, styles.overlay) : null;
|
|
24
|
+
export function Card({ children, href, onClick, title = "Open", ...props }: CardProps): ReactElement {
|
|
25
|
+
const overlay = (href || onClick) && <Clickable title={title} href={href} onClick={onClick} {...props} className={CARD_CSS.overlay} />;
|
|
27
26
|
return (
|
|
28
|
-
<article className={getModuleClass(
|
|
27
|
+
<article className={getModuleClass(CARD_CSS, "card", props)}>
|
|
29
28
|
{overlay}
|
|
30
29
|
{overlay ? <div>{children}</div> : children}
|
|
31
30
|
</article>
|
package/ui/docs/DirectoryPage.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Prose } from "../block/Prose.js";
|
|
3
3
|
import { Markup } from "../misc/Markup.js";
|
|
4
|
-
import { requireMeta } from "../misc/MetaContext.js";
|
|
5
4
|
import { Page } from "../page/Page.js";
|
|
6
5
|
import { TreeCards } from "../tree/TreeCards.js";
|
|
7
6
|
/** Page renderer for a `tree-directory` element — shows title, content, and child cards. */
|
|
8
7
|
export function DirectoryPage({ title, name, description, content, children }) {
|
|
9
|
-
|
|
10
|
-
const path = (url?.pathname ?? "/");
|
|
11
|
-
return (_jsxs(Page, { title: title ?? name, description: description, children: [content && (_jsx(Prose, { children: _jsx(Markup, { children: content }) })), _jsx(TreeCards, { path: path, children: children })] }));
|
|
8
|
+
return (_jsxs(Page, { title: title ?? name, description: description, children: [content && (_jsx(Prose, { children: _jsx(Markup, { children: content }) })), _jsx(TreeCards, { children: children })] }));
|
|
12
9
|
}
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { DirectoryElementProps } from "../../util/element.js";
|
|
3
|
-
import type { AbsolutePath } from "../../util/path.js";
|
|
4
3
|
import { Prose } from "../block/Prose.js";
|
|
5
4
|
import { Markup } from "../misc/Markup.js";
|
|
6
|
-
import { requireMeta } from "../misc/MetaContext.js";
|
|
7
5
|
import { Page } from "../page/Page.js";
|
|
8
6
|
import { TreeCards } from "../tree/TreeCards.js";
|
|
9
7
|
|
|
10
8
|
/** Page renderer for a `tree-directory` element — shows title, content, and child cards. */
|
|
11
9
|
export function DirectoryPage({ title, name, description, content, children }: DirectoryElementProps): ReactNode {
|
|
12
|
-
const { url } = requireMeta();
|
|
13
|
-
const path = (url?.pathname ?? "/") as AbsolutePath;
|
|
14
10
|
return (
|
|
15
11
|
<Page title={title ?? name} description={description}>
|
|
16
12
|
{content && (
|
|
@@ -18,7 +14,7 @@ export function DirectoryPage({ title, name, description, content, children }: D
|
|
|
18
14
|
<Markup>{content}</Markup>
|
|
19
15
|
</Prose>
|
|
20
16
|
)}
|
|
21
|
-
<TreeCards
|
|
17
|
+
<TreeCards>{children}</TreeCards>
|
|
22
18
|
</Page>
|
|
23
19
|
);
|
|
24
20
|
}
|
package/ui/form/ArrayInput.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactElement } from "react";
|
|
2
2
|
import type { Schema } from "../../schema/Schema.js";
|
|
3
3
|
import { type ImmutableArray } from "../../util/array.js";
|
|
4
|
-
import {
|
|
4
|
+
import type { ValueInputProps } from "./Input.js";
|
|
5
5
|
export interface ArrayInputProps<T> extends ValueInputProps<ImmutableArray<T>> {
|
|
6
6
|
one?: string;
|
|
7
7
|
many?: string;
|
package/ui/form/ArrayInput.js
CHANGED
|
@@ -5,7 +5,7 @@ import { splitMessage } from "../../util/error.js";
|
|
|
5
5
|
import { formatUnit } from "../../util/format.js";
|
|
6
6
|
import { Flex } from "../block/Flex.js";
|
|
7
7
|
import { Button } from "./Button.js";
|
|
8
|
-
import {
|
|
8
|
+
import { ButtonInput } from "./ButtonInput.js";
|
|
9
9
|
import { SchemaInput } from "./SchemaInput.js";
|
|
10
10
|
export function ArrayInput({ name,
|
|
11
11
|
// title,
|
|
@@ -17,7 +17,7 @@ placeholder, required = false, disabled = false, message = "", value = [], onVal
|
|
|
17
17
|
return (_jsxs(Flex, { column: true, children: [length ? (value.map((v, i) => {
|
|
18
18
|
const k = i.toString();
|
|
19
19
|
return (_jsxs(Flex, { children: [_jsx(SchemaInput, { name: k, schema: items, value: v, onValue: x => onValue(withArrayIndex(value, i, x)), message: messages[k], disabled: disabled }), _jsx(Button, { onClick: () => onValue(omitArrayIndex(value, i)), disabled: disableRemove, title: disableRemove ? `Minimum ${formatUnit(min, one, { unitDisplay: "long", one, many })}` : `Remove ${one}`, children: _jsx(XMarkIcon, {}) })] }, k));
|
|
20
|
-
})) : (_jsx(
|
|
20
|
+
})) : (_jsx(ButtonInput, { onClick: addNewItem, name: name, required: required && min > 1, disabled: disabled, children: placeholder })), _jsxs(Flex, { children: [_jsxs(Button //
|
|
21
21
|
, { onClick: addNewItem, disabled: disabled || (typeof max === "number" && length >= max), small: true, children: [_jsx(PlusIcon, {}), " Add ", one] }), length && min < 1 ? (_jsxs(Button //
|
|
22
22
|
, { onClick: () => onValue([]), small: true, children: [_jsx(XMarkIcon, {}), " Clear ", many] })) : null] })] }));
|
|
23
23
|
}
|
package/ui/form/ArrayInput.tsx
CHANGED
|
@@ -6,7 +6,8 @@ import { splitMessage } from "../../util/error.js";
|
|
|
6
6
|
import { formatUnit } from "../../util/format.js";
|
|
7
7
|
import { Flex } from "../block/Flex.js";
|
|
8
8
|
import { Button } from "./Button.js";
|
|
9
|
-
import {
|
|
9
|
+
import { ButtonInput } from "./ButtonInput.js";
|
|
10
|
+
import type { ValueInputProps } from "./Input.js";
|
|
10
11
|
import { SchemaInput } from "./SchemaInput.js";
|
|
11
12
|
|
|
12
13
|
export interface ArrayInputProps<T> extends ValueInputProps<ImmutableArray<T>> {
|
|
@@ -64,9 +65,9 @@ export function ArrayInput({
|
|
|
64
65
|
);
|
|
65
66
|
})
|
|
66
67
|
) : (
|
|
67
|
-
<
|
|
68
|
+
<ButtonInput onClick={addNewItem} name={name} required={required && min > 1} disabled={disabled}>
|
|
68
69
|
{placeholder}
|
|
69
|
-
</
|
|
70
|
+
</ButtonInput>
|
|
70
71
|
)}
|
|
71
72
|
<Flex>
|
|
72
73
|
<Button //
|
package/ui/form/Button.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface ButtonVariants extends FlexVariants, StatusVariants, ColorVaria
|
|
|
19
19
|
interface ButtonProps extends ButtonVariants, ClickableProps {
|
|
20
20
|
}
|
|
21
21
|
/** Return either a `<button>` or an `<a href="">` styled as an button, based on whether an `onClick` or `href` prop is provided. */
|
|
22
|
-
export declare function Button(
|
|
22
|
+
export declare function Button(props: ButtonProps): ReactElement;
|
|
23
23
|
/** Get the full className for a button. */
|
|
24
24
|
export declare function getButtonClass(variants: ButtonVariants): string;
|
|
25
25
|
export {};
|
package/ui/form/Button.js
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
2
|
import { FLEX_CSS } from "../block/Flex.js";
|
|
2
3
|
import { getColorClass } from "../misc/Color.js";
|
|
3
4
|
import { getStatusClass } from "../misc/Status.js";
|
|
4
5
|
import { getClass, getModuleClass } from "../util/css.js";
|
|
5
6
|
import BUTTON_CSS from "./Button.module.css";
|
|
6
|
-
import {
|
|
7
|
+
import { Clickable } from "./Clickable.js";
|
|
7
8
|
/** Return either a `<button>` or an `<a href="">` styled as an button, based on whether an `onClick` or `href` prop is provided. */
|
|
8
|
-
export function Button(
|
|
9
|
-
return
|
|
10
|
-
disabled,
|
|
11
|
-
href,
|
|
12
|
-
onClick,
|
|
13
|
-
title,
|
|
14
|
-
target,
|
|
15
|
-
download,
|
|
16
|
-
children,
|
|
17
|
-
}, getButtonClass(variants));
|
|
9
|
+
export function Button(props) {
|
|
10
|
+
return _jsx(Clickable, { ...props, className: getButtonClass(props) });
|
|
18
11
|
}
|
|
19
12
|
/** Get the full className for a button. */
|
|
20
13
|
export function getButtonClass(variants) {
|
package/ui/form/Button.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { type ColorVariants, getColorClass } from "../misc/Color.js";
|
|
|
4
4
|
import { getStatusClass, type StatusVariants } from "../misc/Status.js";
|
|
5
5
|
import { type Classes, getClass, getModuleClass } from "../util/css.js";
|
|
6
6
|
import BUTTON_CSS from "./Button.module.css";
|
|
7
|
-
import { type ClickableProps
|
|
7
|
+
import { Clickable, type ClickableProps } from "./Clickable.js";
|
|
8
8
|
|
|
9
9
|
/** Variants for buttons. */
|
|
10
10
|
export interface ButtonVariants extends FlexVariants, StatusVariants, ColorVariants {
|
|
@@ -23,19 +23,8 @@ export interface ButtonVariants extends FlexVariants, StatusVariants, ColorVaria
|
|
|
23
23
|
interface ButtonProps extends ButtonVariants, ClickableProps {}
|
|
24
24
|
|
|
25
25
|
/** Return either a `<button>` or an `<a href="">` styled as an button, based on whether an `onClick` or `href` prop is provided. */
|
|
26
|
-
export function Button(
|
|
27
|
-
return
|
|
28
|
-
{
|
|
29
|
-
disabled,
|
|
30
|
-
href,
|
|
31
|
-
onClick,
|
|
32
|
-
title,
|
|
33
|
-
target,
|
|
34
|
-
download,
|
|
35
|
-
children,
|
|
36
|
-
},
|
|
37
|
-
getButtonClass(variants),
|
|
38
|
-
);
|
|
26
|
+
export function Button(props: ButtonProps): ReactElement {
|
|
27
|
+
return <Clickable {...props} className={getButtonClass(props)} />;
|
|
39
28
|
}
|
|
40
29
|
|
|
41
30
|
/** Get the full className for a button. */
|
package/ui/form/ButtonInput.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ import { type InputProps } from "./Input.js";
|
|
|
4
4
|
export interface ButtonInputProps extends InputProps, ClickableProps {
|
|
5
5
|
}
|
|
6
6
|
/** Return either a `<button>` or an `<a href="">` styled as an input, based on whether an `onClick` or `href` prop is provided. */
|
|
7
|
-
export declare function ButtonInput({ title, placeholder,
|
|
7
|
+
export declare function ButtonInput({ title, placeholder, children, ...props }: ButtonInputProps): ReactElement;
|
package/ui/form/ButtonInput.js
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
2
|
import { notNullish } from "../../util/null.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
3
|
+
import { Clickable } from "./Clickable.js";
|
|
4
|
+
import { FLEX_BUTTON_INPUT_CLASS, PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS } from "./Input.js";
|
|
4
5
|
/** Return either a `<button>` or an `<a href="">` styled as an input, based on whether an `onClick` or `href` prop is provided. */
|
|
5
|
-
export function ButtonInput({
|
|
6
|
-
// name,
|
|
7
|
-
title, placeholder, disabled, href, onClick, target,
|
|
8
|
-
// message = "",
|
|
9
|
-
download, children = title, }) {
|
|
6
|
+
export function ButtonInput({ title, placeholder, children = title, ...props }) {
|
|
10
7
|
const hasChildren = notNullish(children);
|
|
11
|
-
return
|
|
12
|
-
disabled,
|
|
13
|
-
href,
|
|
14
|
-
onClick,
|
|
15
|
-
target,
|
|
16
|
-
download,
|
|
17
|
-
children: hasChildren ? children : placeholder,
|
|
18
|
-
}, hasChildren ? ELEMENTS_BUTTON_INPUT_CLASS : PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS);
|
|
8
|
+
return (_jsx(Clickable, { ...props, className: hasChildren ? FLEX_BUTTON_INPUT_CLASS : PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS, children: hasChildren ? children : placeholder }));
|
|
19
9
|
}
|
package/ui/form/ButtonInput.tsx
CHANGED
|
@@ -1,33 +1,16 @@
|
|
|
1
1
|
import type { ReactElement } from "react";
|
|
2
2
|
import { notNullish } from "../../util/null.js";
|
|
3
|
-
import { type ClickableProps
|
|
4
|
-
import {
|
|
3
|
+
import { Clickable, type ClickableProps } from "./Clickable.js";
|
|
4
|
+
import { FLEX_BUTTON_INPUT_CLASS, type InputProps, PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS } from "./Input.js";
|
|
5
5
|
|
|
6
6
|
export interface ButtonInputProps extends InputProps, ClickableProps {}
|
|
7
7
|
|
|
8
8
|
/** Return either a `<button>` or an `<a href="">` styled as an input, based on whether an `onClick` or `href` prop is provided. */
|
|
9
|
-
export function ButtonInput({
|
|
10
|
-
// name,
|
|
11
|
-
title,
|
|
12
|
-
placeholder,
|
|
13
|
-
disabled,
|
|
14
|
-
href,
|
|
15
|
-
onClick,
|
|
16
|
-
target,
|
|
17
|
-
// message = "",
|
|
18
|
-
download,
|
|
19
|
-
children = title,
|
|
20
|
-
}: ButtonInputProps): ReactElement {
|
|
9
|
+
export function ButtonInput({ title, placeholder, children = title, ...props }: ButtonInputProps): ReactElement {
|
|
21
10
|
const hasChildren = notNullish(children);
|
|
22
|
-
return
|
|
23
|
-
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
onClick,
|
|
27
|
-
target,
|
|
28
|
-
download,
|
|
29
|
-
children: hasChildren ? children : placeholder,
|
|
30
|
-
},
|
|
31
|
-
hasChildren ? ELEMENTS_BUTTON_INPUT_CLASS : PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS,
|
|
11
|
+
return (
|
|
12
|
+
<Clickable {...props} className={hasChildren ? FLEX_BUTTON_INPUT_CLASS : PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS}>
|
|
13
|
+
{hasChildren ? children : placeholder}
|
|
14
|
+
</Clickable>
|
|
32
15
|
);
|
|
33
16
|
}
|
package/ui/form/CheckboxInput.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { notNullish } from "../../util/null.js";
|
|
3
|
-
import { CHECKBOX_CLASS,
|
|
3
|
+
import { CHECKBOX_CLASS, FLEX_LABEL_INPUT_CLASS } from "./Input.js";
|
|
4
4
|
/** Checkbox element. */
|
|
5
5
|
export function CheckboxInput({ name, title, placeholder = title || "Yes", required = false, disabled = false, message = "", value = false, onValue, children, }) {
|
|
6
6
|
const hasChildren = notNullish(children);
|
|
7
|
-
return (_jsxs("label", { className:
|
|
7
|
+
return (_jsxs("label", { className: FLEX_LABEL_INPUT_CLASS, "aria-invalid": !!message, children: [_jsx("input", { name: name, type: "checkbox", defaultChecked: !!value, onChange: e => onValue?.(!!e.currentTarget.checked), required: required, disabled: disabled, title: message, className: CHECKBOX_CLASS }), _jsx("span", { children: hasChildren ? children : placeholder })] }));
|
|
8
8
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactElement, ReactNode } from "react";
|
|
2
2
|
import { notNullish } from "../../util/null.js";
|
|
3
|
-
import { CHECKBOX_CLASS,
|
|
3
|
+
import { CHECKBOX_CLASS, FLEX_LABEL_INPUT_CLASS, type ValueInputProps } from "./Input.js";
|
|
4
4
|
|
|
5
5
|
export interface CheckboxProps extends ValueInputProps<boolean> {
|
|
6
6
|
children?: ReactNode | undefined;
|
|
@@ -20,7 +20,7 @@ export function CheckboxInput({
|
|
|
20
20
|
}: CheckboxProps): ReactElement {
|
|
21
21
|
const hasChildren = notNullish(children);
|
|
22
22
|
return (
|
|
23
|
-
<label className={
|
|
23
|
+
<label className={FLEX_LABEL_INPUT_CLASS} aria-invalid={!!message}>
|
|
24
24
|
<input
|
|
25
25
|
name={name}
|
|
26
26
|
type="checkbox"
|
package/ui/form/Clickable.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MouseEvent, ReactElement, ReactNode } from "react";
|
|
2
2
|
import type { Path } from "../../util/path.js";
|
|
3
|
-
import {
|
|
3
|
+
import type { ImmutableURI, URIString } from "../../util/uri.js";
|
|
4
4
|
/***
|
|
5
5
|
* Handler for a clickable `onClick` event.
|
|
6
6
|
* - Returned value (if defined) is notified to the user using `notifySuccess()`
|
|
@@ -24,5 +24,12 @@ export interface ClickableProps {
|
|
|
24
24
|
/** The contents of the clickable. */
|
|
25
25
|
children?: ReactNode | undefined;
|
|
26
26
|
}
|
|
27
|
+
export interface StylableClickableProps extends ClickableProps {
|
|
28
|
+
className?: string | undefined;
|
|
29
|
+
}
|
|
27
30
|
/** Return either a `<button>` or an `<a href="">` based on whether an `onClick` or `href` prop is provided. */
|
|
28
|
-
export declare function
|
|
31
|
+
export declare function Clickable(props: StylableClickableProps): ReactElement;
|
|
32
|
+
/** Return an `<a href="">` element. */
|
|
33
|
+
export declare function LinkClickable({ disabled, href, title, target, download, children, className, }: StylableClickableProps): ReactElement;
|
|
34
|
+
/** Return a `<button>` element. */
|
|
35
|
+
export declare function ButtonClickable({ disabled, onClick, title, children, className }: StylableClickableProps): ReactElement;
|
package/ui/form/Clickable.js
CHANGED
|
@@ -2,20 +2,25 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useInstance } from "../../react/useInstance.js";
|
|
3
3
|
import { useStore } from "../../react/useStore.js";
|
|
4
4
|
import { BusyStore } from "../../store/BusyStore.js";
|
|
5
|
-
import {
|
|
5
|
+
import { isURLActive } from "../../util/index.js";
|
|
6
|
+
import { getLink } from "../../util/link.js";
|
|
6
7
|
import { LOADING } from "../misc/Loading.js";
|
|
8
|
+
import { requireMeta } from "../misc/MetaContext.js";
|
|
7
9
|
import { callNotifiedElement } from "../util/notice.js";
|
|
8
10
|
/** Return either a `<button>` or an `<a href="">` based on whether an `onClick` or `href` prop is provided. */
|
|
9
|
-
export function
|
|
10
|
-
return props.href ? _jsx(LinkClickable, { ...props
|
|
11
|
+
export function Clickable(props) {
|
|
12
|
+
return props.href ? _jsx(LinkClickable, { ...props }) : _jsx(ButtonClickable, { ...props });
|
|
11
13
|
}
|
|
12
14
|
/** Return an `<a href="">` element. */
|
|
13
|
-
function LinkClickable({ disabled = false, href, title, target, download, children = "Go", className, }) {
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
export function LinkClickable({ disabled = false, href, title, target, download, children = "Go", className, }) {
|
|
16
|
+
// Resolve `href` against the current page URL and site root so site-absolute paths (`/foo`) honour the base subfolder.
|
|
17
|
+
const { url, root } = requireMeta();
|
|
18
|
+
const link = disabled ? undefined : getLink(href, url, root);
|
|
19
|
+
const active = isURLActive(link, url);
|
|
20
|
+
return (_jsx("a", { href: link?.href, title: title, download: download, target: target, className: className, "aria-current": active ? "page" : undefined, children: children }));
|
|
16
21
|
}
|
|
17
22
|
/** Return a `<button>` element. */
|
|
18
|
-
function ButtonClickable({ disabled = false, onClick, title, children = "Click", className
|
|
23
|
+
export function ButtonClickable({ disabled = false, onClick, title, children = "Click", className }) {
|
|
19
24
|
// Create a `BusyStore<undefined>` to keep track of the `onClick` call and any thrown errors.
|
|
20
25
|
const store = useInstance(BusyStore, undefined);
|
|
21
26
|
// Track the `busy/unbusy` state of the button to show a loading spinner appropriately.
|
package/ui/form/Clickable.tsx
CHANGED
|
@@ -2,9 +2,12 @@ import type { MouseEvent, ReactElement, ReactNode } from "react";
|
|
|
2
2
|
import { useInstance } from "../../react/useInstance.js";
|
|
3
3
|
import { useStore } from "../../react/useStore.js";
|
|
4
4
|
import { BusyStore } from "../../store/BusyStore.js";
|
|
5
|
+
import { isURLActive } from "../../util/index.js";
|
|
6
|
+
import { getLink } from "../../util/link.js";
|
|
5
7
|
import type { Path } from "../../util/path.js";
|
|
6
|
-
import {
|
|
8
|
+
import type { ImmutableURI, URIString } from "../../util/uri.js";
|
|
7
9
|
import { LOADING } from "../misc/Loading.js";
|
|
10
|
+
import { requireMeta } from "../misc/MetaContext.js";
|
|
8
11
|
import { callNotifiedElement } from "../util/notice.js";
|
|
9
12
|
|
|
10
13
|
/***
|
|
@@ -32,13 +35,17 @@ export interface ClickableProps {
|
|
|
32
35
|
children?: ReactNode | undefined;
|
|
33
36
|
}
|
|
34
37
|
|
|
38
|
+
export interface StylableClickableProps extends ClickableProps {
|
|
39
|
+
className?: string | undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
35
42
|
/** Return either a `<button>` or an `<a href="">` based on whether an `onClick` or `href` prop is provided. */
|
|
36
|
-
export function
|
|
37
|
-
return props.href ? <LinkClickable {...props}
|
|
43
|
+
export function Clickable(props: StylableClickableProps): ReactElement {
|
|
44
|
+
return props.href ? <LinkClickable {...props} /> : <ButtonClickable {...props} />;
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
/** Return an `<a href="">` element. */
|
|
41
|
-
function LinkClickable({
|
|
48
|
+
export function LinkClickable({
|
|
42
49
|
disabled = false,
|
|
43
50
|
href,
|
|
44
51
|
title,
|
|
@@ -46,23 +53,20 @@ function LinkClickable({
|
|
|
46
53
|
download,
|
|
47
54
|
children = "Go",
|
|
48
55
|
className,
|
|
49
|
-
}:
|
|
50
|
-
|
|
56
|
+
}: StylableClickableProps): ReactElement {
|
|
57
|
+
// Resolve `href` against the current page URL and site root so site-absolute paths (`/foo`) honour the base subfolder.
|
|
58
|
+
const { url, root } = requireMeta();
|
|
59
|
+
const link = disabled ? undefined : getLink(href, url, root);
|
|
60
|
+
const active = isURLActive(link, url);
|
|
51
61
|
return (
|
|
52
|
-
<a href={link} title={title} download={download} target={target} className={className}>
|
|
62
|
+
<a href={link?.href} title={title} download={download} target={target} className={className} aria-current={active ? "page" : undefined}>
|
|
53
63
|
{children}
|
|
54
64
|
</a>
|
|
55
65
|
);
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
/** Return a `<button>` element. */
|
|
59
|
-
function ButtonClickable({
|
|
60
|
-
disabled = false,
|
|
61
|
-
onClick,
|
|
62
|
-
title,
|
|
63
|
-
children = "Click",
|
|
64
|
-
className,
|
|
65
|
-
}: ClickableProps & { className: string | undefined }): ReactElement {
|
|
69
|
+
export function ButtonClickable({ disabled = false, onClick, title, children = "Click", className }: StylableClickableProps): ReactElement {
|
|
66
70
|
// Create a `BusyStore<undefined>` to keep track of the `onClick` call and any thrown errors.
|
|
67
71
|
const store = useInstance(BusyStore, undefined);
|
|
68
72
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactElement } from "react";
|
|
2
2
|
import type { Schema } from "../../schema/Schema.js";
|
|
3
3
|
import type { ImmutableDictionary } from "../../util/dictionary.js";
|
|
4
|
-
import {
|
|
4
|
+
import type { ValueInputProps } from "./Input.js";
|
|
5
5
|
export interface DictionaryInputProps<T> extends ValueInputProps<ImmutableDictionary<T>> {
|
|
6
6
|
one?: string;
|
|
7
7
|
many?: string;
|
|
@@ -6,7 +6,7 @@ import { formatUnit } from "../../util/format.js";
|
|
|
6
6
|
import { omitProp } from "../../util/object.js";
|
|
7
7
|
import { Flex } from "../block/Flex.js";
|
|
8
8
|
import { Button } from "./Button.js";
|
|
9
|
-
import {
|
|
9
|
+
import { ButtonInput } from "./ButtonInput.js";
|
|
10
10
|
import { SchemaInput } from "./SchemaInput.js";
|
|
11
11
|
import { TextInput } from "./TextInput.js";
|
|
12
12
|
export function DictionaryInput({ name,
|
|
@@ -26,7 +26,7 @@ placeholder, required = false, disabled = false, message = "", value = {}, onVal
|
|
|
26
26
|
onValue(withDictionaryItem(omitDictionaryItem(value, k), x, v));
|
|
27
27
|
k = x; // Update this so it works until content is re-rendered.
|
|
28
28
|
}, message: message, disabled: disabled }), _jsx(SchemaInput, { name: `${r}-value`, schema: items, value: v, onValue: x => onValue(withDictionaryItem(value, k, x)), message: message, disabled: disabled }), _jsx(Button, { onClick: () => onValue(omitProp(value, k)), disabled: disableRemove, title: disableRemove ? `Minimum ${formatUnit(min, one, { unitDisplay: "long", one, many })}` : `Remove ${one}`, children: _jsx(XMarkIcon, {}) })] }, r));
|
|
29
|
-
})) : (_jsx(
|
|
29
|
+
})) : (_jsx(ButtonInput, { onClick: addNewItem, name: name, required: required && min > 1, disabled: disabled, children: placeholder })), _jsxs(Flex, { children: [_jsxs(Button //
|
|
30
30
|
, { onClick: addNewItem, disabled: disabled || (typeof max === "number" && length >= max), small: true, children: [_jsx(PlusIcon, {}), " Add ", one] }), length && min < 1 ? (_jsxs(Button //
|
|
31
31
|
, { onClick: () => onValue({}), small: true, children: [_jsx(XMarkIcon, {}), " Clear ", many] })) : null] })] }));
|
|
32
32
|
}
|
|
@@ -8,7 +8,8 @@ import { formatUnit } from "../../util/format.js";
|
|
|
8
8
|
import { omitProp } from "../../util/object.js";
|
|
9
9
|
import { Flex } from "../block/Flex.js";
|
|
10
10
|
import { Button } from "./Button.js";
|
|
11
|
-
import {
|
|
11
|
+
import { ButtonInput } from "./ButtonInput.js";
|
|
12
|
+
import type { ValueInputProps } from "./Input.js";
|
|
12
13
|
import { SchemaInput } from "./SchemaInput.js";
|
|
13
14
|
import { TextInput } from "./TextInput.js";
|
|
14
15
|
|
|
@@ -80,9 +81,9 @@ export function DictionaryInput({
|
|
|
80
81
|
);
|
|
81
82
|
})
|
|
82
83
|
) : (
|
|
83
|
-
<
|
|
84
|
+
<ButtonInput onClick={addNewItem} name={name} required={required && min > 1} disabled={disabled}>
|
|
84
85
|
{placeholder}
|
|
85
|
-
</
|
|
86
|
+
</ButtonInput>
|
|
86
87
|
)}
|
|
87
88
|
<Flex>
|
|
88
89
|
<Button //
|
package/ui/form/Input.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ReactElement, ReactNode } from "react";
|
|
2
|
-
import { type ClickableProps } from "./Clickable.js";
|
|
3
2
|
export declare const RADIO_CLASS: any;
|
|
4
3
|
export declare const CHECKBOX_CLASS: any;
|
|
5
4
|
export declare const PLACEHOLDER_CLASS: any;
|
|
@@ -9,11 +8,11 @@ export declare const MULTILINE_TEXT_INPUT_CLASS: string;
|
|
|
9
8
|
export declare const SELECT_INPUT_CLASS: string;
|
|
10
9
|
export declare const EMPTY_OPTION_INPUT_CLASS: any;
|
|
11
10
|
export declare const VALUE_OPTION_INPUT_CLASS: any;
|
|
12
|
-
export declare const
|
|
13
|
-
export declare const
|
|
11
|
+
export declare const FLEX_INPUT_CLASS: string;
|
|
12
|
+
export declare const FLEX_BUTTON_INPUT_CLASS: string;
|
|
14
13
|
export declare const PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS: string;
|
|
15
|
-
export declare const
|
|
16
|
-
export declare const
|
|
14
|
+
export declare const FLEX_LABEL_INPUT_CLASS: string;
|
|
15
|
+
export declare const PLACEHOLDER_FLEX_LABEL_INPUT_CLASS: string;
|
|
17
16
|
export declare const WRAPPER_INPUT_CLASS: string;
|
|
18
17
|
/** Props all inputs allow. */
|
|
19
18
|
export interface InputProps {
|
|
@@ -37,11 +36,12 @@ export interface ValueInputProps<O, I = never> extends InputProps {
|
|
|
37
36
|
/** Called when the value for the input changes, so you can make changes based on the new value (or `undefined` to set back to default). */
|
|
38
37
|
onValue(value: O | undefined): void;
|
|
39
38
|
}
|
|
40
|
-
/** Return either a `<button>` or an `<a href="">` styled as an input, based on whether an `onClick` or `href` prop is provided. */
|
|
41
|
-
export declare function Input({ title, placeholder, disabled, href, onClick, target, download, children, }: InputProps & ClickableProps): ReactElement;
|
|
42
39
|
/** Input that is loading. */
|
|
43
40
|
export declare const LOADING_INPUT: import("react/jsx-runtime").JSX.Element;
|
|
44
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Wraps an input with support for absolutely-positioned `data-slot` icon elements on either side.
|
|
43
|
+
* - This is so you can put an icon before or after an input.
|
|
44
|
+
*/
|
|
45
45
|
export declare function InputWrapper({ children }: {
|
|
46
46
|
children: ReactNode;
|
|
47
47
|
}): ReactElement;
|
package/ui/form/Input.js
CHANGED
|
@@ -2,7 +2,6 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { FLEX_CSS } from "../block/Flex.js";
|
|
3
3
|
import { LOADING } from "../misc/Loading.js";
|
|
4
4
|
import { getClass } from "../util/css.js";
|
|
5
|
-
import { getClickable } from "./Clickable.js";
|
|
6
5
|
import INPUT_CSS from "./Input.module.css";
|
|
7
6
|
// Precomposed classes.
|
|
8
7
|
export const RADIO_CLASS = INPUT_CSS.radio;
|
|
@@ -14,30 +13,18 @@ export const MULTILINE_TEXT_INPUT_CLASS = getClass(TEXT_INPUT_CLASS, INPUT_CSS.m
|
|
|
14
13
|
export const SELECT_INPUT_CLASS = getClass(INPUT_CSS.input, INPUT_CSS.select);
|
|
15
14
|
export const EMPTY_OPTION_INPUT_CLASS = INPUT_CSS.empty;
|
|
16
15
|
export const VALUE_OPTION_INPUT_CLASS = INPUT_CSS.value;
|
|
17
|
-
export const
|
|
18
|
-
export const
|
|
19
|
-
export const PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS = getClass(
|
|
20
|
-
export const
|
|
21
|
-
export const
|
|
16
|
+
export const FLEX_INPUT_CLASS = getClass(INPUT_CSS.input, FLEX_CSS.elements, FLEX_CSS.center);
|
|
17
|
+
export const FLEX_BUTTON_INPUT_CLASS = getClass(INPUT_CSS.input, INPUT_CSS.button, FLEX_CSS.elements, FLEX_CSS.center);
|
|
18
|
+
export const PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS = getClass(FLEX_BUTTON_INPUT_CLASS, INPUT_CSS.placeholder);
|
|
19
|
+
export const FLEX_LABEL_INPUT_CLASS = getClass(INPUT_CSS.input, INPUT_CSS.label, FLEX_CSS.elements, FLEX_CSS.left);
|
|
20
|
+
export const PLACEHOLDER_FLEX_LABEL_INPUT_CLASS = getClass(FLEX_LABEL_INPUT_CLASS, INPUT_CSS.placeholder);
|
|
22
21
|
export const WRAPPER_INPUT_CLASS = getClass(INPUT_CSS.input, INPUT_CSS.wrapper);
|
|
23
|
-
/** Return either a `<button>` or an `<a href="">` styled as an input, based on whether an `onClick` or `href` prop is provided. */
|
|
24
|
-
export function Input({
|
|
25
|
-
// name,
|
|
26
|
-
title, placeholder = title,
|
|
27
|
-
// message,
|
|
28
|
-
disabled, href, onClick, target, download, children, }) {
|
|
29
|
-
return getClickable({
|
|
30
|
-
disabled,
|
|
31
|
-
href,
|
|
32
|
-
onClick,
|
|
33
|
-
target,
|
|
34
|
-
download,
|
|
35
|
-
children: children || placeholder,
|
|
36
|
-
}, getClass(INPUT_CSS.input, INPUT_CSS.button, children ? undefined : INPUT_CSS.placeholder));
|
|
37
|
-
}
|
|
38
22
|
/** Input that is loading. */
|
|
39
|
-
export const LOADING_INPUT = _jsx("div", { className:
|
|
40
|
-
/**
|
|
23
|
+
export const LOADING_INPUT = _jsx("div", { className: FLEX_INPUT_CLASS, children: LOADING });
|
|
24
|
+
/**
|
|
25
|
+
* Wraps an input with support for absolutely-positioned `data-slot` icon elements on either side.
|
|
26
|
+
* - This is so you can put an icon before or after an input.
|
|
27
|
+
*/
|
|
41
28
|
export function InputWrapper({ children }) {
|
|
42
29
|
return _jsx("div", { className: WRAPPER_INPUT_CLASS, children: children });
|
|
43
30
|
}
|