@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.
Files changed (71) hide show
  1. package/README.md +3 -0
  2. package/THIRD_PARTY_NOTICES +24 -0
  3. package/dist/components/badge.d.ts +28 -0
  4. package/dist/components/badge.js +64 -0
  5. package/dist/components/button.d.ts +2 -6
  6. package/dist/components/button.js +33 -25
  7. package/dist/components/button.native.d.ts +15 -0
  8. package/dist/components/button.native.js +165 -0
  9. package/dist/components/button.shared.d.ts +65 -0
  10. package/dist/components/button.shared.js +61 -0
  11. package/dist/components/dialog.d.ts +20 -2
  12. package/dist/components/dialog.js +31 -9
  13. package/dist/components/horizontal-scroller.js +31 -34
  14. package/dist/components/input.native.d.ts +23 -0
  15. package/dist/components/input.native.js +66 -0
  16. package/dist/components/input.shared.d.ts +19 -0
  17. package/dist/components/input.shared.js +0 -0
  18. package/dist/components/loader.d.ts +1 -6
  19. package/dist/components/loader.native.d.ts +28 -0
  20. package/dist/components/loader.native.js +23 -0
  21. package/dist/components/loader.shared.d.ts +17 -0
  22. package/dist/components/loader.shared.js +9 -0
  23. package/dist/components/matter-icon.d.ts +2 -8
  24. package/dist/components/matter-icon.js +10 -30
  25. package/dist/components/matter-icon.native.d.ts +25 -0
  26. package/dist/components/matter-icon.native.js +78 -0
  27. package/dist/components/matter-icon.shared.d.ts +21 -0
  28. package/dist/components/matter-icon.shared.js +18 -0
  29. package/dist/components/separator.native.d.ts +14 -0
  30. package/dist/components/separator.native.js +16 -0
  31. package/dist/components/separator.shared.d.ts +6 -0
  32. package/dist/components/separator.shared.js +0 -0
  33. package/dist/components/sheet.native.d.ts +27 -0
  34. package/dist/components/sheet.native.js +69 -0
  35. package/dist/components/sheet.shared.d.ts +9 -0
  36. package/dist/components/sheet.shared.js +0 -0
  37. package/dist/components/sidebar.js +1 -1
  38. package/dist/components/stella-wordmark.js +5 -13
  39. package/dist/components/stella-wordmark.native.d.ts +9 -0
  40. package/dist/components/stella-wordmark.native.js +27 -0
  41. package/dist/components/stella-wordmark.shared.d.ts +6 -0
  42. package/dist/components/stella-wordmark.shared.js +16 -0
  43. package/dist/components/switch.native.d.ts +13 -0
  44. package/dist/components/switch.native.js +33 -0
  45. package/dist/components/switch.shared.d.ts +9 -0
  46. package/dist/components/switch.shared.js +0 -0
  47. package/dist/components/table.d.ts +86 -8
  48. package/dist/components/table.js +46 -20
  49. package/dist/components/text.d.ts +11 -0
  50. package/dist/components/text.js +25 -0
  51. package/dist/components/text.native.d.ts +15 -0
  52. package/dist/components/text.native.js +46 -0
  53. package/dist/components/text.shared.d.ts +78 -0
  54. package/dist/components/text.shared.js +71 -0
  55. package/dist/components/textarea.native.d.ts +10 -0
  56. package/dist/components/textarea.native.js +28 -0
  57. package/dist/components/textarea.shared.d.ts +4 -0
  58. package/dist/components/textarea.shared.js +0 -0
  59. package/dist/components/tooltip.d.ts +15 -2
  60. package/dist/components/tooltip.js +18 -2
  61. package/dist/components/truncated-text.d.ts +33 -0
  62. package/dist/components/truncated-text.js +111 -0
  63. package/dist/index.d.ts +6 -5
  64. package/dist/index.js +5 -3
  65. package/dist/inspector/dock.js +1 -1
  66. package/dist/lib/scroll-edge-fade.d.ts +23 -0
  67. package/dist/lib/scroll-edge-fade.js +62 -0
  68. package/dist/native-env.d.ts +1 -0
  69. package/package.json +112 -7
  70. package/dist/calendar/index.d.ts +0 -4
  71. 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
- 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
- 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
- declare const DialogPopup: ({ backdropClassName, className, children, showCloseButton, mobileLayout, layer, viewportClassName, ...props }: Dialog$1.Popup.Props & {
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 };