@alepha/ui 0.11.3 → 0.11.5
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/dist/AlephaMantineProvider-Ba88lMeq.js +3 -0
- package/dist/AlephaMantineProvider-Be0DAazb.js +150 -0
- package/dist/AlephaMantineProvider-Be0DAazb.js.map +1 -0
- package/dist/index.d.ts +289 -225
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +650 -729
- package/dist/index.js.map +1 -1
- package/package.json +14 -12
- package/src/RootRouter.ts +1 -1
- package/src/components/buttons/ActionButton.tsx +542 -0
- package/src/components/buttons/BurgerButton.tsx +20 -0
- package/src/components/{DarkModeButton.tsx → buttons/DarkModeButton.tsx} +27 -14
- package/src/components/buttons/LanguageButton.tsx +28 -0
- package/src/components/buttons/OmnibarButton.tsx +32 -0
- package/src/components/buttons/ToggleSidebarButton.tsx +28 -0
- package/src/components/dialogs/AlertDialog.tsx +10 -10
- package/src/components/dialogs/ConfirmDialog.tsx +18 -18
- package/src/components/dialogs/PromptDialog.tsx +5 -3
- package/src/components/{Control.tsx → form/Control.tsx} +6 -3
- package/src/components/{ControlDate.tsx → form/ControlDate.tsx} +4 -1
- package/src/components/{ControlSelect.tsx → form/ControlSelect.tsx} +4 -1
- package/src/components/{TypeForm.tsx → form/TypeForm.tsx} +8 -6
- package/src/components/layout/AdminShell.tsx +97 -0
- package/src/components/{AlephaMantineProvider.tsx → layout/AlephaMantineProvider.tsx} +30 -10
- package/src/components/layout/AppBar.tsx +133 -0
- package/src/components/layout/Omnibar.tsx +43 -0
- package/src/components/layout/Sidebar.tsx +410 -0
- package/src/components/table/DataTable.tsx +63 -0
- package/src/constants/ui.ts +8 -0
- package/src/index.ts +89 -24
- package/src/services/DialogService.tsx +13 -32
- package/src/services/ToastService.tsx +16 -4
- package/src/utils/parseInput.ts +1 -1
- package/dist/AlephaMantineProvider-DDbIijPF.js +0 -96
- package/dist/AlephaMantineProvider-DDbIijPF.js.map +0 -1
- package/dist/AlephaMantineProvider-pOu8hOzK.js +0 -3
- package/src/components/Action.tsx +0 -345
- package/src/components/DataTable.css +0 -199
- package/src/components/DataTable.tsx +0 -724
- package/src/components/Omnibar.tsx +0 -77
- package/src/components/Sidebar.css +0 -217
- package/src/components/Sidebar.tsx +0 -255
package/src/RootRouter.ts
CHANGED
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type RouterGoOptions,
|
|
3
|
+
type UseActionReturn,
|
|
4
|
+
type UseActiveOptions,
|
|
5
|
+
useAction,
|
|
6
|
+
useActive,
|
|
7
|
+
useRouter,
|
|
8
|
+
} from "@alepha/react";
|
|
9
|
+
import { type FormModel, useFormState } from "@alepha/react-form";
|
|
10
|
+
import {
|
|
11
|
+
Button,
|
|
12
|
+
type ButtonProps,
|
|
13
|
+
Flex,
|
|
14
|
+
Menu,
|
|
15
|
+
type MenuItemProps,
|
|
16
|
+
type MenuProps,
|
|
17
|
+
type MenuTargetProps,
|
|
18
|
+
ThemeIcon,
|
|
19
|
+
type ThemeIconProps,
|
|
20
|
+
Tooltip,
|
|
21
|
+
type TooltipProps,
|
|
22
|
+
} from "@mantine/core";
|
|
23
|
+
import { IconCheck, IconChevronRight } from "@tabler/icons-react";
|
|
24
|
+
import type { ButtonHTMLAttributes, ReactNode } from "react";
|
|
25
|
+
|
|
26
|
+
export interface ActionMenuItem {
|
|
27
|
+
/**
|
|
28
|
+
* Menu item type
|
|
29
|
+
*/
|
|
30
|
+
type?: "item" | "divider" | "label";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Label text for the menu item
|
|
34
|
+
*/
|
|
35
|
+
label?: string | ReactNode;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Icon element to display before the label
|
|
39
|
+
*/
|
|
40
|
+
icon?: ReactNode;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Click handler for menu items
|
|
44
|
+
*/
|
|
45
|
+
onClick?: () => void;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Href for navigation menu items
|
|
49
|
+
*/
|
|
50
|
+
href?: string;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Color for the menu item (e.g., "red" for danger actions)
|
|
54
|
+
*/
|
|
55
|
+
color?: string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Nested submenu items
|
|
59
|
+
*/
|
|
60
|
+
children?: ActionMenuItem[];
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Whether the menu item is active
|
|
64
|
+
*/
|
|
65
|
+
active?: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ActionMenuConfig {
|
|
69
|
+
/**
|
|
70
|
+
* Array of menu items to display
|
|
71
|
+
*/
|
|
72
|
+
items: ActionMenuItem[];
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Menu position relative to the button
|
|
76
|
+
*/
|
|
77
|
+
position?:
|
|
78
|
+
| "bottom"
|
|
79
|
+
| "bottom-start"
|
|
80
|
+
| "bottom-end"
|
|
81
|
+
| "top"
|
|
82
|
+
| "top-start"
|
|
83
|
+
| "top-end"
|
|
84
|
+
| "left"
|
|
85
|
+
| "right";
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Menu width
|
|
89
|
+
*/
|
|
90
|
+
width?: number | string;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Menu shadow
|
|
94
|
+
*/
|
|
95
|
+
shadow?: "xs" | "sm" | "md" | "lg" | "xl";
|
|
96
|
+
|
|
97
|
+
on?: "hover" | "click";
|
|
98
|
+
|
|
99
|
+
targetProps?: MenuTargetProps;
|
|
100
|
+
menuProps?: MenuProps;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface ActionCommonProps extends ButtonProps {
|
|
104
|
+
children?: ReactNode;
|
|
105
|
+
textVisibleFrom?: "xs" | "sm" | "md" | "lg" | "xl";
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Tooltip to display on hover. Can be a string for simple tooltips
|
|
109
|
+
* or a TooltipProps object for advanced configuration.
|
|
110
|
+
*/
|
|
111
|
+
tooltip?: string | TooltipProps;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Menu configuration. When provided, the action will display a dropdown menu.
|
|
115
|
+
*/
|
|
116
|
+
menu?: ActionMenuConfig;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* If set, a confirmation dialog will be shown before performing the action.
|
|
120
|
+
* If `true`, a default title and message will be used.
|
|
121
|
+
* If a string, it will be used as the message with a default title.
|
|
122
|
+
* If an object, it can contain `title` and `message` properties to customize the dialog.
|
|
123
|
+
*/
|
|
124
|
+
confirm?: boolean | string | { title?: string; message: string };
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Icon to display on the left side of the button.
|
|
128
|
+
* If no children are provided, the button will be styled as an icon-only button.
|
|
129
|
+
*/
|
|
130
|
+
icon?: ReactNode;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Additional props to pass to the ThemeIcon wrapping the icon.
|
|
134
|
+
*/
|
|
135
|
+
themeIconProps?: ThemeIconProps;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export type ActionProps = ActionCommonProps &
|
|
139
|
+
(
|
|
140
|
+
| ActionNavigationButtonProps
|
|
141
|
+
| ActionClickButtonProps
|
|
142
|
+
| ActionSubmitButtonProps
|
|
143
|
+
| ActionHookButtonProps
|
|
144
|
+
| {}
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
// Helper function to render menu items recursively
|
|
150
|
+
const ActionMenuItem = (props: {
|
|
151
|
+
item: ActionMenuItem;
|
|
152
|
+
index: number;
|
|
153
|
+
}): ReactNode => {
|
|
154
|
+
const { item, index } = props;
|
|
155
|
+
|
|
156
|
+
const router = useRouter();
|
|
157
|
+
const action = useAction(
|
|
158
|
+
{
|
|
159
|
+
handler: async (e: any) => {
|
|
160
|
+
await item.onClick?.();
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
[item.onClick],
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Render divider
|
|
167
|
+
if (item.type === "divider") {
|
|
168
|
+
return <Menu.Divider key={index} />;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Render label
|
|
172
|
+
if (item.type === "label") {
|
|
173
|
+
return <Menu.Label key={index}>{item.label}</Menu.Label>;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Render submenu if it has children
|
|
177
|
+
if (item.children && item.children.length > 0) {
|
|
178
|
+
return (
|
|
179
|
+
<Menu key={index} trigger="hover" position="right-start" offset={2}>
|
|
180
|
+
<Menu.Target>
|
|
181
|
+
<Menu.Item
|
|
182
|
+
leftSection={item.icon}
|
|
183
|
+
rightSection={<IconChevronRight size={14} />}
|
|
184
|
+
>
|
|
185
|
+
{item.label}
|
|
186
|
+
</Menu.Item>
|
|
187
|
+
</Menu.Target>
|
|
188
|
+
<Menu.Dropdown>
|
|
189
|
+
{item.children.map((child, childIndex) => (
|
|
190
|
+
<ActionMenuItem item={child} index={childIndex} key={childIndex} />
|
|
191
|
+
))}
|
|
192
|
+
</Menu.Dropdown>
|
|
193
|
+
</Menu>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const menuItemProps: MenuItemProps & ButtonHTMLAttributes<unknown> = {};
|
|
198
|
+
if (props.item.onClick) {
|
|
199
|
+
menuItemProps.onClick = action.run;
|
|
200
|
+
} else if (props.item.href) {
|
|
201
|
+
Object.assign(menuItemProps, router.anchor(props.item.href));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// render regular menu item
|
|
205
|
+
return (
|
|
206
|
+
<Menu.Item
|
|
207
|
+
key={index}
|
|
208
|
+
leftSection={item.icon}
|
|
209
|
+
onClick={item.onClick}
|
|
210
|
+
color={item.color}
|
|
211
|
+
rightSection={
|
|
212
|
+
item.active ? (
|
|
213
|
+
<ThemeIcon size={"xs"} variant={"transparent"}>
|
|
214
|
+
<IconCheck />
|
|
215
|
+
</ThemeIcon>
|
|
216
|
+
) : undefined
|
|
217
|
+
}
|
|
218
|
+
{...menuItemProps}
|
|
219
|
+
>
|
|
220
|
+
{item.label}
|
|
221
|
+
</Menu.Item>
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const ActionButton = (_props: ActionProps) => {
|
|
226
|
+
const props = { variant: "subtle", ..._props };
|
|
227
|
+
const { tooltip, menu, icon, ...restProps } = props;
|
|
228
|
+
|
|
229
|
+
if (props.icon) {
|
|
230
|
+
const icon = (
|
|
231
|
+
<ThemeIcon
|
|
232
|
+
w={24} // TODO: make size configurable
|
|
233
|
+
variant={"transparent"}
|
|
234
|
+
size={"sm"}
|
|
235
|
+
c={"var(--mantine-color-text)"}
|
|
236
|
+
{...props.themeIconProps}
|
|
237
|
+
>
|
|
238
|
+
{props.icon}
|
|
239
|
+
</ThemeIcon>
|
|
240
|
+
);
|
|
241
|
+
if (!props.children) {
|
|
242
|
+
restProps.children = icon;
|
|
243
|
+
restProps.p ??= "xs";
|
|
244
|
+
} else {
|
|
245
|
+
restProps.leftSection = icon;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (props.leftSection && !props.children) {
|
|
250
|
+
restProps.className ??= "mantine-Action-iconOnly";
|
|
251
|
+
restProps.p ??= "xs";
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (props.textVisibleFrom) {
|
|
255
|
+
const { children, textVisibleFrom, leftSection, ...rest } = restProps;
|
|
256
|
+
return (
|
|
257
|
+
<>
|
|
258
|
+
<Flex w={"100%"} visibleFrom={textVisibleFrom}>
|
|
259
|
+
<ActionButton
|
|
260
|
+
flex={1}
|
|
261
|
+
{...rest}
|
|
262
|
+
leftSection={leftSection}
|
|
263
|
+
tooltip={tooltip}
|
|
264
|
+
menu={menu}
|
|
265
|
+
>
|
|
266
|
+
{children}
|
|
267
|
+
</ActionButton>
|
|
268
|
+
</Flex>
|
|
269
|
+
<Flex w={"100%"} hiddenFrom={textVisibleFrom}>
|
|
270
|
+
<ActionButton px={"xs"} {...rest} tooltip={tooltip} menu={menu}>
|
|
271
|
+
{leftSection}
|
|
272
|
+
</ActionButton>
|
|
273
|
+
</Flex>
|
|
274
|
+
</>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const renderAction = () => {
|
|
279
|
+
if ("href" in restProps && restProps.href) {
|
|
280
|
+
if (restProps.href.startsWith("http")) {
|
|
281
|
+
return (
|
|
282
|
+
<ActionHrefButton {...restProps} href={restProps.href}>
|
|
283
|
+
{restProps.children}
|
|
284
|
+
</ActionHrefButton>
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
return (
|
|
288
|
+
<ActionNavigationButton {...restProps} href={restProps.href}>
|
|
289
|
+
{restProps.children}
|
|
290
|
+
</ActionNavigationButton>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
delete (restProps as any).classNameActive;
|
|
295
|
+
delete (restProps as any).variantActive;
|
|
296
|
+
|
|
297
|
+
if ("action" in restProps && restProps.action) {
|
|
298
|
+
return (
|
|
299
|
+
<ActionHookButton {...restProps} action={restProps.action}>
|
|
300
|
+
{restProps.children}
|
|
301
|
+
</ActionHookButton>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if ("onClick" in restProps && restProps.onClick) {
|
|
306
|
+
return (
|
|
307
|
+
<ActionClickButton {...restProps} onClick={restProps.onClick}>
|
|
308
|
+
{restProps.children}
|
|
309
|
+
</ActionClickButton>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if ("form" in restProps && restProps.form) {
|
|
314
|
+
return (
|
|
315
|
+
<ActionSubmitButton {...restProps} form={restProps.form}>
|
|
316
|
+
{restProps.children}
|
|
317
|
+
</ActionSubmitButton>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return <Button {...(restProps as any)}>{restProps.children}</Button>;
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
let actionElement = renderAction();
|
|
325
|
+
|
|
326
|
+
// wrap with Menu if provided
|
|
327
|
+
if (menu) {
|
|
328
|
+
actionElement = (
|
|
329
|
+
<Menu
|
|
330
|
+
position={menu.position || "bottom-start"}
|
|
331
|
+
width={menu.width || 200}
|
|
332
|
+
shadow={menu.shadow || "md"}
|
|
333
|
+
trigger={menu.on === "hover" ? "hover" : "click"}
|
|
334
|
+
{...menu.menuProps}
|
|
335
|
+
>
|
|
336
|
+
<Menu.Target {...menu.targetProps}>{actionElement}</Menu.Target>
|
|
337
|
+
<Menu.Dropdown>
|
|
338
|
+
{menu.items.map((item, index) => (
|
|
339
|
+
<ActionMenuItem item={item} index={index} key={index} />
|
|
340
|
+
))}
|
|
341
|
+
</Menu.Dropdown>
|
|
342
|
+
</Menu>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Wrap with Tooltip if provided
|
|
347
|
+
if (tooltip) {
|
|
348
|
+
const tooltipProps: TooltipProps =
|
|
349
|
+
typeof tooltip === "string"
|
|
350
|
+
? { label: tooltip, children: actionElement }
|
|
351
|
+
: { ...tooltip, children: actionElement };
|
|
352
|
+
|
|
353
|
+
return <Tooltip {...tooltipProps} />;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return actionElement;
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
export default ActionButton;
|
|
360
|
+
|
|
361
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
// Action Submit
|
|
364
|
+
|
|
365
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
366
|
+
|
|
367
|
+
export interface ActionSubmitButtonProps extends ButtonProps {
|
|
368
|
+
form: FormModel<any>;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Action button that submits a form with loading and disabled state handling.
|
|
373
|
+
*/
|
|
374
|
+
const ActionSubmitButton = (props: ActionSubmitButtonProps) => {
|
|
375
|
+
const { form, ...buttonProps } = props;
|
|
376
|
+
const state = useFormState(form);
|
|
377
|
+
return (
|
|
378
|
+
<Button
|
|
379
|
+
{...buttonProps}
|
|
380
|
+
loading={state.loading}
|
|
381
|
+
disabled={state.loading}
|
|
382
|
+
type={"submit"}
|
|
383
|
+
>
|
|
384
|
+
{props.children}
|
|
385
|
+
</Button>
|
|
386
|
+
);
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
390
|
+
|
|
391
|
+
// Action with useAction Hook
|
|
392
|
+
|
|
393
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
394
|
+
|
|
395
|
+
export interface ActionHookButtonProps extends ButtonProps {
|
|
396
|
+
action: UseActionReturn<any[], any>;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Action button that integrates with useAction hook return value.
|
|
401
|
+
* Automatically handles loading state and executes the action on click.
|
|
402
|
+
*
|
|
403
|
+
* @example
|
|
404
|
+
* ```tsx
|
|
405
|
+
* const saveAction = useAction({
|
|
406
|
+
* handler: async (data) => {
|
|
407
|
+
* await api.save(data);
|
|
408
|
+
* }
|
|
409
|
+
* }, []);
|
|
410
|
+
*
|
|
411
|
+
* <ActionButton action={saveAction}>
|
|
412
|
+
* Save
|
|
413
|
+
* </ActionButton>
|
|
414
|
+
* ```
|
|
415
|
+
*/
|
|
416
|
+
const ActionHookButton = (props: ActionHookButtonProps) => {
|
|
417
|
+
const { action, ...buttonProps } = props;
|
|
418
|
+
|
|
419
|
+
return (
|
|
420
|
+
<Button
|
|
421
|
+
{...buttonProps}
|
|
422
|
+
disabled={action.loading || props.disabled}
|
|
423
|
+
loading={action.loading}
|
|
424
|
+
onClick={() => action.run()}
|
|
425
|
+
>
|
|
426
|
+
{props.children}
|
|
427
|
+
</Button>
|
|
428
|
+
);
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
432
|
+
|
|
433
|
+
// Action Click
|
|
434
|
+
|
|
435
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
436
|
+
|
|
437
|
+
export interface ActionClickButtonProps extends ButtonProps {
|
|
438
|
+
onClick: (e: any) => any;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Basic action button that handles click events with loading and error handling.
|
|
443
|
+
*
|
|
444
|
+
* @example
|
|
445
|
+
* ```tsx
|
|
446
|
+
* <ActionButton onClick={() => api.doSomething()}>
|
|
447
|
+
* Do Something
|
|
448
|
+
* </ActionButton>
|
|
449
|
+
* ```
|
|
450
|
+
*/
|
|
451
|
+
const ActionClickButton = (props: ActionClickButtonProps) => {
|
|
452
|
+
const action = useAction(
|
|
453
|
+
{
|
|
454
|
+
handler: async (e: any) => {
|
|
455
|
+
await props.onClick(e);
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
[props.onClick],
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
return (
|
|
462
|
+
<Button
|
|
463
|
+
{...props}
|
|
464
|
+
disabled={action.loading || props.disabled}
|
|
465
|
+
loading={action.loading}
|
|
466
|
+
onClick={action.run}
|
|
467
|
+
>
|
|
468
|
+
{props.children}
|
|
469
|
+
</Button>
|
|
470
|
+
);
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
474
|
+
|
|
475
|
+
// Action Navigation
|
|
476
|
+
|
|
477
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
478
|
+
|
|
479
|
+
export interface ActionNavigationButtonProps extends ButtonProps {
|
|
480
|
+
href: string;
|
|
481
|
+
active?: Partial<UseActiveOptions> | false;
|
|
482
|
+
routerGoOptions?: RouterGoOptions;
|
|
483
|
+
classNameActive?: string;
|
|
484
|
+
variantActive?: ButtonProps["variant"];
|
|
485
|
+
target?: string;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Action for navigation with active state support.
|
|
490
|
+
*/
|
|
491
|
+
const ActionNavigationButton = (props: ActionNavigationButtonProps) => {
|
|
492
|
+
const {
|
|
493
|
+
active: options,
|
|
494
|
+
classNameActive,
|
|
495
|
+
variantActive,
|
|
496
|
+
routerGoOptions,
|
|
497
|
+
...buttonProps
|
|
498
|
+
} = props;
|
|
499
|
+
const router = useRouter();
|
|
500
|
+
const { isPending, isActive } = useActive(
|
|
501
|
+
options ? { href: props.href, ...options } : { href: props.href },
|
|
502
|
+
);
|
|
503
|
+
const anchorProps = router.anchor(props.href, routerGoOptions);
|
|
504
|
+
|
|
505
|
+
const className = buttonProps.className || "";
|
|
506
|
+
if (isActive && options !== false && classNameActive) {
|
|
507
|
+
buttonProps.className = `${className} ${classNameActive}`.trim();
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return (
|
|
511
|
+
<Button
|
|
512
|
+
component={"a"}
|
|
513
|
+
loading={isPending}
|
|
514
|
+
{...buttonProps}
|
|
515
|
+
{...anchorProps}
|
|
516
|
+
variant={
|
|
517
|
+
isActive && options !== false
|
|
518
|
+
? (variantActive ?? "filled")
|
|
519
|
+
: (buttonProps.variant ?? "subtle")
|
|
520
|
+
}
|
|
521
|
+
>
|
|
522
|
+
{props.children}
|
|
523
|
+
</Button>
|
|
524
|
+
);
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
const ActionHrefButton = (props: ActionNavigationButtonProps) => {
|
|
528
|
+
const {
|
|
529
|
+
active: options,
|
|
530
|
+
classNameActive,
|
|
531
|
+
variantActive,
|
|
532
|
+
routerGoOptions,
|
|
533
|
+
target,
|
|
534
|
+
...buttonProps
|
|
535
|
+
} = props;
|
|
536
|
+
|
|
537
|
+
return (
|
|
538
|
+
<Button component={"a"} target={target} {...buttonProps}>
|
|
539
|
+
{props.children}
|
|
540
|
+
</Button>
|
|
541
|
+
);
|
|
542
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useStore } from "@alepha/react";
|
|
2
|
+
import { Burger, type BurgerProps } from "@mantine/core";
|
|
3
|
+
|
|
4
|
+
export interface BurgerButtonProps extends BurgerProps {}
|
|
5
|
+
|
|
6
|
+
const BurgerButton = (props: BurgerButtonProps) => {
|
|
7
|
+
const [opened, setOpened] = useStore("alepha.ui.sidebar.opened");
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<Burger
|
|
11
|
+
opened={opened}
|
|
12
|
+
onClick={() => setOpened(!opened)}
|
|
13
|
+
hiddenFrom="sm"
|
|
14
|
+
size="sm"
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default BurgerButton;
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ActionIcon,
|
|
3
2
|
Flex,
|
|
3
|
+
type MantineBreakpoint,
|
|
4
4
|
SegmentedControl,
|
|
5
|
+
type SegmentedControlProps,
|
|
5
6
|
useComputedColorScheme,
|
|
6
7
|
useMantineColorScheme,
|
|
7
8
|
} from "@mantine/core";
|
|
8
9
|
import { IconMoon, IconSun } from "@tabler/icons-react";
|
|
9
10
|
import { useEffect, useState } from "react";
|
|
11
|
+
import ActionButton, { type ActionProps } from "./ActionButton.tsx";
|
|
10
12
|
|
|
11
13
|
export interface DarkModeButtonProps {
|
|
12
14
|
mode?: "minimal" | "segmented";
|
|
13
|
-
size?:
|
|
15
|
+
size?: MantineBreakpoint;
|
|
14
16
|
variant?:
|
|
15
17
|
| "filled"
|
|
16
18
|
| "light"
|
|
@@ -18,6 +20,11 @@ export interface DarkModeButtonProps {
|
|
|
18
20
|
| "default"
|
|
19
21
|
| "subtle"
|
|
20
22
|
| "transparent";
|
|
23
|
+
|
|
24
|
+
fullWidth?: boolean;
|
|
25
|
+
|
|
26
|
+
segmentedProps?: Partial<SegmentedControlProps>;
|
|
27
|
+
actionProps?: Partial<ActionProps>;
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
const DarkModeButton = (props: DarkModeButtonProps) => {
|
|
@@ -57,25 +64,31 @@ const DarkModeButton = (props: DarkModeButtonProps) => {
|
|
|
57
64
|
),
|
|
58
65
|
},
|
|
59
66
|
]}
|
|
67
|
+
w={props.fullWidth ? "100%" : undefined}
|
|
68
|
+
{...props.segmentedProps}
|
|
60
69
|
/>
|
|
61
70
|
);
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
return (
|
|
65
|
-
<
|
|
74
|
+
<ActionButton
|
|
66
75
|
onClick={toggleColorScheme}
|
|
67
|
-
variant={props.variant ?? "
|
|
68
|
-
size={props.size ?? "
|
|
76
|
+
variant={props.variant ?? "outline"}
|
|
77
|
+
size={props.size ?? "sm"}
|
|
69
78
|
aria-label="Toggle color scheme"
|
|
70
|
-
|
|
71
|
-
{
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
px={"xs"}
|
|
80
|
+
fullWidth={props.fullWidth ?? false}
|
|
81
|
+
icon={
|
|
82
|
+
colorScheme === "dark" ? (
|
|
83
|
+
<IconSun size={20} />
|
|
84
|
+
) : colorScheme === "light" ? (
|
|
85
|
+
<IconMoon size={20} />
|
|
86
|
+
) : (
|
|
87
|
+
<Flex h={20} w={20} />
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
{...props.actionProps}
|
|
91
|
+
/>
|
|
79
92
|
);
|
|
80
93
|
};
|
|
81
94
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useI18n } from "@alepha/react-i18n";
|
|
2
|
+
import { IconLanguage } from "@tabler/icons-react";
|
|
3
|
+
import ActionButton, { type ActionProps } from "./ActionButton.tsx";
|
|
4
|
+
|
|
5
|
+
export interface LanguageButtonProps {
|
|
6
|
+
languages?: string[];
|
|
7
|
+
actionProps?: ActionProps;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const LanguageButton = (props: LanguageButtonProps) => {
|
|
11
|
+
const i18n = useI18n();
|
|
12
|
+
return (
|
|
13
|
+
<ActionButton
|
|
14
|
+
icon={<IconLanguage />}
|
|
15
|
+
variant={"outline"}
|
|
16
|
+
menu={{
|
|
17
|
+
items: i18n.languages.map((lang) => ({
|
|
18
|
+
label: i18n.tr(lang),
|
|
19
|
+
onClick: () => i18n.setLang(lang),
|
|
20
|
+
active: i18n.lang === lang,
|
|
21
|
+
})),
|
|
22
|
+
}}
|
|
23
|
+
{...props.actionProps}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default LanguageButton;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Flex, Kbd, Text } from "@mantine/core";
|
|
2
|
+
import { spotlight } from "@mantine/spotlight";
|
|
3
|
+
import { IconSearch } from "@tabler/icons-react";
|
|
4
|
+
import ActionButton, { type ActionProps } from "./ActionButton.tsx";
|
|
5
|
+
|
|
6
|
+
export interface OmnibarButtonProps {
|
|
7
|
+
actionProps?: ActionProps;
|
|
8
|
+
collapsed?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const OmnibarButton = (props: OmnibarButtonProps) => {
|
|
12
|
+
return (
|
|
13
|
+
<ActionButton
|
|
14
|
+
variant={"outline"}
|
|
15
|
+
miw={256}
|
|
16
|
+
onClick={spotlight.open}
|
|
17
|
+
justify={"space-between"}
|
|
18
|
+
rightSection={<Kbd size={"sm"}>⌘+K</Kbd>}
|
|
19
|
+
radius={"md"}
|
|
20
|
+
{...props.actionProps}
|
|
21
|
+
>
|
|
22
|
+
<Flex align={"center"} gap={"xs"}>
|
|
23
|
+
<IconSearch size={16} color={"gray"} />
|
|
24
|
+
<Text size={"xs"} c={"dimmed"}>
|
|
25
|
+
Search...
|
|
26
|
+
</Text>
|
|
27
|
+
</Flex>
|
|
28
|
+
</ActionButton>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default OmnibarButton;
|