@spacesync/client 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.
- package/README.md +39 -0
- package/dist/analog-clock-picker.d.ts +10 -0
- package/dist/analog-clock-picker.js +408 -0
- package/dist/analog-clock-picker.js.map +1 -0
- package/dist/api.d.ts +222 -0
- package/dist/api.js +167 -0
- package/dist/api.js.map +1 -0
- package/dist/app-modal.d.ts +25 -0
- package/dist/app-modal.js +145 -0
- package/dist/app-modal.js.map +1 -0
- package/dist/app-shell-mobile-nav.d.ts +29 -0
- package/dist/app-shell-mobile-nav.js +122 -0
- package/dist/app-shell-mobile-nav.js.map +1 -0
- package/dist/app-web-shell.d.ts +20 -0
- package/dist/app-web-shell.js +45 -0
- package/dist/app-web-shell.js.map +1 -0
- package/dist/booking-schedule-calendars.d.ts +95 -0
- package/dist/booking-schedule-calendars.js +1343 -0
- package/dist/booking-schedule-calendars.js.map +1 -0
- package/dist/chat-stick-to-bottom.d.ts +22 -0
- package/dist/chat-stick-to-bottom.js +190 -0
- package/dist/chat-stick-to-bottom.js.map +1 -0
- package/dist/detail-page-header.d.ts +16 -0
- package/dist/detail-page-header.js +220 -0
- package/dist/detail-page-header.js.map +1 -0
- package/dist/dialog-CIdvylPK.d.ts +16 -0
- package/dist/errors-D1niseaa.d.ts +34 -0
- package/dist/file-picker-field.d.ts +23 -0
- package/dist/file-picker-field.js +165 -0
- package/dist/file-picker-field.js.map +1 -0
- package/dist/file-viewer-dialog.d.ts +15 -0
- package/dist/file-viewer-dialog.js +387 -0
- package/dist/file-viewer-dialog.js.map +1 -0
- package/dist/floor-switcher.d.ts +14 -0
- package/dist/floor-switcher.js +416 -0
- package/dist/floor-switcher.js.map +1 -0
- package/dist/forms.d.ts +52 -0
- package/dist/forms.js +66 -0
- package/dist/forms.js.map +1 -0
- package/dist/image-picker-field.d.ts +11 -0
- package/dist/image-picker-field.js +135 -0
- package/dist/image-picker-field.js.map +1 -0
- package/dist/layout-canvas-view.d.ts +57 -0
- package/dist/layout-canvas-view.js +974 -0
- package/dist/layout-canvas-view.js.map +1 -0
- package/dist/lib/dateDisplay.d.ts +24 -0
- package/dist/lib/dateDisplay.js +84 -0
- package/dist/lib/dateDisplay.js.map +1 -0
- package/dist/live-resource-meta.d.ts +11 -0
- package/dist/live-resource-meta.js +27 -0
- package/dist/live-resource-meta.js.map +1 -0
- package/dist/live-space-pulse-dot.d.ts +8 -0
- package/dist/live-space-pulse-dot.js +18 -0
- package/dist/live-space-pulse-dot.js.map +1 -0
- package/dist/notification-sidebar-preview.d.ts +37 -0
- package/dist/notification-sidebar-preview.js +121 -0
- package/dist/notification-sidebar-preview.js.map +1 -0
- package/dist/page-list-chrome.d.ts +124 -0
- package/dist/page-list-chrome.js +970 -0
- package/dist/page-list-chrome.js.map +1 -0
- package/dist/platform-support-message-composer.d.ts +38 -0
- package/dist/platform-support-message-composer.js +446 -0
- package/dist/platform-support-message-composer.js.map +1 -0
- package/dist/shell-sidebar-nav.d.ts +16 -0
- package/dist/shell-sidebar-nav.js +69 -0
- package/dist/shell-sidebar-nav.js.map +1 -0
- package/dist/shell-theme.d.ts +6 -0
- package/dist/shell-theme.js +9 -0
- package/dist/shell-theme.js.map +1 -0
- package/dist/shell-top-bar-chrome.d.ts +39 -0
- package/dist/shell-top-bar-chrome.js +105 -0
- package/dist/shell-top-bar-chrome.js.map +1 -0
- package/dist/shell-top-bar.d.ts +16 -0
- package/dist/shell-top-bar.js +71 -0
- package/dist/shell-top-bar.js.map +1 -0
- package/dist/styles/theme.css +223 -0
- package/dist/styles/theme.css.map +1 -0
- package/dist/styles/theme.d.ts +2 -0
- package/dist/support-presence-status.d.ts +11 -0
- package/dist/support-presence-status.js +92 -0
- package/dist/support-presence-status.js.map +1 -0
- package/dist/theme-appearance-control.d.ts +5 -0
- package/dist/theme-appearance-control.js +114 -0
- package/dist/theme-appearance-control.js.map +1 -0
- package/dist/theme-boot-script.d.ts +4 -0
- package/dist/theme-boot-script.js +9 -0
- package/dist/theme-boot-script.js.map +1 -0
- package/dist/theme-provider.d.ts +9 -0
- package/dist/theme-provider.js +25 -0
- package/dist/theme-provider.js.map +1 -0
- package/dist/ui.d.ts +765 -0
- package/dist/ui.js +5637 -0
- package/dist/ui.js.map +1 -0
- package/dist/use-client-pagination.d.ts +22 -0
- package/dist/use-client-pagination.js +51 -0
- package/dist/use-client-pagination.js.map +1 -0
- package/dist/use-client-sort.d.ts +20 -0
- package/dist/use-client-sort.js +41 -0
- package/dist/use-client-sort.js.map +1 -0
- package/package.json +208 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { ChevronLeft } from 'lucide-react';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { createContext, useEffect, useContext } from 'react';
|
|
4
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
5
|
+
import { cva } from 'class-variance-authority';
|
|
6
|
+
import { clsx } from 'clsx';
|
|
7
|
+
import { twMerge } from 'tailwind-merge';
|
|
8
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
9
|
+
|
|
10
|
+
// src/shell/detail-page-header.tsx
|
|
11
|
+
function cn(...inputs) {
|
|
12
|
+
return twMerge(clsx(inputs));
|
|
13
|
+
}
|
|
14
|
+
var buttonVariants = cva(
|
|
15
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium cursor-pointer transition-all disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
16
|
+
{
|
|
17
|
+
variants: {
|
|
18
|
+
variant: {
|
|
19
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80 active:scale-[0.98]",
|
|
20
|
+
destructive: "bg-destructive text-white hover:bg-destructive/90 active:bg-destructive/80 active:scale-[0.98] focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 dark:active:bg-destructive/70",
|
|
21
|
+
outline: "border bg-background text-foreground hover:bg-accent hover:text-accent-foreground active:bg-accent/80 active:scale-[0.98] dark:bg-input/30 dark:border-input dark:hover:bg-input/50 dark:active:bg-input/60",
|
|
22
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 active:bg-secondary/70 active:scale-[0.98]",
|
|
23
|
+
ghost: "hover:bg-accent hover:text-accent-foreground active:bg-accent/90 active:scale-[0.98] dark:hover:bg-accent/50 dark:active:bg-accent/40",
|
|
24
|
+
link: "text-primary underline-offset-4 hover:underline active:text-primary/80"
|
|
25
|
+
},
|
|
26
|
+
size: {
|
|
27
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
28
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
29
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
30
|
+
icon: "size-9 rounded-md"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
variant: "default",
|
|
35
|
+
size: "default"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
var Button = React.forwardRef(function Button2({ className, variant, size, asChild = false, ...props }, ref) {
|
|
40
|
+
const Comp = asChild ? Slot : "button";
|
|
41
|
+
return /* @__PURE__ */ jsx(
|
|
42
|
+
Comp,
|
|
43
|
+
{
|
|
44
|
+
ref,
|
|
45
|
+
"data-slot": "button",
|
|
46
|
+
className: cn(buttonVariants({ variant, size }), className),
|
|
47
|
+
...props
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
Button.displayName = "Button";
|
|
52
|
+
var EMPTY_TOP_BAR_STATE = {};
|
|
53
|
+
var ShellTopBarStoreContext = createContext(null);
|
|
54
|
+
function useShellTopBarStore() {
|
|
55
|
+
const store = useContext(ShellTopBarStoreContext);
|
|
56
|
+
if (!store) {
|
|
57
|
+
throw new Error("ShellTopBar components must be used within ShellTopBarProvider");
|
|
58
|
+
}
|
|
59
|
+
return store;
|
|
60
|
+
}
|
|
61
|
+
function ShellTopBar({
|
|
62
|
+
title,
|
|
63
|
+
icon,
|
|
64
|
+
actions
|
|
65
|
+
}) {
|
|
66
|
+
const store = useShellTopBarStore();
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
store.setState({ title, icon, actions });
|
|
69
|
+
return () => store.setState(EMPTY_TOP_BAR_STATE);
|
|
70
|
+
}, [title, icon, actions, store]);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/shell/shell-top-bar-chrome.ts
|
|
75
|
+
var shellTopBarMutedIconClass = "text-sidebar-foreground/60";
|
|
76
|
+
var shellTopBarTitleTextClass = "text-sidebar-foreground";
|
|
77
|
+
var shellTopBarActionSize = cn(
|
|
78
|
+
"inline-flex !h-7 min-h-0 items-center justify-center gap-1 px-2.5 text-xs font-medium",
|
|
79
|
+
"[&_svg:not([class*='size-'])]:size-3.5"
|
|
80
|
+
);
|
|
81
|
+
var shellTopBarRadius = "rounded-md";
|
|
82
|
+
var shellTopBarNeutralRest = "border border-sidebar-border bg-sidebar-accent text-sidebar-foreground shadow-none transition-colors";
|
|
83
|
+
var shellTopBarNeutralHover = "hover:!border-sidebar-foreground/14 hover:!bg-sidebar-foreground/6 hover:!text-sidebar-foreground";
|
|
84
|
+
var shellTopBarNeutralActive = "active:!border-sidebar-foreground/10 active:!bg-sidebar-foreground/4 active:!text-sidebar-foreground";
|
|
85
|
+
var shellTopBarBackButtonClass = cn(
|
|
86
|
+
"inline-flex !h-6 !w-6 shrink-0 items-center justify-center text-sidebar-foreground/70",
|
|
87
|
+
shellTopBarRadius,
|
|
88
|
+
shellTopBarNeutralRest,
|
|
89
|
+
shellTopBarNeutralHover,
|
|
90
|
+
shellTopBarNeutralActive
|
|
91
|
+
);
|
|
92
|
+
cn(
|
|
93
|
+
"inline-flex !h-7 !w-7 shrink-0 items-center justify-center text-sidebar-foreground/70",
|
|
94
|
+
shellTopBarRadius,
|
|
95
|
+
shellTopBarNeutralRest,
|
|
96
|
+
shellTopBarNeutralHover,
|
|
97
|
+
shellTopBarNeutralActive,
|
|
98
|
+
"[&_svg:not([class*='size-'])]:size-3.5"
|
|
99
|
+
);
|
|
100
|
+
cn(
|
|
101
|
+
shellTopBarActionSize,
|
|
102
|
+
shellTopBarRadius,
|
|
103
|
+
"!border-sidebar-border !bg-sidebar-accent !text-sidebar-foreground shadow-none transition-colors",
|
|
104
|
+
"!py-0",
|
|
105
|
+
shellTopBarNeutralHover,
|
|
106
|
+
shellTopBarNeutralActive,
|
|
107
|
+
"disabled:pointer-events-none disabled:!border-sidebar-border/60 disabled:!bg-sidebar-accent/60 disabled:!text-sidebar-foreground/40"
|
|
108
|
+
);
|
|
109
|
+
cn(
|
|
110
|
+
shellTopBarActionSize,
|
|
111
|
+
shellTopBarRadius,
|
|
112
|
+
"bg-blue-500 text-white transition-colors hover:bg-blue-600",
|
|
113
|
+
"disabled:pointer-events-none disabled:opacity-50"
|
|
114
|
+
);
|
|
115
|
+
cn(
|
|
116
|
+
shellTopBarActionSize,
|
|
117
|
+
shellTopBarRadius,
|
|
118
|
+
"bg-emerald-600 text-white transition-colors hover:bg-emerald-700"
|
|
119
|
+
);
|
|
120
|
+
cn(
|
|
121
|
+
shellTopBarActionSize,
|
|
122
|
+
shellTopBarRadius,
|
|
123
|
+
"border border-emerald-200 bg-emerald-50 text-emerald-700 transition-colors hover:border-emerald-300 hover:bg-emerald-100"
|
|
124
|
+
);
|
|
125
|
+
cn(
|
|
126
|
+
shellTopBarActionSize,
|
|
127
|
+
shellTopBarRadius,
|
|
128
|
+
"border border-blue-200 bg-blue-50 text-blue-700 transition-colors hover:border-blue-300 hover:bg-blue-100"
|
|
129
|
+
);
|
|
130
|
+
cn(
|
|
131
|
+
"flex items-center gap-0.5 border border-sidebar-border bg-sidebar-accent p-0.5",
|
|
132
|
+
shellTopBarRadius
|
|
133
|
+
);
|
|
134
|
+
cn(
|
|
135
|
+
"flex h-7 items-center gap-1.5 border border-sidebar-border bg-sidebar-accent pl-2.5 pr-1 text-sidebar-foreground",
|
|
136
|
+
shellTopBarRadius
|
|
137
|
+
);
|
|
138
|
+
cn(
|
|
139
|
+
"inline-flex !h-6 !w-6 items-center justify-center text-sidebar-foreground/70",
|
|
140
|
+
shellTopBarRadius,
|
|
141
|
+
shellTopBarNeutralRest,
|
|
142
|
+
shellTopBarNeutralHover,
|
|
143
|
+
shellTopBarNeutralActive,
|
|
144
|
+
"[&_svg:not([class*='size-'])]:size-3.5"
|
|
145
|
+
);
|
|
146
|
+
cn(
|
|
147
|
+
shellTopBarActionSize,
|
|
148
|
+
"min-w-[72px]",
|
|
149
|
+
shellTopBarNeutralRest,
|
|
150
|
+
shellTopBarNeutralHover,
|
|
151
|
+
shellTopBarNeutralActive
|
|
152
|
+
);
|
|
153
|
+
var mobileBackClass = "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-border bg-muted text-muted-foreground transition-colors hover:bg-muted/80 hover:text-foreground";
|
|
154
|
+
function DetailTitleRow({
|
|
155
|
+
onBack,
|
|
156
|
+
backAriaLabel,
|
|
157
|
+
icon,
|
|
158
|
+
title,
|
|
159
|
+
backClassName,
|
|
160
|
+
backIconSize
|
|
161
|
+
}) {
|
|
162
|
+
return /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [
|
|
163
|
+
/* @__PURE__ */ jsx(
|
|
164
|
+
Button,
|
|
165
|
+
{
|
|
166
|
+
type: "button",
|
|
167
|
+
variant: "ghost",
|
|
168
|
+
size: "icon",
|
|
169
|
+
onClick: onBack,
|
|
170
|
+
"aria-label": backAriaLabel,
|
|
171
|
+
className: backClassName,
|
|
172
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { size: backIconSize, strokeWidth: 2 })
|
|
173
|
+
}
|
|
174
|
+
),
|
|
175
|
+
/* @__PURE__ */ jsx("span", { className: `flex shrink-0 items-center ${shellTopBarMutedIconClass}`, children: icon }),
|
|
176
|
+
typeof title === "string" ? /* @__PURE__ */ jsx("span", { className: `truncate text-sm font-normal ${shellTopBarTitleTextClass}`, children: title }) : /* @__PURE__ */ jsx("span", { className: `min-w-0 flex-1 truncate text-sm font-normal ${shellTopBarTitleTextClass}`, children: title })
|
|
177
|
+
] });
|
|
178
|
+
}
|
|
179
|
+
function DetailPageShellHeader(props) {
|
|
180
|
+
return /* @__PURE__ */ jsx(
|
|
181
|
+
ShellTopBar,
|
|
182
|
+
{
|
|
183
|
+
title: /* @__PURE__ */ jsx(DetailTitleRow, { ...props, backClassName: shellTopBarBackButtonClass, backIconSize: 14 }),
|
|
184
|
+
actions: props.actions
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
function DetailPageMobileHeader({
|
|
189
|
+
onBack,
|
|
190
|
+
backAriaLabel,
|
|
191
|
+
icon,
|
|
192
|
+
title,
|
|
193
|
+
mobileTitle,
|
|
194
|
+
actions
|
|
195
|
+
}) {
|
|
196
|
+
const resolvedMobileTitle = mobileTitle ?? (typeof title === "string" ? /* @__PURE__ */ jsx("h1", { className: "truncate text-sm font-semibold text-foreground", children: title }) : /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1 truncate text-sm font-semibold text-foreground", children: title }));
|
|
197
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center justify-between gap-3 border-b border-border px-5 py-3 md:hidden", children: [
|
|
198
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
|
|
199
|
+
/* @__PURE__ */ jsx(
|
|
200
|
+
Button,
|
|
201
|
+
{
|
|
202
|
+
type: "button",
|
|
203
|
+
variant: "ghost",
|
|
204
|
+
size: "icon",
|
|
205
|
+
onClick: onBack,
|
|
206
|
+
"aria-label": backAriaLabel,
|
|
207
|
+
className: mobileBackClass,
|
|
208
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { size: 18, strokeWidth: 2 })
|
|
209
|
+
}
|
|
210
|
+
),
|
|
211
|
+
/* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center text-muted-foreground", children: icon }),
|
|
212
|
+
resolvedMobileTitle
|
|
213
|
+
] }),
|
|
214
|
+
actions ? /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-2", children: actions }) : null
|
|
215
|
+
] });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export { DetailPageMobileHeader, DetailPageShellHeader };
|
|
219
|
+
//# sourceMappingURL=detail-page-header.js.map
|
|
220
|
+
//# sourceMappingURL=detail-page-header.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ui/utils.ts","../src/ui/button.tsx","../src/shell/shell-top-bar.tsx","../src/shell/shell-top-bar-chrome.ts","../src/shell/detail-page-header.tsx"],"names":["Button","jsxs","jsx"],"mappings":"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","file":"detail-page-header.js","sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"./utils\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium cursor-pointer transition-all disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80 active:scale-[0.98]\",\n destructive:\n \"bg-destructive text-white hover:bg-destructive/90 active:bg-destructive/80 active:scale-[0.98] focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 dark:active:bg-destructive/70\",\n outline:\n \"border bg-background text-foreground hover:bg-accent hover:text-accent-foreground active:bg-accent/80 active:scale-[0.98] dark:bg-input/30 dark:border-input dark:hover:bg-input/50 dark:active:bg-input/60\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-secondary/80 active:bg-secondary/70 active:scale-[0.98]\",\n ghost:\n \"hover:bg-accent hover:text-accent-foreground active:bg-accent/90 active:scale-[0.98] dark:hover:bg-accent/50 dark:active:bg-accent/40\",\n link: \"text-primary underline-offset-4 hover:underline active:text-primary/80\",\n },\n size: {\n default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n sm: \"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5\",\n lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n icon: \"size-9 rounded-md\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nconst Button = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean;\n }\n>(function Button({ className, variant, size, asChild = false, ...props }, ref) {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n ref={ref}\n data-slot=\"button\"\n className={cn(buttonVariants({ variant, size }), className)}\n {...props}\n />\n );\n});\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n","import {\n createContext,\n useContext,\n useEffect,\n useState,\n useSyncExternalStore,\n type ReactNode,\n} from \"react\";\n\ntype ShellTopBarState = {\n title?: ReactNode;\n icon?: ReactNode;\n actions?: ReactNode;\n};\n\ntype ShellTopBarStore = {\n getSnapshot: () => ShellTopBarState;\n subscribe: (listener: () => void) => () => void;\n setState: (next: ShellTopBarState) => void;\n};\n\nconst EMPTY_TOP_BAR_STATE: ShellTopBarState = {};\n\nfunction createShellTopBarStore(): ShellTopBarStore {\n let state = EMPTY_TOP_BAR_STATE;\n const listeners = new Set<() => void>();\n\n return {\n getSnapshot: () => state,\n subscribe: (listener) => {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n setState: (next) => {\n state = next;\n listeners.forEach((listener) => listener());\n },\n };\n}\n\nconst ShellTopBarStoreContext = createContext<ShellTopBarStore | null>(null);\n\nexport function ShellTopBarProvider({ children }: { children: ReactNode }) {\n const [store] = useState(createShellTopBarStore);\n\n return (\n <ShellTopBarStoreContext.Provider value={store}>\n {children}\n </ShellTopBarStoreContext.Provider>\n );\n}\n\nfunction useShellTopBarStore() {\n const store = useContext(ShellTopBarStoreContext);\n if (!store) {\n throw new Error(\"ShellTopBar components must be used within ShellTopBarProvider\");\n }\n return store;\n}\n\n/** Pages render a title and optional actions for the app shell top bar. */\nexport function ShellTopBar({\n title,\n icon,\n actions,\n}: {\n title: ReactNode;\n icon?: ReactNode;\n actions?: ReactNode;\n}) {\n const store = useShellTopBarStore();\n\n useEffect(() => {\n store.setState({ title, icon, actions });\n return () => store.setState(EMPTY_TOP_BAR_STATE);\n }, [title, icon, actions, store]);\n\n return null;\n}\n\n/** Renders registered page title and actions inside AppWebShell topBar. */\nexport function ShellTopBarFrame() {\n const store = useShellTopBarStore();\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);\n const { title, icon, actions } = state;\n\n if (title == null && actions == null) {\n return <div className=\"flex flex-1 items-center\" />;\n }\n\n const titleIsPlainText = typeof title === \"string\";\n\n return (\n <>\n <div className=\"flex min-w-0 flex-1 items-center\">\n {title != null ? (\n <div\n data-slot=\"shell-top-bar-title\"\n className=\"flex min-w-0 flex-1 items-center gap-2 text-sidebar-foreground\"\n >\n {titleIsPlainText ? (\n <>\n {icon != null ? (\n <span className=\"flex shrink-0 items-center text-sidebar-foreground/60\">{icon}</span>\n ) : null}\n <h1 className=\"truncate text-sm font-normal\">{title}</h1>\n </>\n ) : (\n title\n )}\n </div>\n ) : null}\n </div>\n {actions != null ? (\n <div data-slot=\"shell-top-bar-actions\" className=\"flex shrink-0 items-center gap-1.5\">\n {actions}\n </div>\n ) : null}\n </>\n );\n}\n","import { getShellTheme } from \"../theme/shell-theme\";\nimport { cn } from \"../ui/utils\";\n\nexport const shellTopBarMutedIconClass = \"text-sidebar-foreground/60\";\nexport const shellTopBarTitleTextClass = \"text-sidebar-foreground\";\n\n/** Shared sizing and corner radius for shell top bar actions. */\nconst shellTopBarActionSize = cn(\n \"inline-flex !h-7 min-h-0 items-center justify-center gap-1 px-2.5 text-xs font-medium\",\n \"[&_svg:not([class*='size-'])]:size-3.5\",\n);\nconst shellTopBarRadius = \"rounded-md\";\n\nfunction isDarkShell() {\n return getShellTheme() === \"dark\";\n}\n\n/** Resting surface for neutral shell controls (secondary, outline, icon). */\nconst shellTopBarNeutralRest =\n \"border border-sidebar-border bg-sidebar-accent text-sidebar-foreground shadow-none transition-colors\";\n\n/** Visible hover — lighten fill on dark shell, darken on light shell. */\nconst shellTopBarNeutralHover = isDarkShell()\n ? \"hover:!border-sidebar-foreground/25 hover:!bg-sidebar-foreground/12 hover:!text-sidebar-foreground\"\n : \"hover:!border-sidebar-foreground/14 hover:!bg-sidebar-foreground/6 hover:!text-sidebar-foreground\";\n\nconst shellTopBarNeutralActive = isDarkShell()\n ? \"active:!border-sidebar-foreground/20 active:!bg-sidebar-foreground/8 active:!text-sidebar-foreground\"\n : \"active:!border-sidebar-foreground/10 active:!bg-sidebar-foreground/4 active:!text-sidebar-foreground\";\n\n/** Apply to native shell actions (links, ticket buttons) so they share the same hover treatment as {@link ShellTopBarSecondaryActionButton}. */\nexport const shellTopBarNeutralActionDataAttr = {\n \"data-shell-top-bar-action\": \"\",\n} as const;\n\n/** Compact back control in the shell top bar (detail pages). */\nexport const shellTopBarBackButtonClass = cn(\n \"inline-flex !h-6 !w-6 shrink-0 items-center justify-center text-sidebar-foreground/70\",\n shellTopBarRadius,\n shellTopBarNeutralRest,\n shellTopBarNeutralHover,\n shellTopBarNeutralActive,\n);\n\n/** Icon-only control (e.g. overflow menu) in the shell top bar. */\nexport const shellTopBarIconButtonClass = cn(\n \"inline-flex !h-7 !w-7 shrink-0 items-center justify-center text-sidebar-foreground/70\",\n shellTopBarRadius,\n shellTopBarNeutralRest,\n shellTopBarNeutralHover,\n shellTopBarNeutralActive,\n \"[&_svg:not([class*='size-'])]:size-3.5\",\n);\n\n/**\n * Neutral secondary / outline action in the shell top bar.\n * Uses `!` on hover so shadcn `Button variant=\"outline\"` cannot win.\n */\nexport const shellTopBarSecondaryButtonClass = cn(\n shellTopBarActionSize,\n shellTopBarRadius,\n \"!border-sidebar-border !bg-sidebar-accent !text-sidebar-foreground shadow-none transition-colors\",\n \"!py-0\",\n shellTopBarNeutralHover,\n shellTopBarNeutralActive,\n \"disabled:pointer-events-none disabled:!border-sidebar-border/60 disabled:!bg-sidebar-accent/60 disabled:!text-sidebar-foreground/40\",\n);\n\n/** Primary CTA in the shell top bar (New invoice, Add staff, etc.). */\nexport const shellTopBarPrimaryButtonClass = cn(\n shellTopBarActionSize,\n shellTopBarRadius,\n \"bg-blue-500 text-white transition-colors hover:bg-blue-600\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n);\n\n/** Positive / payment emphasis in the shell top bar (e.g. Mark as paid). */\nexport const shellTopBarAccentButtonClass = cn(\n shellTopBarActionSize,\n shellTopBarRadius,\n \"bg-emerald-600 text-white transition-colors hover:bg-emerald-700\",\n);\n\n/** Positive status action in the shell top bar (e.g. Mark resolved). */\nexport const shellTopBarSuccessButtonClass = cn(\n shellTopBarActionSize,\n shellTopBarRadius,\n isDarkShell()\n ? \"border border-emerald-500/35 bg-emerald-500/15 text-emerald-300 transition-colors hover:border-emerald-400/45 hover:bg-emerald-500/25\"\n : \"border border-emerald-200 bg-emerald-50 text-emerald-700 transition-colors hover:border-emerald-300 hover:bg-emerald-100\",\n);\n\n/** Informative / reopen action in the shell top bar. */\nexport const shellTopBarInfoButtonClass = cn(\n shellTopBarActionSize,\n shellTopBarRadius,\n isDarkShell()\n ? \"border border-blue-500/35 bg-blue-500/15 text-blue-300 transition-colors hover:border-blue-400/45 hover:bg-blue-500/25\"\n : \"border border-blue-200 bg-blue-50 text-blue-700 transition-colors hover:border-blue-300 hover:bg-blue-100\",\n);\n\n/** Outline-style action — same neutral chrome as {@link shellTopBarSecondaryButtonClass}. */\nexport const shellTopBarOutlineButtonClass = shellTopBarSecondaryButtonClass;\n\n/** Wrapper for segmented toggles (2D / list, etc.) in the shell top bar. */\nexport const shellTopBarSegmentedShellClass = cn(\n \"flex items-center gap-0.5 border border-sidebar-border bg-sidebar-accent p-0.5\",\n shellTopBarRadius,\n);\n\nexport function shellTopBarSegmentedItemClass(active: boolean) {\n const dark = isDarkShell();\n return cn(\n shellTopBarActionSize,\n \"!h-6 px-2\",\n \"rounded-sm\",\n active\n ? dark\n ? \"bg-sidebar text-sidebar-foreground shadow-sm\"\n : \"bg-white text-sidebar-foreground shadow-sm\"\n : dark\n ? \"text-sidebar-foreground/60 hover:bg-sidebar-foreground/10 hover:text-sidebar-foreground\"\n : \"text-sidebar-foreground/60 hover:bg-sidebar-foreground/6 hover:text-sidebar-foreground\",\n );\n}\n\n/** Grouped field shell (month picker, etc.) in the top bar. */\nexport const shellTopBarControlGroupClass = cn(\n \"flex h-7 items-center gap-1.5 border border-sidebar-border bg-sidebar-accent pl-2.5 pr-1 text-sidebar-foreground\",\n shellTopBarRadius,\n);\n\nexport const shellTopBarControlLabelClass =\n \"m-0 shrink-0 text-[11px] font-normal text-sidebar-foreground/60\";\n\n/** Select trigger embedded in {@link shellTopBarControlGroupClass}. */\nexport const shellTopBarSelectTriggerClass =\n \"h-6 min-w-[140px] flex-1 border-0 bg-transparent px-1 text-xs text-sidebar-foreground shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 [&_svg]:text-sidebar-foreground/60\";\n\n/** Square control for date steppers and similar compact actions. */\nexport const shellTopBarCompactIconButtonClass = cn(\n \"inline-flex !h-6 !w-6 items-center justify-center text-sidebar-foreground/70\",\n shellTopBarRadius,\n shellTopBarNeutralRest,\n shellTopBarNeutralHover,\n shellTopBarNeutralActive,\n \"[&_svg:not([class*='size-'])]:size-3.5\",\n);\n\n/** Center label in a date stepper (shows “Today” or a formatted date). */\nexport const shellTopBarDateLabelButtonClass = cn(\n shellTopBarActionSize,\n \"min-w-[72px]\",\n shellTopBarNeutralRest,\n shellTopBarNeutralHover,\n shellTopBarNeutralActive,\n);\n","import type { ReactNode } from \"react\";\nimport { ChevronLeft } from \"lucide-react\";\nimport { Button } from \"../ui/button\";\nimport { ShellTopBar } from \"./shell-top-bar\";\nimport {\n shellTopBarBackButtonClass,\n shellTopBarMutedIconClass,\n shellTopBarTitleTextClass,\n} from \"./shell-top-bar-chrome\";\n\nexport type DetailPageHeaderProps = {\n onBack: () => void;\n backAriaLabel: string;\n icon: ReactNode;\n title: ReactNode;\n /** Defaults to `title` when it is a plain string */\n mobileTitle?: ReactNode;\n actions?: ReactNode;\n};\n\nconst mobileBackClass =\n \"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-border bg-muted text-muted-foreground transition-colors hover:bg-muted/80 hover:text-foreground\";\n\nfunction DetailTitleRow({\n onBack,\n backAriaLabel,\n icon,\n title,\n backClassName,\n backIconSize,\n}: DetailPageHeaderProps & { backClassName: string; backIconSize: number }) {\n return (\n <span className=\"flex min-w-0 items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={onBack}\n aria-label={backAriaLabel}\n className={backClassName}\n >\n <ChevronLeft size={backIconSize} strokeWidth={2} />\n </Button>\n <span className={`flex shrink-0 items-center ${shellTopBarMutedIconClass}`}>{icon}</span>\n {typeof title === \"string\" ? (\n <span className={`truncate text-sm font-normal ${shellTopBarTitleTextClass}`}>{title}</span>\n ) : (\n <span className={`min-w-0 flex-1 truncate text-sm font-normal ${shellTopBarTitleTextClass}`}>\n {title}\n </span>\n )}\n </span>\n );\n}\n\nexport function DetailPageShellHeader(props: DetailPageHeaderProps) {\n return (\n <ShellTopBar\n title={<DetailTitleRow {...props} backClassName={shellTopBarBackButtonClass} backIconSize={14} />}\n actions={props.actions}\n />\n );\n}\n\nexport function DetailPageMobileHeader({\n onBack,\n backAriaLabel,\n icon,\n title,\n mobileTitle,\n actions,\n}: DetailPageHeaderProps) {\n const resolvedMobileTitle =\n mobileTitle ??\n (typeof title === \"string\" ? (\n <h1 className=\"truncate text-sm font-semibold text-foreground\">{title}</h1>\n ) : (\n <div className=\"min-w-0 flex-1 truncate text-sm font-semibold text-foreground\">{title}</div>\n ));\n\n return (\n <div className=\"flex shrink-0 items-center justify-between gap-3 border-b border-border px-5 py-3 md:hidden\">\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={onBack}\n aria-label={backAriaLabel}\n className={mobileBackClass}\n >\n <ChevronLeft size={18} strokeWidth={2} />\n </Button>\n <span className=\"flex shrink-0 items-center text-muted-foreground\">{icon}</span>\n {resolvedMobileTitle}\n </div>\n {actions ? <div className=\"flex shrink-0 items-center gap-2\">{actions}</div> : null}\n </div>\n );\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
4
|
+
|
|
5
|
+
declare function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>): react_jsx_runtime.JSX.Element;
|
|
6
|
+
declare function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>): react_jsx_runtime.JSX.Element;
|
|
7
|
+
declare function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>): react_jsx_runtime.JSX.Element;
|
|
8
|
+
declare function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>): react_jsx_runtime.JSX.Element;
|
|
9
|
+
declare function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>): react_jsx_runtime.JSX.Element;
|
|
10
|
+
declare function DialogContent({ className, children, ...props }: React.ComponentProps<typeof DialogPrimitive.Content>): react_jsx_runtime.JSX.Element;
|
|
11
|
+
declare function DialogHeader({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
|
|
12
|
+
declare function DialogFooter({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
|
|
13
|
+
declare function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>): react_jsx_runtime.JSX.Element;
|
|
14
|
+
declare function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>): react_jsx_runtime.JSX.Element;
|
|
15
|
+
|
|
16
|
+
export { DialogContent as D, DialogFooter as a, DialogHeader as b, Dialog as c, DialogClose as d, DialogDescription as e, DialogTitle as f, DialogTrigger as g, DialogOverlay as h, DialogPortal as i };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* - "network": the request never got a response at all (server unreachable, timeout, CORS/preflight failure).
|
|
3
|
+
* - "validation": HTTP 400 from our API where `error.details` is the Zod-issues shape produced by
|
|
4
|
+
* `validate.middleware.ts` — field-mappable.
|
|
5
|
+
* - "http": every other status our API returns (401, 403, 404, 409, 500, ...). Not field-specific by default.
|
|
6
|
+
*/
|
|
7
|
+
type ApiClientErrorKind = "network" | "validation" | "http";
|
|
8
|
+
interface ApiFieldError {
|
|
9
|
+
/** Dot-path, e.g. "email" — matched against react-hook-form field names. */
|
|
10
|
+
path: string;
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
13
|
+
interface ApiClientErrorOptions {
|
|
14
|
+
statusCode?: number;
|
|
15
|
+
details?: unknown;
|
|
16
|
+
fieldErrors?: ApiFieldError[];
|
|
17
|
+
}
|
|
18
|
+
declare class ApiClientError extends Error {
|
|
19
|
+
readonly kind: ApiClientErrorKind;
|
|
20
|
+
readonly statusCode?: number;
|
|
21
|
+
readonly details?: unknown;
|
|
22
|
+
readonly fieldErrors?: ApiFieldError[];
|
|
23
|
+
constructor(kind: ApiClientErrorKind, message: string, options?: ApiClientErrorOptions);
|
|
24
|
+
}
|
|
25
|
+
declare function isApiClientError(err: unknown): err is ApiClientError;
|
|
26
|
+
/**
|
|
27
|
+
* Normalizes anything that can be thrown while making a request (axios error,
|
|
28
|
+
* envelope-shape mismatch, or an unexpected exception) into one `ApiClientError`,
|
|
29
|
+
* classified by `kind` so callers like `useApiForm` can apply a sensible default
|
|
30
|
+
* per failure mode instead of treating a dropped connection the same as a 409.
|
|
31
|
+
*/
|
|
32
|
+
declare function normalizeAxiosError(err: unknown): ApiClientError;
|
|
33
|
+
|
|
34
|
+
export { ApiClientError as A, type ApiClientErrorKind as a, type ApiClientErrorOptions as b, type ApiFieldError as c, isApiClientError as i, normalizeAxiosError as n };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type FilePickerFieldVariant = "card" | "button" | "inline";
|
|
5
|
+
interface FilePickerFieldProps {
|
|
6
|
+
label?: string;
|
|
7
|
+
actionLabel?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
selectedLabel?: string;
|
|
10
|
+
accept?: string;
|
|
11
|
+
multiple?: boolean;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
variant?: FilePickerFieldVariant;
|
|
14
|
+
icon?: ReactNode;
|
|
15
|
+
className?: string;
|
|
16
|
+
triggerClassName?: string;
|
|
17
|
+
inputClassName?: string;
|
|
18
|
+
title?: string;
|
|
19
|
+
onFilesSelected: (files: FileList | null) => void;
|
|
20
|
+
}
|
|
21
|
+
declare function FilePickerField({ label, actionLabel, description, selectedLabel, accept, multiple, disabled, variant, icon, className, triggerClassName, inputClassName, title, onFilesSelected, }: FilePickerFieldProps): react_jsx_runtime.JSX.Element;
|
|
22
|
+
|
|
23
|
+
export { FilePickerField, type FilePickerFieldProps, type FilePickerFieldVariant };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useRef } from 'react';
|
|
3
|
+
import { Upload, Paperclip } from 'lucide-react';
|
|
4
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
5
|
+
import { cva } from 'class-variance-authority';
|
|
6
|
+
import { clsx } from 'clsx';
|
|
7
|
+
import { twMerge } from 'tailwind-merge';
|
|
8
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
9
|
+
|
|
10
|
+
// src/files/file-picker-field.tsx
|
|
11
|
+
function cn(...inputs) {
|
|
12
|
+
return twMerge(clsx(inputs));
|
|
13
|
+
}
|
|
14
|
+
var buttonVariants = cva(
|
|
15
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium cursor-pointer transition-all disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
16
|
+
{
|
|
17
|
+
variants: {
|
|
18
|
+
variant: {
|
|
19
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80 active:scale-[0.98]",
|
|
20
|
+
destructive: "bg-destructive text-white hover:bg-destructive/90 active:bg-destructive/80 active:scale-[0.98] focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 dark:active:bg-destructive/70",
|
|
21
|
+
outline: "border bg-background text-foreground hover:bg-accent hover:text-accent-foreground active:bg-accent/80 active:scale-[0.98] dark:bg-input/30 dark:border-input dark:hover:bg-input/50 dark:active:bg-input/60",
|
|
22
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 active:bg-secondary/70 active:scale-[0.98]",
|
|
23
|
+
ghost: "hover:bg-accent hover:text-accent-foreground active:bg-accent/90 active:scale-[0.98] dark:hover:bg-accent/50 dark:active:bg-accent/40",
|
|
24
|
+
link: "text-primary underline-offset-4 hover:underline active:text-primary/80"
|
|
25
|
+
},
|
|
26
|
+
size: {
|
|
27
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
28
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
29
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
30
|
+
icon: "size-9 rounded-md"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
variant: "default",
|
|
35
|
+
size: "default"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
var Button = React.forwardRef(function Button2({ className, variant, size, asChild = false, ...props }, ref) {
|
|
40
|
+
const Comp = asChild ? Slot : "button";
|
|
41
|
+
return /* @__PURE__ */ jsx(
|
|
42
|
+
Comp,
|
|
43
|
+
{
|
|
44
|
+
ref,
|
|
45
|
+
"data-slot": "button",
|
|
46
|
+
className: cn(buttonVariants({ variant, size }), className),
|
|
47
|
+
...props
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
Button.displayName = "Button";
|
|
52
|
+
function FilePickerField({
|
|
53
|
+
label,
|
|
54
|
+
actionLabel,
|
|
55
|
+
description,
|
|
56
|
+
selectedLabel,
|
|
57
|
+
accept,
|
|
58
|
+
multiple = false,
|
|
59
|
+
disabled = false,
|
|
60
|
+
variant = "card",
|
|
61
|
+
icon,
|
|
62
|
+
className,
|
|
63
|
+
triggerClassName,
|
|
64
|
+
inputClassName,
|
|
65
|
+
title,
|
|
66
|
+
onFilesSelected
|
|
67
|
+
}) {
|
|
68
|
+
const inputRef = useRef(null);
|
|
69
|
+
const triggerLabel = selectedLabel ?? actionLabel ?? (multiple ? "Choose files" : "Choose file");
|
|
70
|
+
const fallbackIcon = variant === "card" ? /* @__PURE__ */ jsx(Upload, { size: 16, className: "text-muted-foreground" }) : /* @__PURE__ */ jsx(Paperclip, { size: 13 });
|
|
71
|
+
const handleFiles = (files) => {
|
|
72
|
+
if (disabled) return;
|
|
73
|
+
onFilesSelected(files);
|
|
74
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
75
|
+
};
|
|
76
|
+
const input = /* @__PURE__ */ jsx(
|
|
77
|
+
"input",
|
|
78
|
+
{
|
|
79
|
+
ref: inputRef,
|
|
80
|
+
type: "file",
|
|
81
|
+
accept,
|
|
82
|
+
multiple,
|
|
83
|
+
disabled,
|
|
84
|
+
className: cn("hidden", inputClassName),
|
|
85
|
+
onChange: (event) => handleFiles(event.target.files)
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
if (variant === "button") {
|
|
89
|
+
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
90
|
+
input,
|
|
91
|
+
/* @__PURE__ */ jsxs(
|
|
92
|
+
Button,
|
|
93
|
+
{
|
|
94
|
+
type: "button",
|
|
95
|
+
variant: "outline",
|
|
96
|
+
disabled,
|
|
97
|
+
title,
|
|
98
|
+
className: triggerClassName,
|
|
99
|
+
onClick: () => inputRef.current?.click(),
|
|
100
|
+
children: [
|
|
101
|
+
icon ?? fallbackIcon,
|
|
102
|
+
triggerLabel
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
] });
|
|
107
|
+
}
|
|
108
|
+
if (variant === "inline") {
|
|
109
|
+
return /* @__PURE__ */ jsxs("span", { className, children: [
|
|
110
|
+
input,
|
|
111
|
+
/* @__PURE__ */ jsxs(
|
|
112
|
+
"button",
|
|
113
|
+
{
|
|
114
|
+
type: "button",
|
|
115
|
+
disabled,
|
|
116
|
+
title,
|
|
117
|
+
className: cn(
|
|
118
|
+
"inline-flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-blue-600 disabled:pointer-events-none disabled:text-muted-foreground/60 dark:text-blue-400",
|
|
119
|
+
triggerClassName
|
|
120
|
+
),
|
|
121
|
+
onClick: () => inputRef.current?.click(),
|
|
122
|
+
children: [
|
|
123
|
+
icon ?? fallbackIcon,
|
|
124
|
+
triggerLabel
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
] });
|
|
129
|
+
}
|
|
130
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-1.5", className), children: [
|
|
131
|
+
label ? /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium uppercase tracking-widest text-muted-foreground", children: label }) : null,
|
|
132
|
+
input,
|
|
133
|
+
/* @__PURE__ */ jsxs(
|
|
134
|
+
"button",
|
|
135
|
+
{
|
|
136
|
+
type: "button",
|
|
137
|
+
disabled,
|
|
138
|
+
title,
|
|
139
|
+
className: cn(
|
|
140
|
+
"flex cursor-pointer flex-col items-center justify-center rounded-lg border border-dashed border-border bg-muted/80 px-4 py-6 text-center transition-colors hover:border-border hover:bg-accent disabled:pointer-events-none disabled:opacity-60",
|
|
141
|
+
triggerClassName
|
|
142
|
+
),
|
|
143
|
+
onClick: () => inputRef.current?.click(),
|
|
144
|
+
onDragOver: (event) => {
|
|
145
|
+
if (disabled) return;
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
},
|
|
148
|
+
onDrop: (event) => {
|
|
149
|
+
if (disabled) return;
|
|
150
|
+
event.preventDefault();
|
|
151
|
+
handleFiles(event.dataTransfer.files);
|
|
152
|
+
},
|
|
153
|
+
children: [
|
|
154
|
+
/* @__PURE__ */ jsx("span", { className: "mb-1 flex size-8 items-center justify-center rounded-lg bg-muted", children: icon ?? fallbackIcon }),
|
|
155
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: triggerLabel }),
|
|
156
|
+
description ? /* @__PURE__ */ jsx("span", { className: "mt-0.5 text-[10px] text-muted-foreground", children: description }) : null
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
] });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export { FilePickerField };
|
|
164
|
+
//# sourceMappingURL=file-picker-field.js.map
|
|
165
|
+
//# sourceMappingURL=file-picker-field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ui/utils.ts","../src/ui/button.tsx","../src/files/file-picker-field.tsx"],"names":["Button","jsx"],"mappings":"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","file":"file-picker-field.js","sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"./utils\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium cursor-pointer transition-all disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80 active:scale-[0.98]\",\n destructive:\n \"bg-destructive text-white hover:bg-destructive/90 active:bg-destructive/80 active:scale-[0.98] focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 dark:active:bg-destructive/70\",\n outline:\n \"border bg-background text-foreground hover:bg-accent hover:text-accent-foreground active:bg-accent/80 active:scale-[0.98] dark:bg-input/30 dark:border-input dark:hover:bg-input/50 dark:active:bg-input/60\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-secondary/80 active:bg-secondary/70 active:scale-[0.98]\",\n ghost:\n \"hover:bg-accent hover:text-accent-foreground active:bg-accent/90 active:scale-[0.98] dark:hover:bg-accent/50 dark:active:bg-accent/40\",\n link: \"text-primary underline-offset-4 hover:underline active:text-primary/80\",\n },\n size: {\n default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n sm: \"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5\",\n lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n icon: \"size-9 rounded-md\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nconst Button = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean;\n }\n>(function Button({ className, variant, size, asChild = false, ...props }, ref) {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n ref={ref}\n data-slot=\"button\"\n className={cn(buttonVariants({ variant, size }), className)}\n {...props}\n />\n );\n});\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n","import { useRef, type ReactNode } from \"react\";\nimport { Paperclip, Upload } from \"lucide-react\";\nimport { Button } from \"../ui/button\";\nimport { cn } from \"../ui/utils\";\n\nexport type FilePickerFieldVariant = \"card\" | \"button\" | \"inline\";\n\nexport interface FilePickerFieldProps {\n label?: string;\n actionLabel?: string;\n description?: string;\n selectedLabel?: string;\n accept?: string;\n multiple?: boolean;\n disabled?: boolean;\n variant?: FilePickerFieldVariant;\n icon?: ReactNode;\n className?: string;\n triggerClassName?: string;\n inputClassName?: string;\n title?: string;\n onFilesSelected: (files: FileList | null) => void;\n}\n\nexport function FilePickerField({\n label,\n actionLabel,\n description,\n selectedLabel,\n accept,\n multiple = false,\n disabled = false,\n variant = \"card\",\n icon,\n className,\n triggerClassName,\n inputClassName,\n title,\n onFilesSelected,\n}: FilePickerFieldProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n const triggerLabel = selectedLabel ?? actionLabel ?? (multiple ? \"Choose files\" : \"Choose file\");\n const fallbackIcon = variant === \"card\" ? (\n <Upload size={16} className=\"text-muted-foreground\" />\n ) : (\n <Paperclip size={13} />\n );\n\n const handleFiles = (files: FileList | null) => {\n if (disabled) return;\n onFilesSelected(files);\n if (inputRef.current) inputRef.current.value = \"\";\n };\n\n const input = (\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n className={cn(\"hidden\", inputClassName)}\n onChange={(event) => handleFiles(event.target.files)}\n />\n );\n\n if (variant === \"button\") {\n return (\n <div className={className}>\n {input}\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={disabled}\n title={title}\n className={triggerClassName}\n onClick={() => inputRef.current?.click()}\n >\n {icon ?? fallbackIcon}\n {triggerLabel}\n </Button>\n </div>\n );\n }\n\n if (variant === \"inline\") {\n return (\n <span className={className}>\n {input}\n <button\n type=\"button\"\n disabled={disabled}\n title={title}\n className={cn(\n \"inline-flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-blue-600 disabled:pointer-events-none disabled:text-muted-foreground/60 dark:text-blue-400\",\n triggerClassName,\n )}\n onClick={() => inputRef.current?.click()}\n >\n {icon ?? fallbackIcon}\n {triggerLabel}\n </button>\n </span>\n );\n }\n\n return (\n <div className={cn(\"flex flex-col gap-1.5\", className)}>\n {label ? (\n <span className=\"text-[10px] font-medium uppercase tracking-widest text-muted-foreground\">\n {label}\n </span>\n ) : null}\n {input}\n <button\n type=\"button\"\n disabled={disabled}\n title={title}\n className={cn(\n \"flex cursor-pointer flex-col items-center justify-center rounded-lg border border-dashed border-border bg-muted/80 px-4 py-6 text-center transition-colors hover:border-border hover:bg-accent disabled:pointer-events-none disabled:opacity-60\",\n triggerClassName,\n )}\n onClick={() => inputRef.current?.click()}\n onDragOver={(event) => {\n if (disabled) return;\n event.preventDefault();\n }}\n onDrop={(event) => {\n if (disabled) return;\n event.preventDefault();\n handleFiles(event.dataTransfer.files);\n }}\n >\n <span className=\"mb-1 flex size-8 items-center justify-center rounded-lg bg-muted\">\n {icon ?? fallbackIcon}\n </span>\n <span className=\"text-xs font-medium text-muted-foreground\">{triggerLabel}</span>\n {description ? (\n <span className=\"mt-0.5 text-[10px] text-muted-foreground\">{description}</span>\n ) : null}\n </button>\n </div>\n );\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
interface FileViewerFile {
|
|
4
|
+
name: string;
|
|
5
|
+
mimeType: string;
|
|
6
|
+
sizeLabel?: string;
|
|
7
|
+
url: string;
|
|
8
|
+
}
|
|
9
|
+
declare function FileViewerDialog({ file, open, onOpenChange, }: {
|
|
10
|
+
file: FileViewerFile | null;
|
|
11
|
+
open: boolean;
|
|
12
|
+
onOpenChange: (open: boolean) => void;
|
|
13
|
+
}): react_jsx_runtime.JSX.Element;
|
|
14
|
+
|
|
15
|
+
export { FileViewerDialog, type FileViewerFile };
|