@alepha/ui 0.11.2 → 0.11.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/AlephaMantineProvider-CtIV-8MV.mjs +150 -0
  2. package/dist/AlephaMantineProvider-CtIV-8MV.mjs.map +1 -0
  3. package/dist/AlephaMantineProvider-D-vu9aCD.mjs +3 -0
  4. package/dist/{index.d.ts → index.d.mts} +290 -226
  5. package/dist/index.d.mts.map +1 -0
  6. package/dist/{index.js → index.mjs} +651 -730
  7. package/dist/index.mjs.map +1 -0
  8. package/package.json +14 -12
  9. package/src/RootRouter.ts +1 -1
  10. package/src/components/buttons/ActionButton.tsx +542 -0
  11. package/src/components/buttons/BurgerButton.tsx +20 -0
  12. package/src/components/{DarkModeButton.tsx → buttons/DarkModeButton.tsx} +27 -14
  13. package/src/components/buttons/LanguageButton.tsx +28 -0
  14. package/src/components/buttons/OmnibarButton.tsx +32 -0
  15. package/src/components/buttons/ToggleSidebarButton.tsx +28 -0
  16. package/src/components/dialogs/AlertDialog.tsx +10 -10
  17. package/src/components/dialogs/ConfirmDialog.tsx +18 -18
  18. package/src/components/dialogs/PromptDialog.tsx +5 -3
  19. package/src/components/{Control.tsx → form/Control.tsx} +6 -3
  20. package/src/components/{ControlDate.tsx → form/ControlDate.tsx} +4 -1
  21. package/src/components/{ControlSelect.tsx → form/ControlSelect.tsx} +4 -1
  22. package/src/components/{TypeForm.tsx → form/TypeForm.tsx} +8 -6
  23. package/src/components/layout/AdminShell.tsx +97 -0
  24. package/src/components/{AlephaMantineProvider.tsx → layout/AlephaMantineProvider.tsx} +30 -10
  25. package/src/components/layout/AppBar.tsx +133 -0
  26. package/src/components/layout/Omnibar.tsx +43 -0
  27. package/src/components/layout/Sidebar.tsx +410 -0
  28. package/src/components/table/DataTable.tsx +63 -0
  29. package/src/constants/ui.ts +8 -0
  30. package/src/index.ts +89 -24
  31. package/src/services/DialogService.tsx +13 -32
  32. package/src/services/ToastService.tsx +16 -4
  33. package/src/utils/parseInput.ts +1 -1
  34. package/dist/AlephaMantineProvider-DDbIijPF.js +0 -96
  35. package/dist/AlephaMantineProvider-DDbIijPF.js.map +0 -1
  36. package/dist/AlephaMantineProvider-pOu8hOzK.js +0 -3
  37. package/dist/index.d.ts.map +0 -1
  38. package/dist/index.js.map +0 -1
  39. package/src/components/Action.tsx +0 -345
  40. package/src/components/DataTable.css +0 -199
  41. package/src/components/DataTable.tsx +0 -724
  42. package/src/components/Omnibar.tsx +0 -77
  43. package/src/components/Sidebar.css +0 -217
  44. package/src/components/Sidebar.tsx +0 -255
@@ -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?: string | number;
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
- <ActionIcon
74
+ <ActionButton
66
75
  onClick={toggleColorScheme}
67
- variant={props.variant ?? "default"}
68
- size={props.size ?? "lg"}
76
+ variant={props.variant ?? "outline"}
77
+ size={props.size ?? "sm"}
69
78
  aria-label="Toggle color scheme"
70
- >
71
- {colorScheme === "dark" ? (
72
- <IconSun size={20} />
73
- ) : colorScheme === "light" ? (
74
- <IconMoon size={20} />
75
- ) : (
76
- <Flex h={20} />
77
- )}
78
- </ActionIcon>
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;