@stll/ui 0.55.1 → 0.57.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 +3 -0
- package/THIRD_PARTY_NOTICES +24 -0
- package/dist/components/badge.d.ts +28 -0
- package/dist/components/badge.js +64 -0
- package/dist/components/button.d.ts +2 -6
- package/dist/components/button.js +33 -25
- package/dist/components/button.native.d.ts +15 -0
- package/dist/components/button.native.js +165 -0
- package/dist/components/button.shared.d.ts +65 -0
- package/dist/components/button.shared.js +61 -0
- package/dist/components/dialog.d.ts +20 -2
- package/dist/components/dialog.js +31 -9
- package/dist/components/horizontal-scroller.js +31 -34
- package/dist/components/input.native.d.ts +23 -0
- package/dist/components/input.native.js +66 -0
- package/dist/components/input.shared.d.ts +19 -0
- package/dist/components/input.shared.js +0 -0
- package/dist/components/loader.d.ts +1 -6
- package/dist/components/loader.native.d.ts +28 -0
- package/dist/components/loader.native.js +23 -0
- package/dist/components/loader.shared.d.ts +17 -0
- package/dist/components/loader.shared.js +9 -0
- package/dist/components/matter-icon.d.ts +2 -8
- package/dist/components/matter-icon.js +10 -30
- package/dist/components/matter-icon.native.d.ts +25 -0
- package/dist/components/matter-icon.native.js +78 -0
- package/dist/components/matter-icon.shared.d.ts +21 -0
- package/dist/components/matter-icon.shared.js +18 -0
- package/dist/components/separator.native.d.ts +14 -0
- package/dist/components/separator.native.js +16 -0
- package/dist/components/separator.shared.d.ts +6 -0
- package/dist/components/separator.shared.js +0 -0
- package/dist/components/sheet.native.d.ts +27 -0
- package/dist/components/sheet.native.js +69 -0
- package/dist/components/sheet.shared.d.ts +9 -0
- package/dist/components/sheet.shared.js +0 -0
- package/dist/components/sidebar.js +1 -1
- package/dist/components/stella-wordmark.js +5 -13
- package/dist/components/stella-wordmark.native.d.ts +9 -0
- package/dist/components/stella-wordmark.native.js +27 -0
- package/dist/components/stella-wordmark.shared.d.ts +6 -0
- package/dist/components/stella-wordmark.shared.js +16 -0
- package/dist/components/switch.native.d.ts +13 -0
- package/dist/components/switch.native.js +33 -0
- package/dist/components/switch.shared.d.ts +9 -0
- package/dist/components/switch.shared.js +0 -0
- package/dist/components/table.d.ts +86 -8
- package/dist/components/table.js +46 -20
- package/dist/components/text.d.ts +11 -0
- package/dist/components/text.js +25 -0
- package/dist/components/text.native.d.ts +15 -0
- package/dist/components/text.native.js +46 -0
- package/dist/components/text.shared.d.ts +78 -0
- package/dist/components/text.shared.js +71 -0
- package/dist/components/textarea.native.d.ts +10 -0
- package/dist/components/textarea.native.js +28 -0
- package/dist/components/textarea.shared.d.ts +4 -0
- package/dist/components/textarea.shared.js +0 -0
- package/dist/components/tooltip.d.ts +15 -2
- package/dist/components/tooltip.js +18 -2
- package/dist/components/truncated-text.d.ts +33 -0
- package/dist/components/truncated-text.js +111 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +5 -3
- package/dist/inspector/dock.js +1 -1
- package/dist/lib/scroll-edge-fade.d.ts +23 -0
- package/dist/lib/scroll-edge-fade.js +62 -0
- package/dist/native-env.d.ts +1 -0
- package/package.json +112 -7
- package/dist/calendar/index.d.ts +0 -4
- package/dist/calendar/index.js +0 -4
package/README.md
CHANGED
|
@@ -13,6 +13,9 @@ export map below is what "part of the design system" means.
|
|
|
13
13
|
Peer dependencies: `react`, `react-dom`, `@base-ui/react`, `tailwindcss` (v4),
|
|
14
14
|
and `@tanstack/react-virtual`.
|
|
15
15
|
|
|
16
|
+
Derived in part from [coss ui](https://github.com/cosscom/ui) (MIT); see
|
|
17
|
+
`THIRD_PARTY_NOTICES`.
|
|
18
|
+
|
|
16
19
|
## Import
|
|
17
20
|
|
|
18
21
|
Every module has its own subpath, and the package declares `sideEffects: false`,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Portions of @stll/ui are derived from coss ui (https://github.com/cosscom/ui):
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2025 coss.com
|
|
6
|
+
Originally Copyright (c) 2025 Origin UI
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
SOFTWARE.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { StatusTone } from "./list.js";
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
3
|
+
//#region src/components/badge.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A short label that classifies the thing beside it: a status, a type, a
|
|
6
|
+
* count. The words carry the meaning; the tone and the optional dot only
|
|
7
|
+
* reinforce it, so colour is never the sole signal.
|
|
8
|
+
*
|
|
9
|
+
* The tones are the ones `ListItemStatus` uses, plus `info`, so a row that
|
|
10
|
+
* shows a status as a dot and label and a card that shows it as a badge read
|
|
11
|
+
* as the same colour.
|
|
12
|
+
*/
|
|
13
|
+
declare function Badge({ className, tone, size, dot, children, render, ...props }: BadgeProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
14
|
+
type BadgeTone = StatusTone | "info";
|
|
15
|
+
/** `sm` stands on its own beside a title; `xs` sits in a line of meta text. */
|
|
16
|
+
type BadgeSize = "xs" | "sm";
|
|
17
|
+
type BadgeProps = useRender.ComponentProps<"span"> & {
|
|
18
|
+
tone?: BadgeTone;
|
|
19
|
+
size?: BadgeSize;
|
|
20
|
+
/** A leading dot in the tone's colour, for a status rather than a type. */
|
|
21
|
+
dot?: boolean;
|
|
22
|
+
};
|
|
23
|
+
declare const badgeVariants: (props?: ({
|
|
24
|
+
size?: "sm" | "xs" | null | undefined;
|
|
25
|
+
tone?: "destructive" | "info" | "neutral" | "success" | "warning" | null | undefined;
|
|
26
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
27
|
+
//#endregion
|
|
28
|
+
export { Badge, type BadgeProps, type BadgeSize, type BadgeTone, badgeVariants };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
4
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
5
|
+
import { cva } from "class-variance-authority";
|
|
6
|
+
//#region src/components/badge.tsx
|
|
7
|
+
/**
|
|
8
|
+
* A short label that classifies the thing beside it: a status, a type, a
|
|
9
|
+
* count. The words carry the meaning; the tone and the optional dot only
|
|
10
|
+
* reinforce it, so colour is never the sole signal.
|
|
11
|
+
*
|
|
12
|
+
* The tones are the ones `ListItemStatus` uses, plus `info`, so a row that
|
|
13
|
+
* shows a status as a dot and label and a card that shows it as a badge read
|
|
14
|
+
* as the same colour.
|
|
15
|
+
*/
|
|
16
|
+
function Badge({ className, tone = "neutral", size = "sm", dot = false, children, render, ...props }) {
|
|
17
|
+
const defaultProps = {
|
|
18
|
+
className: cn(badgeVariants({
|
|
19
|
+
size,
|
|
20
|
+
tone
|
|
21
|
+
}), className),
|
|
22
|
+
"data-slot": "badge",
|
|
23
|
+
"data-tone": tone,
|
|
24
|
+
"data-size": size,
|
|
25
|
+
children: /* @__PURE__ */ jsxs(Fragment, { children: [dot ? /* @__PURE__ */ jsx("span", {
|
|
26
|
+
"aria-hidden": "true",
|
|
27
|
+
className: cn("size-1.5 shrink-0 rounded-full", BADGE_DOT_CLASS[tone]),
|
|
28
|
+
"data-slot": "badge-dot"
|
|
29
|
+
}) : null, children] })
|
|
30
|
+
};
|
|
31
|
+
return useRender({
|
|
32
|
+
defaultTagName: "span",
|
|
33
|
+
props: mergeProps(defaultProps, props),
|
|
34
|
+
render
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const badgeVariants = cva("inline-flex w-fit shrink-0 items-center rounded-md font-medium whitespace-nowrap tabular-nums [&_svg]:pointer-events-none [&_svg]:shrink-0", {
|
|
38
|
+
defaultVariants: {
|
|
39
|
+
size: "sm",
|
|
40
|
+
tone: "neutral"
|
|
41
|
+
},
|
|
42
|
+
variants: {
|
|
43
|
+
size: {
|
|
44
|
+
sm: "h-5 gap-1.5 px-2 text-xs [&_svg:not([class*='size-'])]:size-3.5",
|
|
45
|
+
xs: "text-2xs h-4 gap-1 px-1.5 [&_svg:not([class*='size-'])]:size-3"
|
|
46
|
+
},
|
|
47
|
+
tone: {
|
|
48
|
+
destructive: "bg-destructive/12 text-destructive-foreground",
|
|
49
|
+
info: "bg-info/12 text-info-foreground",
|
|
50
|
+
neutral: "bg-muted text-muted-foreground",
|
|
51
|
+
success: "bg-success/12 text-success-foreground",
|
|
52
|
+
warning: "bg-warning/12 text-warning-foreground"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
const BADGE_DOT_CLASS = {
|
|
57
|
+
destructive: "bg-destructive",
|
|
58
|
+
info: "bg-info",
|
|
59
|
+
neutral: "bg-foreground-disabled",
|
|
60
|
+
success: "bg-success",
|
|
61
|
+
warning: "bg-warning"
|
|
62
|
+
};
|
|
63
|
+
//#endregion
|
|
64
|
+
export { Badge, badgeVariants };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { ButtonContract } from "./button.shared.js";
|
|
1
2
|
import { ReactNode } from "react";
|
|
2
|
-
import { VariantProps } from "class-variance-authority";
|
|
3
3
|
import { useRender } from "@base-ui/react/use-render";
|
|
4
4
|
//#region src/components/button.d.ts
|
|
5
5
|
/**
|
|
@@ -46,11 +46,7 @@ declare const BUTTON_SIZE_CONTENT: {
|
|
|
46
46
|
readonly xl: "text";
|
|
47
47
|
readonly xs: "text";
|
|
48
48
|
};
|
|
49
|
-
type ButtonProps = {
|
|
50
|
-
variant?: VariantProps<typeof buttonVariants>["variant"];
|
|
51
|
-
size?: VariantProps<typeof buttonVariants>["size"];
|
|
52
|
-
loading?: boolean;
|
|
53
|
-
icon?: ReactNode;
|
|
49
|
+
type ButtonProps = Omit<ButtonContract, "children" | "disabled"> & {
|
|
54
50
|
tooltip?: ReactNode;
|
|
55
51
|
"data-accessible-disabled"?: "";
|
|
56
52
|
} & useRender.ComponentProps<"button">;
|
|
@@ -3,6 +3,7 @@ import { cn } from "../lib/utils.js";
|
|
|
3
3
|
import { renderTooltipTrigger } from "./tooltip-trigger-helper.js";
|
|
4
4
|
import { BUTTON_DISPOSITION, blockDisabledActivation, blockDisabledKeyActivation, resolveButtonDisposition } from "../lib/button-disposition.js";
|
|
5
5
|
import { MENU_ROW_CLASS_NAME } from "../lib/menu-row.js";
|
|
6
|
+
import { BUTTON_VARIANT_TOKENS } from "./button.shared.js";
|
|
6
7
|
import { Loader } from "./loader.js";
|
|
7
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
9
|
import { mergeProps } from "@base-ui/react/merge-props";
|
|
@@ -31,37 +32,44 @@ const buttonAccessibleDisabledClass = "cursor-not-allowed opacity-64";
|
|
|
31
32
|
*/
|
|
32
33
|
const buttonPressClass = "motion-safe:transition-transform motion-safe:duration-150 ease-out motion-safe:active:scale-[0.98]";
|
|
33
34
|
const BUTTON_DEFAULT_SIZE = "default";
|
|
35
|
+
/** A variant's shared colours (./button.shared.ts), before the web's states. */
|
|
36
|
+
const tokens = (variant) => {
|
|
37
|
+
const { surface, label } = BUTTON_VARIANT_TOKENS[variant];
|
|
38
|
+
return label === "" ? surface : `${surface} ${label}`;
|
|
39
|
+
};
|
|
40
|
+
const BUTTON_SIZE_CLASS_NAMES = {
|
|
41
|
+
chip: "h-7 gap-1.5 rounded-full px-2.5 text-xs before:rounded-full sm:text-xs",
|
|
42
|
+
default: "h-9 px-[calc(--spacing(3)-1px)] sm:h-8",
|
|
43
|
+
icon: "size-9 sm:size-8",
|
|
44
|
+
"icon-lg": "size-10 sm:size-9",
|
|
45
|
+
"icon-sm": "size-8 sm:size-7",
|
|
46
|
+
"icon-xl": "size-11 sm:size-10 [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5",
|
|
47
|
+
"icon-xs": "size-7 rounded-md before:rounded-[calc(var(--radius-md)-1px)] sm:size-6 not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-4 sm:not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-3.5",
|
|
48
|
+
lg: "h-10 px-[calc(--spacing(3.5)-1px)] sm:h-9",
|
|
49
|
+
row: `${MENU_ROW_CLASS_NAME} [&_svg]:mx-0`,
|
|
50
|
+
sm: "h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] text-sm sm:h-7 sm:text-xs",
|
|
51
|
+
xl: "h-11 px-[calc(--spacing(4)-1px)] text-lg sm:h-10 sm:text-base [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5",
|
|
52
|
+
xs: "h-7 gap-1 rounded-md px-[calc(--spacing(2)-1px)] text-sm before:rounded-[calc(var(--radius-md)-1px)] sm:h-6 sm:text-xs [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5"
|
|
53
|
+
};
|
|
54
|
+
const BUTTON_VARIANT_CLASS_NAMES = {
|
|
55
|
+
default: `${tokens("default")} shadow-primary/24 [:hover,[data-pressed]]:bg-primary/90 shadow-xs not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none`,
|
|
56
|
+
destructive: `${tokens("destructive")} shadow-destructive/8 [:hover,[data-pressed]]:border-destructive/40 [:hover,[data-pressed]]:bg-destructive/14 [:active,[data-pressed]]:bg-destructive/18 shadow-xs not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] [:disabled,:active,[data-pressed]]:shadow-none`,
|
|
57
|
+
"destructive-outline": `${tokens("destructive-outline")} dark:bg-input/32 [:hover,[data-pressed]]:border-destructive/32 [:hover,[data-pressed]]:bg-destructive/4 shadow-xs/5 not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none`,
|
|
58
|
+
"destructive-ghost": `${tokens("destructive-ghost")} data-pressed:bg-destructive/10 [:hover,[data-pressed]]:bg-destructive/10`,
|
|
59
|
+
ghost: `${tokens("ghost")} data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent`,
|
|
60
|
+
link: `${tokens("link")} underline-offset-4 [:hover,[data-pressed]]:underline`,
|
|
61
|
+
muted: `${tokens("muted")} data-pressed:bg-accent data-pressed:text-foreground [:hover,[data-pressed]]:bg-accent [:hover,[data-pressed]]:text-foreground`,
|
|
62
|
+
outline: `${tokens("outline")} dark:bg-input/32 [:hover,[data-pressed]]:bg-accent/50 dark:[:hover,[data-pressed]]:bg-input/64 shadow-xs/5 not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none`,
|
|
63
|
+
secondary: `${tokens("secondary")} [:active,[data-pressed]]:bg-secondary/80 [:hover,[data-pressed]]:bg-secondary/90`
|
|
64
|
+
};
|
|
34
65
|
const buttonVariants = cva("focus-visible:ring-ring focus-visible:ring-offset-background relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-lg border text-base font-medium whitespace-nowrap outline-none select-none before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] focus-visible:ring-2 focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-64 sm:text-sm pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 [&_svg]:pointer-events-none [&_svg]:-mx-0.5 [&_svg]:shrink-0 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4", {
|
|
35
66
|
defaultVariants: {
|
|
36
67
|
size: BUTTON_DEFAULT_SIZE,
|
|
37
68
|
variant: "default"
|
|
38
69
|
},
|
|
39
70
|
variants: {
|
|
40
|
-
size:
|
|
41
|
-
|
|
42
|
-
default: "h-9 px-[calc(--spacing(3)-1px)] sm:h-8",
|
|
43
|
-
icon: "size-9 sm:size-8",
|
|
44
|
-
"icon-lg": "size-10 sm:size-9",
|
|
45
|
-
"icon-sm": "size-8 sm:size-7",
|
|
46
|
-
"icon-xl": "size-11 sm:size-10 [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5",
|
|
47
|
-
"icon-xs": "size-7 rounded-md before:rounded-[calc(var(--radius-md)-1px)] sm:size-6 not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-4 sm:not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-3.5",
|
|
48
|
-
lg: "h-10 px-[calc(--spacing(3.5)-1px)] sm:h-9",
|
|
49
|
-
row: `${MENU_ROW_CLASS_NAME} [&_svg]:mx-0`,
|
|
50
|
-
sm: "h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] text-sm sm:h-7 sm:text-xs",
|
|
51
|
-
xl: "h-11 px-[calc(--spacing(4)-1px)] text-lg sm:h-10 sm:text-base [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5",
|
|
52
|
-
xs: "h-7 gap-1 rounded-md px-[calc(--spacing(2)-1px)] text-sm before:rounded-[calc(var(--radius-md)-1px)] sm:h-6 sm:text-xs [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5"
|
|
53
|
-
},
|
|
54
|
-
variant: {
|
|
55
|
-
default: "border-primary bg-primary text-primary-foreground shadow-primary/24 [:hover,[data-pressed]]:bg-primary/90 shadow-xs not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none",
|
|
56
|
-
destructive: "border-destructive/32 bg-destructive/10 text-destructive-foreground shadow-destructive/8 [:hover,[data-pressed]]:border-destructive/40 [:hover,[data-pressed]]:bg-destructive/14 [:active,[data-pressed]]:bg-destructive/18 shadow-xs not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] [:disabled,:active,[data-pressed]]:shadow-none",
|
|
57
|
-
"destructive-outline": "border-input bg-popover text-destructive-foreground dark:bg-input/32 [:hover,[data-pressed]]:border-destructive/32 [:hover,[data-pressed]]:bg-destructive/4 shadow-xs/5 not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none",
|
|
58
|
-
"destructive-ghost": "text-destructive data-pressed:bg-destructive/10 [:hover,[data-pressed]]:bg-destructive/10 border-transparent",
|
|
59
|
-
ghost: "text-foreground data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent border-transparent",
|
|
60
|
-
link: "border-transparent underline-offset-4 [:hover,[data-pressed]]:underline",
|
|
61
|
-
muted: "text-muted-foreground data-pressed:bg-accent data-pressed:text-foreground [:hover,[data-pressed]]:bg-accent [:hover,[data-pressed]]:text-foreground border-transparent",
|
|
62
|
-
outline: "border-input bg-popover text-foreground dark:bg-input/32 [:hover,[data-pressed]]:bg-accent/50 dark:[:hover,[data-pressed]]:bg-input/64 shadow-xs/5 not-dark:bg-clip-padding not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none",
|
|
63
|
-
secondary: "bg-secondary text-secondary-foreground [:active,[data-pressed]]:bg-secondary/80 [:hover,[data-pressed]]:bg-secondary/90 border-transparent"
|
|
64
|
-
}
|
|
71
|
+
size: BUTTON_SIZE_CLASS_NAMES,
|
|
72
|
+
variant: BUTTON_VARIANT_CLASS_NAMES
|
|
65
73
|
}
|
|
66
74
|
});
|
|
67
75
|
/** What a button's box holds: a glyph alone, or a visible label. */
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ButtonContract } from "./button.shared.js";
|
|
2
|
+
import { PressableProps } from "react-native";
|
|
3
|
+
//#region src/components/button.native.d.ts
|
|
4
|
+
type ButtonProps = Omit<PressableProps, "children" | "disabled"> & ButtonContract & {
|
|
5
|
+
className?: string | undefined;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* The web's `Button` (./button.tsx) on a phone: the same variants and sizes,
|
|
9
|
+
* read from ./button.shared.ts, on a Pressable. A string child is set as the
|
|
10
|
+
* button's label in its variant's colour; any other child is laid out as
|
|
11
|
+
* given, which is how a row holds a glyph beside two lines of text.
|
|
12
|
+
*/
|
|
13
|
+
export declare const Button: ({ accessibilityState, children, className, disabled, hitSlop, icon, loading, size, variant, ...props }: ButtonProps) => import("react").JSX.Element;
|
|
14
|
+
//#endregion
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { BUTTON_VARIANT_TOKENS } from "./button.shared.js";
|
|
2
|
+
import { Loader } from "./loader.native.js";
|
|
3
|
+
import { Text as Text$1 } from "./text.native.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { Pressable } from "react-native";
|
|
6
|
+
//#region src/components/button.native.tsx
|
|
7
|
+
/** A control's box: centred content inside the variant's border. */
|
|
8
|
+
const CONTROL = "items-center justify-center border";
|
|
9
|
+
/**
|
|
10
|
+
* Every shared size in a phone's metrics. A text size grows to the platform's
|
|
11
|
+
* 44-point touch target where the web relies on a coarse-pointer hit area; a
|
|
12
|
+
* smaller control keeps its look and reaches the target through `slop`.
|
|
13
|
+
*/
|
|
14
|
+
const SIZES = {
|
|
15
|
+
chip: {
|
|
16
|
+
box: `${CONTROL} min-h-7 gap-1.5 rounded-full px-2.5`,
|
|
17
|
+
label: "text-xs",
|
|
18
|
+
slop: 8
|
|
19
|
+
},
|
|
20
|
+
default: {
|
|
21
|
+
box: `${CONTROL} min-h-11 gap-2 rounded-lg px-4`,
|
|
22
|
+
label: "",
|
|
23
|
+
slop: 0
|
|
24
|
+
},
|
|
25
|
+
icon: {
|
|
26
|
+
box: `${CONTROL} size-11 rounded-lg`,
|
|
27
|
+
label: "",
|
|
28
|
+
slop: 0
|
|
29
|
+
},
|
|
30
|
+
"icon-lg": {
|
|
31
|
+
box: `${CONTROL} size-12 rounded-lg`,
|
|
32
|
+
label: "",
|
|
33
|
+
slop: 0
|
|
34
|
+
},
|
|
35
|
+
"icon-sm": {
|
|
36
|
+
box: `${CONTROL} size-8 rounded-lg`,
|
|
37
|
+
label: "",
|
|
38
|
+
slop: 6
|
|
39
|
+
},
|
|
40
|
+
"icon-xl": {
|
|
41
|
+
box: `${CONTROL} size-11 rounded-full`,
|
|
42
|
+
label: "",
|
|
43
|
+
slop: 0
|
|
44
|
+
},
|
|
45
|
+
"icon-xs": {
|
|
46
|
+
box: `${CONTROL} size-7 rounded-md`,
|
|
47
|
+
label: "",
|
|
48
|
+
slop: 8
|
|
49
|
+
},
|
|
50
|
+
lg: {
|
|
51
|
+
box: `${CONTROL} min-h-12 gap-2 rounded-lg px-5`,
|
|
52
|
+
label: "",
|
|
53
|
+
slop: 0
|
|
54
|
+
},
|
|
55
|
+
row: {
|
|
56
|
+
box: "min-h-11 w-full items-start gap-3 py-3",
|
|
57
|
+
label: "",
|
|
58
|
+
slop: 0
|
|
59
|
+
},
|
|
60
|
+
sm: {
|
|
61
|
+
box: `${CONTROL} min-h-9 gap-1.5 rounded-lg px-3`,
|
|
62
|
+
label: "text-sm",
|
|
63
|
+
slop: 0
|
|
64
|
+
},
|
|
65
|
+
xl: {
|
|
66
|
+
box: `${CONTROL} min-h-14 gap-2 rounded-lg px-6`,
|
|
67
|
+
label: "text-lg",
|
|
68
|
+
slop: 0
|
|
69
|
+
},
|
|
70
|
+
xs: {
|
|
71
|
+
box: `${CONTROL} min-h-7 gap-1 rounded-md px-2`,
|
|
72
|
+
label: "text-sm",
|
|
73
|
+
slop: 8
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* A phone has no hover: a pressed button answers with the fill the web shows
|
|
78
|
+
* on hover, or dims when it already has one. `selected` is the pressed state
|
|
79
|
+
* held (a row open beside the list, a chosen option).
|
|
80
|
+
*/
|
|
81
|
+
const PRESS = {
|
|
82
|
+
default: {
|
|
83
|
+
pressed: "active:opacity-80",
|
|
84
|
+
selected: ""
|
|
85
|
+
},
|
|
86
|
+
destructive: {
|
|
87
|
+
pressed: "active:bg-destructive/18",
|
|
88
|
+
selected: ""
|
|
89
|
+
},
|
|
90
|
+
"destructive-outline": {
|
|
91
|
+
pressed: "active:bg-destructive/4",
|
|
92
|
+
selected: ""
|
|
93
|
+
},
|
|
94
|
+
"destructive-ghost": {
|
|
95
|
+
pressed: "active:bg-destructive/10",
|
|
96
|
+
selected: "bg-destructive/10"
|
|
97
|
+
},
|
|
98
|
+
ghost: {
|
|
99
|
+
pressed: "active:bg-accent",
|
|
100
|
+
selected: "bg-accent"
|
|
101
|
+
},
|
|
102
|
+
link: {
|
|
103
|
+
pressed: "active:opacity-60",
|
|
104
|
+
selected: ""
|
|
105
|
+
},
|
|
106
|
+
muted: {
|
|
107
|
+
pressed: "active:bg-accent",
|
|
108
|
+
selected: "bg-accent"
|
|
109
|
+
},
|
|
110
|
+
outline: {
|
|
111
|
+
pressed: "active:bg-accent",
|
|
112
|
+
selected: "bg-accent"
|
|
113
|
+
},
|
|
114
|
+
secondary: {
|
|
115
|
+
pressed: "active:bg-secondary/80",
|
|
116
|
+
selected: ""
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* The web's `Button` (./button.tsx) on a phone: the same variants and sizes,
|
|
121
|
+
* read from ./button.shared.ts, on a Pressable. A string child is set as the
|
|
122
|
+
* button's label in its variant's colour; any other child is laid out as
|
|
123
|
+
* given, which is how a row holds a glyph beside two lines of text.
|
|
124
|
+
*/
|
|
125
|
+
const Button = ({ accessibilityState, children, className, disabled = false, hitSlop, icon, loading = false, size, variant, ...props }) => {
|
|
126
|
+
const shape = SIZES[size ?? "default"];
|
|
127
|
+
const tokens = BUTTON_VARIANT_TOKENS[variant ?? "default"];
|
|
128
|
+
const press = PRESS[variant ?? "default"];
|
|
129
|
+
const inactive = disabled || loading;
|
|
130
|
+
const selected = accessibilityState?.selected === true;
|
|
131
|
+
const label = [
|
|
132
|
+
tokens.label,
|
|
133
|
+
variant === "link" ? "text-foreground underline" : "",
|
|
134
|
+
shape.label
|
|
135
|
+
].filter((name) => name !== "").join(" ");
|
|
136
|
+
return /* @__PURE__ */ jsxs(Pressable, {
|
|
137
|
+
accessibilityRole: "button",
|
|
138
|
+
accessibilityState: {
|
|
139
|
+
...accessibilityState,
|
|
140
|
+
busy: loading,
|
|
141
|
+
disabled: inactive
|
|
142
|
+
},
|
|
143
|
+
className: [
|
|
144
|
+
"flex-row",
|
|
145
|
+
tokens.surface,
|
|
146
|
+
shape.box,
|
|
147
|
+
inactive ? "opacity-64" : press.pressed,
|
|
148
|
+
selected ? press.selected : "",
|
|
149
|
+
className ?? ""
|
|
150
|
+
].filter((name) => name !== "").join(" "),
|
|
151
|
+
disabled: inactive,
|
|
152
|
+
hitSlop: hitSlop ?? (shape.slop === 0 ? void 0 : shape.slop),
|
|
153
|
+
...props,
|
|
154
|
+
children: [loading ? /* @__PURE__ */ jsx(Loader, {
|
|
155
|
+
size: "sm",
|
|
156
|
+
variant: "decorative"
|
|
157
|
+
}) : icon, typeof children === "string" || typeof children === "number" ? /* @__PURE__ */ jsx(Text$1, {
|
|
158
|
+
className: label,
|
|
159
|
+
variant: "button",
|
|
160
|
+
children
|
|
161
|
+
}) : children]
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
//#endregion
|
|
165
|
+
export { Button };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
//#region src/components/button.shared.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Each variant's colours: the box (`surface`: border and fill) and the
|
|
5
|
+
* label. Neither carries state; each renderer adds its own hover, press and
|
|
6
|
+
* focus treatment on top.
|
|
7
|
+
*/
|
|
8
|
+
export declare const BUTTON_VARIANT_TOKENS: {
|
|
9
|
+
readonly default: {
|
|
10
|
+
readonly surface: "border-primary bg-primary";
|
|
11
|
+
readonly label: "text-primary-foreground";
|
|
12
|
+
};
|
|
13
|
+
readonly destructive: {
|
|
14
|
+
readonly surface: "border-destructive/32 bg-destructive/10";
|
|
15
|
+
readonly label: "text-destructive-foreground";
|
|
16
|
+
};
|
|
17
|
+
readonly "destructive-outline": {
|
|
18
|
+
readonly surface: "border-input bg-popover";
|
|
19
|
+
readonly label: "text-destructive-foreground";
|
|
20
|
+
};
|
|
21
|
+
readonly "destructive-ghost": {
|
|
22
|
+
readonly surface: "border-transparent";
|
|
23
|
+
readonly label: "text-destructive";
|
|
24
|
+
};
|
|
25
|
+
readonly ghost: {
|
|
26
|
+
readonly surface: "border-transparent";
|
|
27
|
+
readonly label: "text-foreground";
|
|
28
|
+
};
|
|
29
|
+
readonly link: {
|
|
30
|
+
readonly surface: "border-transparent";
|
|
31
|
+
readonly label: "";
|
|
32
|
+
};
|
|
33
|
+
readonly muted: {
|
|
34
|
+
readonly surface: "border-transparent";
|
|
35
|
+
readonly label: "text-muted-foreground";
|
|
36
|
+
};
|
|
37
|
+
readonly outline: {
|
|
38
|
+
readonly surface: "border-input bg-popover";
|
|
39
|
+
readonly label: "text-foreground";
|
|
40
|
+
};
|
|
41
|
+
readonly secondary: {
|
|
42
|
+
readonly surface: "border-transparent bg-secondary";
|
|
43
|
+
readonly label: "text-secondary-foreground";
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export type ButtonVariant = keyof typeof BUTTON_VARIANT_TOKENS;
|
|
47
|
+
/** Every size a button comes in, on every platform. */
|
|
48
|
+
export declare const BUTTON_SIZES: readonly ["chip", "default", "icon", "icon-lg", "icon-sm", "icon-xl", "icon-xs", "lg", "row", "sm", "xl", "xs"];
|
|
49
|
+
export type ButtonSize = (typeof BUTTON_SIZES)[number];
|
|
50
|
+
/**
|
|
51
|
+
* The props every button renderer accepts, with one meaning. A renderer adds
|
|
52
|
+
* its platform's own props (a web button's `type` and `render`, a Pressable's
|
|
53
|
+
* `onPress` and `hitSlop`) on top.
|
|
54
|
+
*/
|
|
55
|
+
export type ButtonContract = {
|
|
56
|
+
variant?: ButtonVariant | null | undefined;
|
|
57
|
+
size?: ButtonSize | null | undefined;
|
|
58
|
+
/** Busy: a spinner takes the icon's place and the button cannot act. */
|
|
59
|
+
loading?: boolean;
|
|
60
|
+
disabled?: boolean;
|
|
61
|
+
/** A glyph before the label. */
|
|
62
|
+
icon?: ReactNode;
|
|
63
|
+
children?: ReactNode;
|
|
64
|
+
};
|
|
65
|
+
//#endregion
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//#region src/components/button.shared.ts
|
|
2
|
+
/**
|
|
3
|
+
* Each variant's colours: the box (`surface`: border and fill) and the
|
|
4
|
+
* label. Neither carries state; each renderer adds its own hover, press and
|
|
5
|
+
* focus treatment on top.
|
|
6
|
+
*/
|
|
7
|
+
const BUTTON_VARIANT_TOKENS = {
|
|
8
|
+
default: {
|
|
9
|
+
surface: "border-primary bg-primary",
|
|
10
|
+
label: "text-primary-foreground"
|
|
11
|
+
},
|
|
12
|
+
destructive: {
|
|
13
|
+
surface: "border-destructive/32 bg-destructive/10",
|
|
14
|
+
label: "text-destructive-foreground"
|
|
15
|
+
},
|
|
16
|
+
"destructive-outline": {
|
|
17
|
+
surface: "border-input bg-popover",
|
|
18
|
+
label: "text-destructive-foreground"
|
|
19
|
+
},
|
|
20
|
+
"destructive-ghost": {
|
|
21
|
+
surface: "border-transparent",
|
|
22
|
+
label: "text-destructive"
|
|
23
|
+
},
|
|
24
|
+
ghost: {
|
|
25
|
+
surface: "border-transparent",
|
|
26
|
+
label: "text-foreground"
|
|
27
|
+
},
|
|
28
|
+
link: {
|
|
29
|
+
surface: "border-transparent",
|
|
30
|
+
label: ""
|
|
31
|
+
},
|
|
32
|
+
muted: {
|
|
33
|
+
surface: "border-transparent",
|
|
34
|
+
label: "text-muted-foreground"
|
|
35
|
+
},
|
|
36
|
+
outline: {
|
|
37
|
+
surface: "border-input bg-popover",
|
|
38
|
+
label: "text-foreground"
|
|
39
|
+
},
|
|
40
|
+
secondary: {
|
|
41
|
+
surface: "border-transparent bg-secondary",
|
|
42
|
+
label: "text-secondary-foreground"
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/** Every size a button comes in, on every platform. */
|
|
46
|
+
const BUTTON_SIZES = [
|
|
47
|
+
"chip",
|
|
48
|
+
"default",
|
|
49
|
+
"icon",
|
|
50
|
+
"icon-lg",
|
|
51
|
+
"icon-sm",
|
|
52
|
+
"icon-xl",
|
|
53
|
+
"icon-xs",
|
|
54
|
+
"lg",
|
|
55
|
+
"row",
|
|
56
|
+
"sm",
|
|
57
|
+
"xl",
|
|
58
|
+
"xs"
|
|
59
|
+
];
|
|
60
|
+
//#endregion
|
|
61
|
+
export { BUTTON_SIZES, BUTTON_VARIANT_TOKENS };
|
|
@@ -6,7 +6,10 @@ import { OverlayLayer } from "../lib/overlay-layer.js";
|
|
|
6
6
|
declare const DialogCreateHandle: typeof Dialog$1.createHandle;
|
|
7
7
|
type DialogLabels = {
|
|
8
8
|
close: string;
|
|
9
|
+
/** The hint after Escape on unsaved changes: press it again to discard. */
|
|
9
10
|
unsavedChanges: string;
|
|
11
|
+
/** The hint after a click outside or on a close button: close again. */
|
|
12
|
+
unsavedChangesClose: string;
|
|
10
13
|
};
|
|
11
14
|
declare const DialogProvider: ({ labels, children }: React$1.PropsWithChildren<{
|
|
12
15
|
labels: DialogLabels;
|
|
@@ -38,7 +41,22 @@ declare const DIALOG_MOBILE_LAYOUT: {
|
|
|
38
41
|
readonly fullscreen: "fullscreen";
|
|
39
42
|
};
|
|
40
43
|
type DialogMobileLayout = (typeof DIALOG_MOBILE_LAYOUT)[keyof typeof DIALOG_MOBILE_LAYOUT];
|
|
41
|
-
|
|
44
|
+
/**
|
|
45
|
+
* How wide the popup may grow. `sm` is a confirmation or a single field,
|
|
46
|
+
* `default` a short form, `lg` a form with two columns or a table, `xl` a
|
|
47
|
+
* review or a document picker. Unprefixed like the width it replaces, so a
|
|
48
|
+
* caller's own `max-w-*` still wins; below `sm` the bottom-sheet and
|
|
49
|
+
* fullscreen layouts lift the cap and a centred popup keeps it.
|
|
50
|
+
*/
|
|
51
|
+
type DialogSize = "sm" | "default" | "lg" | "xl";
|
|
52
|
+
declare const DIALOG_SIZE_CLASS_NAMES: {
|
|
53
|
+
readonly sm: "max-w-sm";
|
|
54
|
+
readonly default: "max-w-lg";
|
|
55
|
+
readonly lg: "max-w-2xl";
|
|
56
|
+
readonly xl: "max-w-4xl";
|
|
57
|
+
};
|
|
58
|
+
declare const DialogPopup: ({ backdropClassName, className, children, showCloseButton, mobileLayout, layer, size, viewportClassName, ...props }: Dialog$1.Popup.Props & {
|
|
59
|
+
size?: DialogSize | undefined;
|
|
42
60
|
backdropClassName?: string;
|
|
43
61
|
showCloseButton?: boolean;
|
|
44
62
|
mobileLayout?: DialogMobileLayout;
|
|
@@ -55,4 +73,4 @@ declare const DialogPanel: ({ className, scrollFade, ...props }: React$1.Compone
|
|
|
55
73
|
scrollFade?: boolean;
|
|
56
74
|
}) => React$1.JSX.Element;
|
|
57
75
|
//#endregion
|
|
58
|
-
export { DIALOG_MOBILE_LAYOUT, Dialog, DialogBackdrop, DialogBackdrop as DialogOverlay, DialogClose, DialogPopup as DialogContent, DialogPopup, DialogCreateHandle, DialogDescription, DialogFooter, DialogFormState, DialogHeader, type DialogMobileLayout, DialogPanel, DialogPortal, DialogProvider, DialogTitle, DialogTrigger, DialogViewport };
|
|
76
|
+
export { DIALOG_MOBILE_LAYOUT, DIALOG_SIZE_CLASS_NAMES, Dialog, DialogBackdrop, DialogBackdrop as DialogOverlay, DialogClose, DialogPopup as DialogContent, DialogPopup, DialogCreateHandle, DialogDescription, DialogFooter, DialogFormState, DialogHeader, type DialogLabels, type DialogMobileLayout, DialogPanel, DialogPortal, DialogProvider, type DialogSize, DialogTitle, DialogTrigger, DialogViewport };
|