@tradalab/lyra 2.1.0 → 2.3.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 +99 -99
- package/dist/blocks/index.d.ts +69 -1
- package/dist/blocks/index.js +158 -4
- package/dist/blocks/index.js.map +1 -1
- package/dist/{chunk-7GY2KYNW.js → chunk-2HDU26FC.js} +4 -101
- package/dist/chunk-2HDU26FC.js.map +1 -0
- package/dist/chunk-F2ET7EL2.js +104 -0
- package/dist/chunk-F2ET7EL2.js.map +1 -0
- package/dist/{chunk-F5HR244O.js → chunk-HN3UZUCS.js} +4 -5
- package/dist/chunk-HN3UZUCS.js.map +1 -0
- package/dist/chunk-VA6K3TP3.js +281 -0
- package/dist/chunk-VA6K3TP3.js.map +1 -0
- package/dist/{chunk-5M44N7SV.js → chunk-VDO6PQ5L.js} +4 -4
- package/dist/{chunk-5M44N7SV.js.map → chunk-VDO6PQ5L.js.map} +1 -1
- package/dist/{chunk-5Q4UTRYU.js → chunk-VGOGWZCF.js} +17 -3
- package/dist/chunk-VGOGWZCF.js.map +1 -0
- package/dist/{chunk-APYQOJAJ.js → chunk-XWGYXLIA.js} +3 -3
- package/dist/{chunk-APYQOJAJ.js.map → chunk-XWGYXLIA.js.map} +1 -1
- package/dist/data-table/index.js +1 -2
- package/dist/data-table/index.js.map +1 -1
- package/dist/people/index.js +5 -5
- package/dist/shell/index.js +4 -4
- package/dist/ui/index.d.ts +29 -2
- package/dist/ui/index.js +26 -266
- package/dist/ui/index.js.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-5Q4UTRYU.js.map +0 -1
- package/dist/chunk-7GY2KYNW.js.map +0 -1
- package/dist/chunk-F5HR244O.js.map +0 -1
- package/dist/chunk-PI6SANLG.js +0 -22
- package/dist/chunk-PI6SANLG.js.map +0 -1
- package/dist/chunk-QGUKTRBR.js +0 -25
- package/dist/chunk-QGUKTRBR.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
# @tradalab/lyra
|
|
2
|
-
|
|
3
|
-
> Tradalab's UI kit — shadcn (`radix-lyra` style) primitives, composed
|
|
4
|
-
> components, and an app shell for **React 19 / Next 16 / Tailwind v4**.
|
|
5
|
-
|
|
6
|
-
ESM-only. Extracted from Tradalab's foundation kit so every company app shares
|
|
7
|
-
the same component surface.
|
|
8
|
-
|
|
9
|
-
## Install
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
pnpm add @tradalab/lyra
|
|
13
|
-
# peers: react ^19, react-dom ^19 (next ^16 only if you use /shell)
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
The stylesheet pulls in Tailwind v4, so your app needs `@tailwindcss/postcss`.
|
|
17
|
-
|
|
18
|
-
```css
|
|
19
|
-
/* app/globals.css */
|
|
20
|
-
@import "@tradalab/lyra/styles.css";
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Subpaths
|
|
24
|
-
|
|
25
|
-
| Subpath | Exports | Boundary |
|
|
26
|
-
|---|---|---|
|
|
27
|
-
| `@tradalab/lyra/utils` | `cn` | server-safe |
|
|
28
|
-
| `@tradalab/lyra/hooks` | `useMobile` | client |
|
|
29
|
-
| `@tradalab/lyra/ui` | 60 shadcn primitives + `AccountMenu`, `Container` | client |
|
|
30
|
-
| `@tradalab/lyra/shell` | `AppShell`, `NavItem`, `NavGroup`, `Breadcrumbs`, `useActiveRoute` | client |
|
|
31
|
-
| `@tradalab/lyra/styles.css` | Tailwind v4 base + `radix-lyra` tokens (light/dark) | CSS |
|
|
32
|
-
|
|
33
|
-
Client subpaths carry a build-injected `"use client"`; import them inside a
|
|
34
|
-
client tree or pass via `children` — don't import them into a Server Component's
|
|
35
|
-
own module.
|
|
36
|
-
|
|
37
|
-
```tsx
|
|
38
|
-
import { Button, Card, CardHeader } from "@tradalab/lyra/ui"
|
|
39
|
-
import { AppShell, NavItem } from "@tradalab/lyra/shell"
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Toast
|
|
43
|
-
|
|
44
|
-
`2.0.0` replaced the sonner-based toast with shadcn's Base UI Toast. `sonner` is
|
|
45
|
-
gone — it is no longer a peer dependency, and apps should drop it.
|
|
46
|
-
|
|
47
|
-
`toast` is a **manager object, not a callable**:
|
|
48
|
-
|
|
49
|
-
```tsx
|
|
50
|
-
import { Toaster, toast } from "@tradalab/lyra/ui"
|
|
51
|
-
|
|
52
|
-
toast.add({ title: "Saved" })
|
|
53
|
-
toast.add({ title: "Upload failed", description: err.message, type: "error" })
|
|
54
|
-
const id = toast.add({ title: "Uploading…", type: "loading", timeout: 0 })
|
|
55
|
-
toast.close(id)
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
`type` is `"success" | "error" | "info" | "warning" | "loading"` — there is no
|
|
59
|
-
`toast.success("x")`. `timeout: 0` never auto-dismisses (sonner's `duration:
|
|
60
|
-
Infinity`). Position belongs to the viewport this package styles, so sonner's
|
|
61
|
-
`position` and `richColors` props have no equivalent. Mount `<Toaster/>` once —
|
|
62
|
-
apps on `@tradalab/nekit` already get it from `NekitProvider`.
|
|
63
|
-
|
|
64
|
-
## Develop
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
make install # pnpm install
|
|
68
|
-
make build # tsup + inject-use-client + verify-build-output
|
|
69
|
-
make verify # typecheck + lint + test
|
|
70
|
-
make ui-add <name> # scaffold a shadcn primitive, adapt, rebuild barrel
|
|
71
|
-
make ui-update <name> # overwrite a primitive with the latest upstream version
|
|
72
|
-
make ui-sync # re-adapt every primitive + rebuild barrel
|
|
73
|
-
make ui-diff # report which primitives changed upstream (exit 1 if any)
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
**Staying current with shadcn** — `make ui-diff` compares every primitive
|
|
77
|
-
against the live `radix-lyra` registry and exits non-zero when one has moved.
|
|
78
|
-
It normalises away everything the CLI would rewrite anyway (icon-library
|
|
79
|
-
placeholders, `cn-*` template classes, import aliases, prettier line-wrapping),
|
|
80
|
-
so what it prints is real change. Prefer it over `shadcn add --diff`, which
|
|
81
|
-
caps its output at five files per run.
|
|
82
|
-
|
|
83
|
-
A primitive lyra deliberately diverges from carries a `customised` note in
|
|
84
|
-
`primitives.lock.json`; `ui-diff` lists those separately instead of failing on
|
|
85
|
-
them forever. Document the divergence in a JSDoc block in the file too —
|
|
86
|
-
`make ui-update` will overwrite it, and that note is what tells the next person
|
|
87
|
-
to put it back.
|
|
88
|
-
|
|
89
|
-
**Layout** — `src/<layer>/` with a barrel `index.ts`; one tsup entry per
|
|
90
|
-
subpath; ESM-only to `dist/`. **Layers** (enforced by `eslint-plugin-boundaries`):
|
|
91
|
-
`utils`/`hooks` (foundation) → `ui` → `shell`. No upward imports.
|
|
92
|
-
|
|
93
|
-
**`"use client"` is not in source** — it is injected onto the client entry
|
|
94
|
-
artifacts post-build by `scripts/inject-use-client.mjs`, then asserted by
|
|
95
|
-
`scripts/verify-build-output.mjs`. `scripts/sync-ui.mjs` adapts shadcn-CLI
|
|
96
|
-
output to repo conventions.
|
|
97
|
-
|
|
98
|
-
**Release** — `pnpm publish --no-git-checks` (the `prepublishOnly` hook builds
|
|
99
|
-
first). Published to the npm public registry.
|
|
1
|
+
# @tradalab/lyra
|
|
2
|
+
|
|
3
|
+
> Tradalab's UI kit — shadcn (`radix-lyra` style) primitives, composed
|
|
4
|
+
> components, and an app shell for **React 19 / Next 16 / Tailwind v4**.
|
|
5
|
+
|
|
6
|
+
ESM-only. Extracted from Tradalab's foundation kit so every company app shares
|
|
7
|
+
the same component surface.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @tradalab/lyra
|
|
13
|
+
# peers: react ^19, react-dom ^19 (next ^16 only if you use /shell)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The stylesheet pulls in Tailwind v4, so your app needs `@tailwindcss/postcss`.
|
|
17
|
+
|
|
18
|
+
```css
|
|
19
|
+
/* app/globals.css */
|
|
20
|
+
@import "@tradalab/lyra/styles.css";
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Subpaths
|
|
24
|
+
|
|
25
|
+
| Subpath | Exports | Boundary |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `@tradalab/lyra/utils` | `cn` | server-safe |
|
|
28
|
+
| `@tradalab/lyra/hooks` | `useMobile` | client |
|
|
29
|
+
| `@tradalab/lyra/ui` | 60 shadcn primitives + `AccountMenu`, `ActionButton`, `Container` | client |
|
|
30
|
+
| `@tradalab/lyra/shell` | `AppShell`, `NavItem`, `NavGroup`, `Breadcrumbs`, `useActiveRoute` | client |
|
|
31
|
+
| `@tradalab/lyra/styles.css` | Tailwind v4 base + `radix-lyra` tokens (light/dark) | CSS |
|
|
32
|
+
|
|
33
|
+
Client subpaths carry a build-injected `"use client"`; import them inside a
|
|
34
|
+
client tree or pass via `children` — don't import them into a Server Component's
|
|
35
|
+
own module.
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { Button, Card, CardHeader } from "@tradalab/lyra/ui"
|
|
39
|
+
import { AppShell, NavItem } from "@tradalab/lyra/shell"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Toast
|
|
43
|
+
|
|
44
|
+
`2.0.0` replaced the sonner-based toast with shadcn's Base UI Toast. `sonner` is
|
|
45
|
+
gone — it is no longer a peer dependency, and apps should drop it.
|
|
46
|
+
|
|
47
|
+
`toast` is a **manager object, not a callable**:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { Toaster, toast } from "@tradalab/lyra/ui"
|
|
51
|
+
|
|
52
|
+
toast.add({ title: "Saved" })
|
|
53
|
+
toast.add({ title: "Upload failed", description: err.message, type: "error" })
|
|
54
|
+
const id = toast.add({ title: "Uploading…", type: "loading", timeout: 0 })
|
|
55
|
+
toast.close(id)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`type` is `"success" | "error" | "info" | "warning" | "loading"` — there is no
|
|
59
|
+
`toast.success("x")`. `timeout: 0` never auto-dismisses (sonner's `duration:
|
|
60
|
+
Infinity`). Position belongs to the viewport this package styles, so sonner's
|
|
61
|
+
`position` and `richColors` props have no equivalent. Mount `<Toaster/>` once —
|
|
62
|
+
apps on `@tradalab/nekit` already get it from `NekitProvider`.
|
|
63
|
+
|
|
64
|
+
## Develop
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
make install # pnpm install
|
|
68
|
+
make build # tsup + inject-use-client + verify-build-output
|
|
69
|
+
make verify # typecheck + lint + test
|
|
70
|
+
make ui-add <name> # scaffold a shadcn primitive, adapt, rebuild barrel
|
|
71
|
+
make ui-update <name> # overwrite a primitive with the latest upstream version
|
|
72
|
+
make ui-sync # re-adapt every primitive + rebuild barrel
|
|
73
|
+
make ui-diff # report which primitives changed upstream (exit 1 if any)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Staying current with shadcn** — `make ui-diff` compares every primitive
|
|
77
|
+
against the live `radix-lyra` registry and exits non-zero when one has moved.
|
|
78
|
+
It normalises away everything the CLI would rewrite anyway (icon-library
|
|
79
|
+
placeholders, `cn-*` template classes, import aliases, prettier line-wrapping),
|
|
80
|
+
so what it prints is real change. Prefer it over `shadcn add --diff`, which
|
|
81
|
+
caps its output at five files per run.
|
|
82
|
+
|
|
83
|
+
A primitive lyra deliberately diverges from carries a `customised` note in
|
|
84
|
+
`primitives.lock.json`; `ui-diff` lists those separately instead of failing on
|
|
85
|
+
them forever. Document the divergence in a JSDoc block in the file too —
|
|
86
|
+
`make ui-update` will overwrite it, and that note is what tells the next person
|
|
87
|
+
to put it back.
|
|
88
|
+
|
|
89
|
+
**Layout** — `src/<layer>/` with a barrel `index.ts`; one tsup entry per
|
|
90
|
+
subpath; ESM-only to `dist/`. **Layers** (enforced by `eslint-plugin-boundaries`):
|
|
91
|
+
`utils`/`hooks` (foundation) → `ui` → `shell`. No upward imports.
|
|
92
|
+
|
|
93
|
+
**`"use client"` is not in source** — it is injected onto the client entry
|
|
94
|
+
artifacts post-build by `scripts/inject-use-client.mjs`, then asserted by
|
|
95
|
+
`scripts/verify-build-output.mjs`. `scripts/sync-ui.mjs` adapts shadcn-CLI
|
|
96
|
+
output to repo conventions.
|
|
97
|
+
|
|
98
|
+
**Release** — `pnpm publish --no-git-checks` (the `prepublishOnly` hook builds
|
|
99
|
+
first). Published to the npm public registry.
|
package/dist/blocks/index.d.ts
CHANGED
|
@@ -125,4 +125,72 @@ declare const TreeIcon: ({ icon, hasChildren, className, ...props }: TreeIconPro
|
|
|
125
125
|
type TreeLabelProps = HTMLAttributes<HTMLSpanElement>;
|
|
126
126
|
declare const TreeLabel: ({ className, ...props }: TreeLabelProps) => React.JSX.Element;
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
interface TzPickerLabels {
|
|
129
|
+
/** Shown when the value is empty and `allowInherit`. Default: "Inherit". */
|
|
130
|
+
inherit?: string;
|
|
131
|
+
/** Placeholder for the search input. Default: "Search timezone". */
|
|
132
|
+
search?: string;
|
|
133
|
+
/** Shown when the filter matches nothing. Default: "No match". */
|
|
134
|
+
noMatch?: string;
|
|
135
|
+
}
|
|
136
|
+
interface TzPickerProps {
|
|
137
|
+
/** IANA zone name, or "" for inherit. */
|
|
138
|
+
value: string;
|
|
139
|
+
onChange: (tz: string) => void;
|
|
140
|
+
/**
|
|
141
|
+
* Whether an empty value is meaningful here. It usually is: a device inherits from its site,
|
|
142
|
+
* a site from its unit, a unit from its parent. Set false where a zone must be chosen.
|
|
143
|
+
*/
|
|
144
|
+
allowInherit?: boolean;
|
|
145
|
+
labels?: TzPickerLabels;
|
|
146
|
+
disabled?: boolean;
|
|
147
|
+
id?: string;
|
|
148
|
+
className?: string;
|
|
149
|
+
}
|
|
150
|
+
declare function TzPicker({ value, onChange, allowInherit, labels, disabled, id, className, }: TzPickerProps): React.JSX.Element;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Pick one id from a known set.
|
|
154
|
+
*
|
|
155
|
+
* The set is INJECTED. That is what lets this live in lyra: it never learns where the
|
|
156
|
+
* options came from, so it works the same for org members, groups, or anything else the app
|
|
157
|
+
* has already loaded - which is exactly how the three copies it replaces differed.
|
|
158
|
+
*
|
|
159
|
+
* Why it exists at all: the screens that use it used to ask for raw uuids typed into a text
|
|
160
|
+
* box. Nobody knows a uuid, so the only way anyone filled those fields was by copying from
|
|
161
|
+
* somewhere else, and a copy can be off by one row. In `people` that attached a personnel
|
|
162
|
+
* record to the wrong account - a wrong-but-valid uid passes every server check and is found
|
|
163
|
+
* by whoever opens someone else's file under their own name.
|
|
164
|
+
*
|
|
165
|
+
* Filtering is client-side, deliberately: every set here is org-scoped and already loaded for
|
|
166
|
+
* the surrounding screen, so matching locally is instant and costs no round trip. A picker
|
|
167
|
+
* paging over every account that exists (admin's platform-wide search) is a different
|
|
168
|
+
* component with a different failure mode - see `PeoplePicker` in `@tradalab/lyra/people`,
|
|
169
|
+
* which takes an async `search` and does not hold a controlled value.
|
|
170
|
+
*/
|
|
171
|
+
/** One choosable thing: an id plus what a person actually recognises it by. */
|
|
172
|
+
interface PickOption {
|
|
173
|
+
id: string;
|
|
174
|
+
label: string;
|
|
175
|
+
/** Secondary line - an email, a status. Searchable along with the label. */
|
|
176
|
+
hint?: string;
|
|
177
|
+
}
|
|
178
|
+
interface OptionPickerLabels {
|
|
179
|
+
/** Shown when nothing is selected. */
|
|
180
|
+
placeholder?: string;
|
|
181
|
+
/** Placeholder inside the search box. */
|
|
182
|
+
search?: string;
|
|
183
|
+
/** Shown when the filter matches nothing. */
|
|
184
|
+
empty?: string;
|
|
185
|
+
}
|
|
186
|
+
declare function OptionPicker({ value, onChange, options, labels, disabled, id, className, }: {
|
|
187
|
+
value: string;
|
|
188
|
+
onChange: (id: string) => void;
|
|
189
|
+
options: PickOption[];
|
|
190
|
+
labels?: OptionPickerLabels;
|
|
191
|
+
disabled?: boolean;
|
|
192
|
+
id?: string;
|
|
193
|
+
className?: string;
|
|
194
|
+
}): React.JSX.Element;
|
|
195
|
+
|
|
196
|
+
export { ConfirmContext, ConfirmDialog, type ConfirmFn, type ConfirmOptions, ConfirmProvider, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, OptionPicker, type OptionPickerLabels, Panel, PanelCard, type PanelCardProps, type PanelItemProps, type PickOption, TreeExpander, type TreeExpanderProps, TreeIcon, type TreeIconProps, TreeLabel, type TreeLabelProps, TreeLines, TreeNode, TreeNodeContent, type TreeNodeContentProps, type TreeNodeProps, TreeNodeTrigger, type TreeNodeTriggerProps, TreeProvider, type TreeProviderProps, TreeView, type TreeViewProps, TzPicker, type TzPickerLabels, type TzPickerProps, useConfirm, useFormField };
|
package/dist/blocks/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { Label } from '../chunk-
|
|
2
|
+
import { Label, Combobox, ComboboxInput, ComboboxContent, ComboboxEmpty, ComboboxList, ComboboxItem } from '../chunk-VA6K3TP3.js';
|
|
3
3
|
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '../chunk-LZZ3UOPT.js';
|
|
4
|
-
import { Dialog, DialogOverlay, DialogContent, DialogTitle, DialogDescription } from '../chunk-
|
|
5
|
-
import
|
|
4
|
+
import { Dialog, DialogOverlay, DialogContent, DialogTitle, DialogDescription } from '../chunk-XWGYXLIA.js';
|
|
5
|
+
import '../chunk-2HDU26FC.js';
|
|
6
|
+
import { Button } from '../chunk-VGOGWZCF.js';
|
|
6
7
|
import { cn } from '../chunk-XH55BHUU.js';
|
|
7
8
|
import * as React from 'react';
|
|
8
9
|
import { createContext, useMemo, useState, useEffect, useContext, useCallback, useId } from 'react';
|
|
@@ -417,7 +418,160 @@ var TreeIcon = ({ icon, hasChildren = false, className, ...props }) => {
|
|
|
417
418
|
return /* @__PURE__ */ jsx("div", { className: cn("mr-2 flex h-4 w-4 items-center justify-center text-muted-foreground transition-transform", className), ...props, children: icon || getDefaultIcon() });
|
|
418
419
|
};
|
|
419
420
|
var TreeLabel = ({ className, ...props }) => /* @__PURE__ */ jsx("span", { className: cn("font flex-1 truncate text-sm", className), ...props });
|
|
421
|
+
var FALLBACK_ZONES = [
|
|
422
|
+
"UTC",
|
|
423
|
+
"Asia/Ho_Chi_Minh",
|
|
424
|
+
"Asia/Bangkok",
|
|
425
|
+
"Asia/Singapore",
|
|
426
|
+
"Asia/Tokyo",
|
|
427
|
+
"Asia/Seoul",
|
|
428
|
+
"Asia/Shanghai",
|
|
429
|
+
"Asia/Kolkata",
|
|
430
|
+
"Europe/London",
|
|
431
|
+
"Europe/Berlin",
|
|
432
|
+
"America/New_York",
|
|
433
|
+
"America/Los_Angeles"
|
|
434
|
+
];
|
|
435
|
+
var cachedZones = null;
|
|
436
|
+
function allZones() {
|
|
437
|
+
if (cachedZones) return cachedZones;
|
|
438
|
+
let names;
|
|
439
|
+
try {
|
|
440
|
+
const supported = Intl.supportedValuesOf;
|
|
441
|
+
names = supported ? supported("timeZone") : FALLBACK_ZONES;
|
|
442
|
+
} catch {
|
|
443
|
+
names = FALLBACK_ZONES;
|
|
444
|
+
}
|
|
445
|
+
cachedZones = names.map((name) => ({ name, offset: offsetOf(name) }));
|
|
446
|
+
return cachedZones;
|
|
447
|
+
}
|
|
448
|
+
function offsetOf(tz) {
|
|
449
|
+
try {
|
|
450
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
451
|
+
timeZone: tz,
|
|
452
|
+
timeZoneName: "shortOffset"
|
|
453
|
+
}).formatToParts(/* @__PURE__ */ new Date());
|
|
454
|
+
return parts.find((p) => p.type === "timeZoneName")?.value ?? "";
|
|
455
|
+
} catch {
|
|
456
|
+
return "";
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function browserTz() {
|
|
460
|
+
try {
|
|
461
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
462
|
+
} catch {
|
|
463
|
+
return "UTC";
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function TzPicker({
|
|
467
|
+
value,
|
|
468
|
+
onChange,
|
|
469
|
+
allowInherit = true,
|
|
470
|
+
labels,
|
|
471
|
+
disabled,
|
|
472
|
+
id,
|
|
473
|
+
className
|
|
474
|
+
}) {
|
|
475
|
+
const l = {
|
|
476
|
+
inherit: labels?.inherit ?? "Inherit",
|
|
477
|
+
search: labels?.search ?? "Search timezone",
|
|
478
|
+
noMatch: labels?.noMatch ?? "No match"
|
|
479
|
+
};
|
|
480
|
+
const own = React.useMemo(() => browserTz(), []);
|
|
481
|
+
const entries = React.useMemo(() => {
|
|
482
|
+
const zones = allZones();
|
|
483
|
+
const out = [];
|
|
484
|
+
if (allowInherit) out.push({ tz: "", name: l.inherit, offset: "" });
|
|
485
|
+
const ownZone = zones.find((z) => z.name === own);
|
|
486
|
+
if (ownZone) out.push({ tz: own, name: own, offset: ownZone.offset });
|
|
487
|
+
for (const z of zones) {
|
|
488
|
+
if (z.name === own) continue;
|
|
489
|
+
out.push({ tz: z.name, name: z.name, offset: z.offset });
|
|
490
|
+
}
|
|
491
|
+
if (value !== "" && !zones.some((z) => z.name === value)) {
|
|
492
|
+
out.push({ tz: value, name: value, offset: offsetOf(value) });
|
|
493
|
+
}
|
|
494
|
+
return out;
|
|
495
|
+
}, [allowInherit, l.inherit, own, value]);
|
|
496
|
+
const selected = entries.find((e) => e.tz === value) ?? entries[0];
|
|
497
|
+
return /* @__PURE__ */ jsxs(
|
|
498
|
+
Combobox,
|
|
499
|
+
{
|
|
500
|
+
items: entries,
|
|
501
|
+
value: selected,
|
|
502
|
+
onValueChange: (item) => onChange(item?.tz ?? ""),
|
|
503
|
+
itemToStringLabel: (e) => e.offset ? `${e.name} \xB7 ${e.offset}` : e.name,
|
|
504
|
+
itemToStringValue: (e) => e.tz,
|
|
505
|
+
isItemEqualToValue: (a, b) => a.tz === b.tz,
|
|
506
|
+
disabled,
|
|
507
|
+
children: [
|
|
508
|
+
/* @__PURE__ */ jsx(
|
|
509
|
+
ComboboxInput,
|
|
510
|
+
{
|
|
511
|
+
id,
|
|
512
|
+
placeholder: l.search,
|
|
513
|
+
className: className ?? "w-full",
|
|
514
|
+
showClear: allowInherit
|
|
515
|
+
}
|
|
516
|
+
),
|
|
517
|
+
/* @__PURE__ */ jsxs(ComboboxContent, { children: [
|
|
518
|
+
/* @__PURE__ */ jsx(ComboboxEmpty, { children: l.noMatch }),
|
|
519
|
+
/* @__PURE__ */ jsx(ComboboxList, { children: (entry) => /* @__PURE__ */ jsxs(ComboboxItem, { value: entry, children: [
|
|
520
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: entry.name }),
|
|
521
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: entry.offset })
|
|
522
|
+
] }, entry.tz || "__inherit") })
|
|
523
|
+
] })
|
|
524
|
+
]
|
|
525
|
+
}
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
var FALLBACK = {
|
|
529
|
+
placeholder: "Select\u2026",
|
|
530
|
+
search: "Search",
|
|
531
|
+
empty: "No match"
|
|
532
|
+
};
|
|
533
|
+
function OptionPicker({
|
|
534
|
+
value,
|
|
535
|
+
onChange,
|
|
536
|
+
options,
|
|
537
|
+
labels,
|
|
538
|
+
disabled,
|
|
539
|
+
id,
|
|
540
|
+
className
|
|
541
|
+
}) {
|
|
542
|
+
const t = (k) => labels?.[k] ?? FALLBACK[k];
|
|
543
|
+
const placeholder = labels?.placeholder ?? FALLBACK.placeholder;
|
|
544
|
+
const entries = React.useMemo(() => {
|
|
545
|
+
const out = [{ id: "", label: placeholder, none: true }, ...options];
|
|
546
|
+
if (value !== "" && !options.some((o) => o.id === value)) {
|
|
547
|
+
out.push({ id: value, label: value });
|
|
548
|
+
}
|
|
549
|
+
return out;
|
|
550
|
+
}, [options, value, placeholder]);
|
|
551
|
+
const selected = entries.find((e) => e.id === value) ?? entries[0];
|
|
552
|
+
return /* @__PURE__ */ jsxs(
|
|
553
|
+
Combobox,
|
|
554
|
+
{
|
|
555
|
+
items: entries,
|
|
556
|
+
value: selected,
|
|
557
|
+
onValueChange: (e) => onChange(e?.id ?? ""),
|
|
558
|
+
itemToStringLabel: (e) => e.none ? e.label : `${e.label} ${e.hint ?? ""} ${e.id}`,
|
|
559
|
+
isItemEqualToValue: (a, b) => a.id === b.id,
|
|
560
|
+
disabled,
|
|
561
|
+
children: [
|
|
562
|
+
/* @__PURE__ */ jsx(ComboboxInput, { id, placeholder: t("search"), className: className ?? "w-full" }),
|
|
563
|
+
/* @__PURE__ */ jsxs(ComboboxContent, { children: [
|
|
564
|
+
/* @__PURE__ */ jsx(ComboboxEmpty, { children: t("empty") }),
|
|
565
|
+
/* @__PURE__ */ jsx(ComboboxList, { children: (entry) => /* @__PURE__ */ jsx(ComboboxItem, { value: entry, children: /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
566
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-sm", children: entry.label }),
|
|
567
|
+
entry.hint ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground truncate text-xs", children: entry.hint }) : null
|
|
568
|
+
] }) }, entry.id || "__none") })
|
|
569
|
+
] })
|
|
570
|
+
]
|
|
571
|
+
}
|
|
572
|
+
);
|
|
573
|
+
}
|
|
420
574
|
|
|
421
|
-
export { ConfirmContext, ConfirmDialog, ConfirmProvider, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Panel, PanelCard, TreeExpander, TreeIcon, TreeLabel, TreeLines, TreeNode, TreeNodeContent, TreeNodeTrigger, TreeProvider, TreeView, useConfirm, useFormField };
|
|
575
|
+
export { ConfirmContext, ConfirmDialog, ConfirmProvider, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, OptionPicker, Panel, PanelCard, TreeExpander, TreeIcon, TreeLabel, TreeLines, TreeNode, TreeNodeContent, TreeNodeTrigger, TreeProvider, TreeView, TzPicker, useConfirm, useFormField };
|
|
422
576
|
//# sourceMappingURL=index.js.map
|
|
423
577
|
//# sourceMappingURL=index.js.map
|