@estiva-app/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +124 -0
  3. package/dist/Avatar.d.ts +16 -0
  4. package/dist/Avatar.d.ts.map +1 -0
  5. package/dist/Button.d.ts +28 -0
  6. package/dist/Button.d.ts.map +1 -0
  7. package/dist/Chip.d.ts +21 -0
  8. package/dist/Chip.d.ts.map +1 -0
  9. package/dist/DialogShell.d.ts +19 -0
  10. package/dist/DialogShell.d.ts.map +1 -0
  11. package/dist/Divider.d.ts +12 -0
  12. package/dist/Divider.d.ts.map +1 -0
  13. package/dist/EmptyState.d.ts +14 -0
  14. package/dist/EmptyState.d.ts.map +1 -0
  15. package/dist/Field.d.ts +13 -0
  16. package/dist/Field.d.ts.map +1 -0
  17. package/dist/IconButton.d.ts +17 -0
  18. package/dist/IconButton.d.ts.map +1 -0
  19. package/dist/Select.d.ts +30 -0
  20. package/dist/Select.d.ts.map +1 -0
  21. package/dist/Skeleton.d.ts +24 -0
  22. package/dist/Skeleton.d.ts.map +1 -0
  23. package/dist/TextInput.d.ts +11 -0
  24. package/dist/TextInput.d.ts.map +1 -0
  25. package/dist/Textarea.d.ts +8 -0
  26. package/dist/Textarea.d.ts.map +1 -0
  27. package/dist/Tooltip.d.ts +22 -0
  28. package/dist/Tooltip.d.ts.map +1 -0
  29. package/dist/cn.d.ts +10 -0
  30. package/dist/cn.d.ts.map +1 -0
  31. package/dist/index.d.ts +26 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +486 -0
  34. package/dist/index.js.map +7 -0
  35. package/package.json +74 -0
  36. package/src/Avatar.stories.tsx +55 -0
  37. package/src/Avatar.tsx +81 -0
  38. package/src/Button.stories.tsx +53 -0
  39. package/src/Button.tsx +67 -0
  40. package/src/Chip.stories.tsx +37 -0
  41. package/src/Chip.tsx +43 -0
  42. package/src/DialogShell.stories.tsx +97 -0
  43. package/src/DialogShell.tsx +65 -0
  44. package/src/Divider.stories.tsx +44 -0
  45. package/src/Divider.tsx +22 -0
  46. package/src/EmptyState.stories.tsx +18 -0
  47. package/src/EmptyState.tsx +24 -0
  48. package/src/Field.stories.tsx +22 -0
  49. package/src/Field.tsx +24 -0
  50. package/src/IconButton.stories.tsx +44 -0
  51. package/src/IconButton.tsx +60 -0
  52. package/src/Select.stories.tsx +107 -0
  53. package/src/Select.tsx +195 -0
  54. package/src/Skeleton.stories.tsx +33 -0
  55. package/src/Skeleton.tsx +38 -0
  56. package/src/TextInput.stories.tsx +18 -0
  57. package/src/TextInput.tsx +29 -0
  58. package/src/Textarea.stories.tsx +17 -0
  59. package/src/Textarea.tsx +25 -0
  60. package/src/Tooltip.stories.tsx +47 -0
  61. package/src/Tooltip.tsx +70 -0
  62. package/src/cn.ts +13 -0
  63. package/src/index.ts +25 -0
  64. package/tailwind-preset.js +162 -0
  65. package/tokens.css +225 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Estiva
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # @estiva-app/ui
2
+
3
+ Estiva's design tokens, and a small set of primitives built on them, for
4
+ every Estiva app — Peek, Ship, and whatever comes next.
5
+
6
+ **Tokens are the contract. Components are a convenience.**
7
+
8
+ ## What is in it
9
+
10
+ - `tailwind-preset.js` — the token *names*, as a Tailwind preset: colours,
11
+ type ramp, radii, shadows, the skeleton animation. Peek's, verbatim.
12
+ - `tokens.css` — the token *values*, one block per theme: `signal` (what
13
+ Peek renders), `ship` (what Ship renders), and the `light` / `dark` bases.
14
+ Same names in every block.
15
+ - Primitives — arriving one at a time (see Storybook). Each has stories that
16
+ need no app data; if a story needs a fixture, the component is not a
17
+ primitive and does not belong here.
18
+
19
+ ## Using it
20
+
21
+ ```js
22
+ // tailwind.config.js
23
+ import estiva from '@estiva-app/ui/tailwind-preset'
24
+ export default {
25
+ presets: [estiva],
26
+ content: ['./index.html', './src/**/*.{ts,tsx}'],
27
+ theme: { extend: { /* your app's own tokens, if any */ } },
28
+ }
29
+ ```
30
+
31
+ ```css
32
+ /* your entry CSS, before anything that uses a token */
33
+ @import '@estiva-app/ui/tokens.css';
34
+ ```
35
+
36
+ ```html
37
+ <html data-theme="ship"> <!-- or "signal" / "dark"; nothing = light. Peek uses the classes: <html class="dark signal">. -->
38
+ ```
39
+
40
+ Install it:
41
+
42
+ ```bash
43
+ npm install @estiva-app/ui
44
+ ```
45
+
46
+ **Spread `estivaContent` into your `content`.** Tailwind does not merge
47
+ `content` from a preset — measured, see the comment in `tailwind-preset.js` —
48
+ so without this every class used only by these components is purged, and the
49
+ result is a build that succeeds with components rendering at the wrong size:
50
+
51
+ ```js
52
+ import estiva, { estivaContent } from '@estiva-app/ui/tailwind-preset'
53
+ export default {
54
+ presets: [estiva],
55
+ content: [...estivaContent, './index.html', './src/**/*.{ts,tsx}'],
56
+ }
57
+ ```
58
+
59
+ ## The rules
60
+
61
+ ### Forking is allowed
62
+
63
+ > Forking a component into your app is allowed and is not a failure. If you
64
+ > fork one, you owe nothing back. Tokens are the only thing you must not
65
+ > diverge from.
66
+
67
+ If a primitive nearly fits but not quite, copy it into your app and change
68
+ it. Say so on the package's ticket — a fork is information about the seam —
69
+ but do not bend your view around a component, and do not wait for
70
+ permission. A library people are afraid to deviate from becomes a tax.
71
+
72
+ ### Tokens, never raw hex
73
+
74
+ If the colour you need has no token, the token is missing: add it here, in
75
+ every theme, rather than reaching for hex in an app.
76
+
77
+ ### The tailwind-merge pitfall
78
+
79
+ A custom text-size class (`text-btn-small`, `text-caption`, `text-h2`…) is
80
+ **silently dropped** by `tailwind-merge` when a `text-{colour}` class follows
81
+ it in the same merged list. In shared components, spell sizes as arbitrary
82
+ values — `text-[12px] leading-[12px]` — and keep the token classes for plain
83
+ `className` strings that never go through `cn()`. The bug appears in the
84
+ consuming app and its cause is in the package, which is why this rule lives
85
+ here.
86
+
87
+ ### Menus and popovers — five rules
88
+
89
+ Stay open on hover · close on mouse leave (for hover-opened menus) · guard
90
+ the parent's hover state · close on outside click · close on Escape. Each is
91
+ forgettable on its own, and a popover that fails one feels broken without
92
+ anyone being able to say why. A click-opened menu (Select, a `…` menu) owes
93
+ the last two; a hover-opened one owes all five.
94
+
95
+ ### Only offer actions that can succeed
96
+
97
+ An action appears only where it works. A control that cannot succeed right
98
+ now is disabled *with its reason* (a tooltip), and a control someone may
99
+ never use is absent, not disabled. Primitives expose what that needs: a
100
+ `disabled` that can explain itself, never a button that fails.
101
+
102
+ ## Themes
103
+
104
+ | Theme | Selected by | Whose |
105
+ |----------|------------------------------------------------|--------------------------------|
106
+ | `signal` | `data-theme="signal"` or `.dark.signal` | Peek — what it renders |
107
+ | `ship` | `data-theme="ship"` | Ship — what it renders |
108
+ | `dark` | `data-theme="dark"` or `.dark` | Peek's base under Signal |
109
+ | `light` | nothing | the `:root` base; Estiva ID |
110
+
111
+ Peek's Signal-only extras (washes, glows, the summary highlight, the huddle
112
+ register, the wordmark, the mono stack) are product, not the contract, and
113
+ stay in Peek's own `.signal` block.
114
+
115
+ ## Working on it
116
+
117
+ ```
118
+ npm install
119
+ npm test # every theme defines every token the preset names, and no other
120
+ npm run storybook # http://localhost:6008 — Design Tokens, and each primitive, in signal and ship
121
+ ```
122
+
123
+ Extracting a primitive from Peek must not change how Peek renders:
124
+ screenshot-compare before and after, in Peek's own Storybook.
@@ -0,0 +1,16 @@
1
+ export declare const hueFor: (key: string) => string;
2
+ /** Peek's rule: the first letter of the first two words. */
3
+ export declare const initialsFor: (name: string) => string;
4
+ export interface AvatarProps {
5
+ /** The picture URL, resolved by the caller. A picture that fails to load falls back to the initials. */
6
+ src?: string;
7
+ /** The person's name — where the initials and the colour come from. */
8
+ name?: string;
9
+ /** Alt text for the picture; the name when absent. Also the initials' source when there is no name. */
10
+ alt?: string;
11
+ /** Pixels. Peek's scale: 16 · 24 · 32 · 36 (default). */
12
+ size?: number;
13
+ className?: string;
14
+ }
15
+ export declare function Avatar({ src, name, alt, size, className }: AvatarProps): import("react").JSX.Element;
16
+ //# sourceMappingURL=Avatar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Avatar.d.ts","sourceRoot":"","sources":["../src/Avatar.tsx"],"names":[],"mappings":"AA2BA,eAAO,MAAM,MAAM,GAAI,KAAK,MAAM,WAIjC,CAAA;AAED,4DAA4D;AAC5D,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,WAMtB,CAAA;AAElB,MAAM,WAAW,WAAW;IAC1B,wGAAwG;IACxG,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,uGAAuG;IACvG,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,MAAM,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,GAAQ,EAAE,IAAS,EAAE,SAAS,EAAE,EAAE,WAAW,+BA0BhF"}
@@ -0,0 +1,28 @@
1
+ import type { ButtonHTMLAttributes, ReactNode } from 'react';
2
+ /**
3
+ * Peek's Button (2026-08-28), verbatim, plus what Ship added and Peek should
4
+ * adopt: a `destructive` variant — the muted button in the error colour, for
5
+ * "Delete project" and its kind; a destructive action is a button like any
6
+ * other, not a dotted link — and `type="button"` by default, so a button
7
+ * inside a form submits it only when asked to.
8
+ *
9
+ * Three variants and two sizes: 32px default / 24px small, 6px radius, 500
10
+ * weight. Sizes are spelled as arbitrary values for the reason the README
11
+ * records: tailwind-merge drops a custom `text-{size}` that is followed by a
12
+ * `text-{colour}`.
13
+ *
14
+ * The primary reads in `text-inverse` on the accent — a theme decides what
15
+ * that is (dark on Peek's light accents, light on Ship's dark one). Under
16
+ * Signal it is also semibold, as Peek has it.
17
+ */
18
+ export type ButtonVariant = 'primary' | 'outlined' | 'muted' | 'destructive';
19
+ export type ButtonSize = 'default' | 'small';
20
+ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
21
+ variant?: ButtonVariant;
22
+ size?: ButtonSize;
23
+ /** 16px, stroke 1.5 on the default size; 14px on small. */
24
+ leadingIcon?: ReactNode;
25
+ children: ReactNode;
26
+ }
27
+ export declare function Button({ variant, size, leadingIcon, className, children, disabled, type, ...props }: ButtonProps): import("react").JSX.Element;
28
+ //# sourceMappingURL=Button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Button.d.ts","sourceRoot":"","sources":["../src/Button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAG5D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,aAAa,CAAA;AAC5E,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,OAAO,CAAA;AAE5C,MAAM,WAAW,WAAY,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;IAC1E,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,MAAM,CAAC,EACrB,OAAiB,EACjB,IAAgB,EAChB,WAAW,EACX,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,IAAe,EACf,GAAG,KAAK,EACT,EAAE,WAAW,+BA2Bb"}
package/dist/Chip.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * Peek's Chip (2026-08-28), verbatim: a 20px pill in one of six colour
4
+ * types, with room for a 12px icon either side. Under Signal the label is
5
+ * mono and the coloured types carry a hairline, as Peek has it. The icon
6
+ * slots centre their icon (Ship's addition — an icon beside text otherwise
7
+ * rides on the baseline).
8
+ *
9
+ * The label is the `chip` type token, 11px / 500; it is a plain class here,
10
+ * never merged, so tailwind-merge cannot drop it.
11
+ */
12
+ export type ChipType = 'neutral' | 'brand' | 'info' | 'warning' | 'success' | 'error';
13
+ export interface ChipProps {
14
+ type?: ChipType;
15
+ label?: string;
16
+ leadingIcon?: ReactNode;
17
+ trailingIcon?: ReactNode;
18
+ className?: string;
19
+ }
20
+ export declare function Chip({ type, label, leadingIcon, trailingIcon, className }: ChipProps): import("react").JSX.Element;
21
+ //# sourceMappingURL=Chip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Chip.d.ts","sourceRoot":"","sources":["../src/Chip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC;;;;;;;;;GASG;AACH,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;AAErF,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,YAAY,CAAC,EAAE,SAAS,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAWD,wBAAgB,IAAI,CAAC,EAAE,IAAgB,EAAE,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,SAAS,+BAUhG"}
@@ -0,0 +1,19 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * Peek's DialogShell (2026-08-28), verbatim: the portal, the backdrop, the
4
+ * 502px card and its chrome — a 48px header with the title and a close
5
+ * button, a body, a 48px footer for the buttons. A dialog is just what goes
6
+ * in the three slots. Plus what Ship added: Escape closes it, and the card
7
+ * says what it is to assistive tech.
8
+ */
9
+ export interface DialogShellProps {
10
+ title: string;
11
+ onClose: () => void;
12
+ footer: ReactNode;
13
+ children: ReactNode;
14
+ /** Extra classes on the body (e.g. `flex flex-col gap-6`, or a max height with `overflow-y-auto`). */
15
+ bodyClassName?: string;
16
+ width?: number;
17
+ }
18
+ export declare function DialogShell({ title, onClose, footer, children, bodyClassName, width }: DialogShellProps): import("react").ReactPortal;
19
+ //# sourceMappingURL=DialogShell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DialogShell.d.ts","sourceRoot":"","sources":["../src/DialogShell.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAMjD;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,MAAM,EAAE,SAAS,CAAA;IACjB,QAAQ,EAAE,SAAS,CAAA;IACnB,sGAAsG;IACtG,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,KAAW,EAAE,EAAE,gBAAgB,+BAyC7G"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Peek's Divider (2026-08-28): a hairline in `border-subtle`, inset 12px each
3
+ * side. Plus what Ship added: `orientation="vertical"` — the same hairline
4
+ * standing up, stretching to its row's height, no inset — and the separator
5
+ * role for assistive tech.
6
+ */
7
+ export interface DividerProps {
8
+ orientation?: 'horizontal' | 'vertical';
9
+ className?: string;
10
+ }
11
+ export declare function Divider({ orientation, className }: DividerProps): import("react").JSX.Element;
12
+ //# sourceMappingURL=Divider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Divider.d.ts","sourceRoot":"","sources":["../src/Divider.tsx"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAA;IACvC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,OAAO,CAAC,EAAE,WAA0B,EAAE,SAAS,EAAE,EAAE,YAAY,+BAQ9E"}
@@ -0,0 +1,14 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * Peek's EmptyState (2026-08-28), verbatim: a 16px icon over a centred line
4
+ * of secondary text. The message is the caller's — a shared component has no
5
+ * words of its own for what is missing.
6
+ */
7
+ export interface EmptyStateProps {
8
+ /** 16px, stroke 1.5. A speech bubble when absent. */
9
+ icon?: ReactNode;
10
+ message: string;
11
+ className?: string;
12
+ }
13
+ export declare function EmptyState({ icon, message, className }: EmptyStateProps): import("react").JSX.Element;
14
+ //# sourceMappingURL=EmptyState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmptyState.d.ts","sourceRoot":"","sources":["../src/EmptyState.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,qDAAqD;IACrD,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,eAAe,+BAOvE"}
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * Peek's Field (2026-08-28), verbatim: a label over a control, 8px apart,
4
+ * with a red asterisk when required. The label is the `input-label` type
5
+ * token as a plain class, never merged.
6
+ */
7
+ export interface FieldProps {
8
+ label: string;
9
+ required?: boolean;
10
+ children: ReactNode;
11
+ }
12
+ export declare function Field({ label, required, children }: FieldProps): import("react").JSX.Element;
13
+ //# sourceMappingURL=Field.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Field.d.ts","sourceRoot":"","sources":["../src/Field.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,KAAK,CAAC,EAAE,KAAK,EAAE,QAAgB,EAAE,QAAQ,EAAE,EAAE,UAAU,+BAUtE"}
@@ -0,0 +1,17 @@
1
+ import type { ButtonHTMLAttributes, ReactNode } from 'react';
2
+ /**
3
+ * Peek's IconButton (2026-08-28), verbatim: a square 4px-padded button
4
+ * around a 16px icon, 8px radius, three variants, an optional tooltip that
5
+ * portals above or below. Plus `type="button"` by default (Ship's addition),
6
+ * so it never submits a form by accident.
7
+ */
8
+ export type IconButtonVariant = 'muted' | 'outlined' | 'primary';
9
+ export interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
10
+ variant?: IconButtonVariant;
11
+ tooltip?: string;
12
+ tooltipPlacement?: 'top' | 'bottom';
13
+ /** The icon: 16px, stroke 1.5. */
14
+ children: ReactNode;
15
+ }
16
+ export declare function IconButton({ variant, className, children, disabled, tooltip, tooltipPlacement, type, ...props }: IconButtonProps): import("react").JSX.Element;
17
+ //# sourceMappingURL=IconButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IconButton.d.ts","sourceRoot":"","sources":["../src/IconButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAI5D;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,CAAA;AAEhE,MAAM,WAAW,eAAgB,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;IAC9E,OAAO,CAAC,EAAE,iBAAiB,CAAA;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gBAAgB,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAA;IACnC,kCAAkC;IAClC,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,UAAU,CAAC,EACzB,OAAiB,EACjB,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,gBAAgB,EAChB,IAAe,EACf,GAAG,KAAK,EACT,EAAE,eAAe,+BA8BjB"}
@@ -0,0 +1,30 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * Peek's Select (2026-08-28), verbatim, plus what Ship added: an option may
4
+ * carry a `leading` node — an avatar beside a person's name — shown in the
5
+ * trigger and in the list.
6
+ *
7
+ * A button that opens a portalled listbox under itself: arrows move, Home
8
+ * and End jump, Enter and Space pick, Escape closes and returns focus to the
9
+ * trigger, Tab closes, a click outside closes, a scroll or resize closes
10
+ * (the list is fixed to where the trigger was). Two sizes; `disabled`
11
+ * explains nothing by itself — wrap it in a tooltip that does.
12
+ */
13
+ export interface SelectOption {
14
+ value: string;
15
+ label: string;
16
+ /** Drawn before the label, 16px — an Avatar, an icon. */
17
+ leading?: ReactNode;
18
+ }
19
+ export interface SelectProps {
20
+ value: string;
21
+ onChange: (value: string) => void;
22
+ options: SelectOption[];
23
+ size?: 'default' | 'small';
24
+ ariaLabel?: string;
25
+ placeholder?: string;
26
+ disabled?: boolean;
27
+ className?: string;
28
+ }
29
+ export declare function Select({ value, onChange, options, size, ariaLabel, placeholder, disabled, className }: SelectProps): import("react").JSX.Element;
30
+ //# sourceMappingURL=Select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../src/Select.tsx"],"names":[],"mappings":"AACA,OAAO,EAAgF,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAIpH;;;;;;;;;;GAUG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,yDAAyD;IACzD,OAAO,CAAC,EAAE,SAAS,CAAA;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,MAAM,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAgB,EAAE,SAAS,EAAE,WAAuB,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,WAAW,+BAgK1I"}
@@ -0,0 +1,24 @@
1
+ import type { CSSProperties } from 'react';
2
+ /**
3
+ * Peek's Skeleton (2026-08-28), the generic parts only: the bar, a row
4
+ * shaped like a list row, and a list of them. Peek's placeholders shaped
5
+ * like a conversation card or a huddle card know what those are and stay
6
+ * in Peek, built from the bar.
7
+ *
8
+ * A list reveals after 150ms (`animate-skeleton-in`, in the preset) so a
9
+ * fast load never flashes a skeleton — Peek's rule.
10
+ */
11
+ export declare function SkeletonBar({ className, style }: {
12
+ className?: string;
13
+ style?: CSSProperties;
14
+ }): import("react").JSX.Element;
15
+ /** A 32px row: a 16px square and a bar, like a row with a face and a name. */
16
+ export declare function SkeletonRow({ barWidth }: {
17
+ barWidth?: number;
18
+ }): import("react").JSX.Element;
19
+ /** Rows of varied widths, so the placeholder does not read as a pattern. */
20
+ export declare function SkeletonList({ rows, className }: {
21
+ rows?: number;
22
+ className?: string;
23
+ }): import("react").JSX.Element;
24
+ //# sourceMappingURL=Skeleton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Skeleton.d.ts","sourceRoot":"","sources":["../src/Skeleton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAG1C;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,aAAa,CAAA;CAAE,+BAE9F;AAID,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,EAAE,QAAc,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,+BAOpE;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,EAAE,IAAQ,EAAE,SAAS,EAAE,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAQ1F"}
@@ -0,0 +1,11 @@
1
+ import { type InputHTMLAttributes } from 'react';
2
+ /**
3
+ * Peek's TextInput (2026-08-28): the inset field with a 8px radius, 14px
4
+ * text, the focus border in every theme (Katerina, 2026-08-28: Ship's inputs
5
+ * focus like Signal's) — and Signal's glow on top.
6
+ * Plus a disabled look (Ship's addition): the disabled surface and text,
7
+ * no pointer.
8
+ */
9
+ export type TextInputProps = InputHTMLAttributes<HTMLInputElement>;
10
+ export declare const TextInput: import("react").ForwardRefExoticComponent<TextInputProps & import("react").RefAttributes<HTMLInputElement>>;
11
+ //# sourceMappingURL=TextInput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TextInput.d.ts","sourceRoot":"","sources":["../src/TextInput.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,mBAAmB,EAAE,MAAM,OAAO,CAAA;AAG5D;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,mBAAmB,CAAC,gBAAgB,CAAC,CAAA;AAElE,eAAO,MAAM,SAAS,6GAgBpB,CAAA"}
@@ -0,0 +1,8 @@
1
+ import { type TextareaHTMLAttributes } from 'react';
2
+ /**
3
+ * Peek's Textarea (2026-08-28), verbatim: TextInput's look on a textarea
4
+ * that does not resize. Plus a disabled look (Ship's addition).
5
+ */
6
+ export type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement>;
7
+ export declare const Textarea: import("react").ForwardRefExoticComponent<TextareaProps & import("react").RefAttributes<HTMLTextAreaElement>>;
8
+ //# sourceMappingURL=Textarea.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Textarea.d.ts","sourceRoot":"","sources":["../src/Textarea.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,sBAAsB,EAAE,MAAM,OAAO,CAAA;AAG/D;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,sBAAsB,CAAC,mBAAmB,CAAC,CAAA;AAEvE,eAAO,MAAM,QAAQ,+GAenB,CAAA"}
@@ -0,0 +1,22 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * Peek's Tooltip and WithTooltip (2026-08-28), verbatim, in one file.
4
+ *
5
+ * `Tooltip` is the surface: a 30px elevated pill with a caption. `WithTooltip`
6
+ * wraps a trigger and portals the surface above or below it on hover, fixed
7
+ * to the viewport and kept 8px inside it. The wrapper is `inline-flex` and
8
+ * shrinks nothing — wrap a control, never a block (a form inside it
9
+ * collapses to its content width; Ship learnt that).
10
+ */
11
+ export interface TooltipProps {
12
+ label: string;
13
+ className?: string;
14
+ }
15
+ export declare function Tooltip({ label, className }: TooltipProps): import("react").JSX.Element;
16
+ export interface WithTooltipProps {
17
+ label: string;
18
+ placement?: 'top' | 'bottom';
19
+ children: ReactNode;
20
+ }
21
+ export declare function WithTooltip({ label, placement, children }: WithTooltipProps): import("react").JSX.Element;
22
+ //# sourceMappingURL=Tooltip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tooltip.d.ts","sourceRoot":"","sources":["../src/Tooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAsE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAI1G;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,OAAO,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,YAAY,+BAMzD;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAA;IAC5B,QAAQ,EAAE,SAAS,CAAA;CACpB;AAKD,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,SAAiB,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BAkCnF"}
package/dist/cn.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { type ClassValue } from 'clsx';
2
+ /**
3
+ * Peek's `cn`: clsx for the conditionals, tailwind-merge for the conflicts.
4
+ *
5
+ * Remember the pitfall the README records: a custom text-size class in a
6
+ * merged list is dropped when a `text-{colour}` follows it. Inside this
7
+ * package, sizes are arbitrary values (`text-[12px]`), never the token class.
8
+ */
9
+ export declare function cn(...inputs: ClassValue[]): string;
10
+ //# sourceMappingURL=cn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cn.d.ts","sourceRoot":"","sources":["../src/cn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAG5C;;;;;;GAMG;AACH,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @estiva-app/ui — the primitives.
3
+ *
4
+ * Shipped **built**: `dist/index.js` (esbuild, one bundle, React external) plus
5
+ * `.d.ts` from `tsc`. Not source — a package of `.ts` is compiled with the
6
+ * *consumer's* tsconfig, and Ship's `noUnusedLocals` alone is enough to fail a
7
+ * build over a library it does not own. See estiva-docs ADR 0002 §4a.
8
+ *
9
+ * `src/` still ships in the tarball, so stepping into a component lands on the
10
+ * TypeScript that produced it.
11
+ */
12
+ export { Avatar, hueFor, initialsFor, type AvatarProps } from './Avatar';
13
+ export { Button, type ButtonProps, type ButtonSize, type ButtonVariant } from './Button';
14
+ export { Chip, type ChipProps, type ChipType } from './Chip';
15
+ export { IconButton, type IconButtonProps, type IconButtonVariant } from './IconButton';
16
+ export { Tooltip, WithTooltip, type TooltipProps, type WithTooltipProps } from './Tooltip';
17
+ export { Field, type FieldProps } from './Field';
18
+ export { Select, type SelectOption, type SelectProps } from './Select';
19
+ export { TextInput, type TextInputProps } from './TextInput';
20
+ export { Textarea, type TextareaProps } from './Textarea';
21
+ export { DialogShell, type DialogShellProps } from './DialogShell';
22
+ export { Divider, type DividerProps } from './Divider';
23
+ export { EmptyState, type EmptyStateProps } from './EmptyState';
24
+ export { SkeletonBar, SkeletonList, SkeletonRow } from './Skeleton';
25
+ export { cn } from './cn';
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAA;AACxE,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAA;AACxF,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAC5D,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAA;AACvF,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAC1F,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,SAAS,CAAA;AAChD,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAA;AACtE,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5D,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAClE,OAAO,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAA;AACtD,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;AAC/D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACnE,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAA"}