@carefully-built/cli 0.1.0 → 0.1.2
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 +148 -7
- package/dist/index.mjs +71 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
- package/registry/ui/avatar/manifest.json +33 -0
- package/registry/ui/avatar/primitives/avatar.tsx +64 -0
- package/registry/ui/avatar/utils/cn.ts +6 -0
- package/registry/ui/button/manifest.json +24 -5
- package/registry/ui/button/utils/cn.ts +6 -0
- package/registry/ui/calendar/manifest.json +35 -0
- package/registry/ui/calendar/primitives/button.tsx +89 -0
- package/registry/ui/calendar/primitives/calendar.tsx +68 -0
- package/registry/ui/calendar/utils/cn.ts +6 -0
- package/registry/ui/card/manifest.json +36 -0
- package/registry/ui/card/primitives/card.tsx +80 -0
- package/registry/ui/card/utils/cn.ts +6 -0
- package/registry/ui/chip/manifest.json +36 -0
- package/registry/ui/chip/primitives/chip-utils.ts +10 -0
- package/registry/ui/chip/primitives/chip.tsx +74 -0
- package/registry/ui/chip/utils/cn.ts +6 -0
- package/registry/ui/chip-utils/manifest.json +33 -0
- package/registry/ui/chip-utils/primitives/chip-utils.ts +10 -0
- package/registry/ui/chip-utils/utils/cn.ts +6 -0
- package/registry/ui/date-display/manifest.json +33 -0
- package/registry/ui/date-display/utils/cn.ts +6 -0
- package/registry/ui/date-display/utils/date-display.ts +61 -0
- package/registry/ui/dialog/manifest.json +43 -0
- package/registry/ui/dialog/primitives/button.tsx +89 -0
- package/registry/ui/dialog/primitives/dialog.tsx +147 -0
- package/registry/ui/dialog/utils/cn.ts +6 -0
- package/registry/ui/display-date/manifest.json +36 -0
- package/registry/ui/display-date/primitives/display-date.tsx +20 -0
- package/registry/ui/display-date/utils/cn.ts +6 -0
- package/registry/ui/display-date/utils/date-display.ts +61 -0
- package/registry/ui/drawer/manifest.json +37 -0
- package/registry/ui/drawer/primitives/drawer.tsx +99 -0
- package/registry/ui/drawer/utils/cn.ts +6 -0
- package/registry/ui/dropdown-menu/manifest.json +37 -0
- package/registry/ui/dropdown-menu/primitives/dropdown-menu.tsx +140 -0
- package/registry/ui/dropdown-menu/utils/cn.ts +6 -0
- package/registry/ui/empty-state/empty-state/collection-empty-state.ts +29 -0
- package/registry/ui/empty-state/empty-state/empty-state-card.tsx +72 -0
- package/registry/ui/empty-state/empty-state/index.ts +8 -0
- package/registry/ui/empty-state/empty-state/initial-empty-state.tsx +36 -0
- package/registry/ui/empty-state/empty-state/no-results-state.tsx +20 -0
- package/registry/ui/empty-state/manifest.json +63 -0
- package/registry/ui/empty-state/primitives/button.tsx +89 -0
- package/registry/ui/empty-state/primitives/card.tsx +80 -0
- package/registry/ui/empty-state/utils/cn.ts +6 -0
- package/registry/ui/error-page/error-page/error-code.tsx +16 -0
- package/registry/ui/error-page/error-page/error-page-content.ts +75 -0
- package/registry/ui/error-page/error-page/index.ts +19 -0
- package/registry/ui/error-page/error-page/posthog-error-capture.ts +83 -0
- package/registry/ui/error-page/error-page/saas-error-page.tsx +146 -0
- package/registry/ui/error-page/manifest.json +64 -0
- package/registry/ui/error-page/primitives/button.tsx +89 -0
- package/registry/ui/error-page/utils/cn.ts +6 -0
- package/registry/ui/field-detail-row/manifest.json +32 -0
- package/registry/ui/field-detail-row/primitives/field-detail-row.tsx +28 -0
- package/registry/ui/field-detail-row/utils/cn.ts +6 -0
- package/registry/ui/file-dropzone/manifest.json +35 -0
- package/registry/ui/file-dropzone/primitives/button.tsx +89 -0
- package/registry/ui/file-dropzone/primitives/file-dropzone.tsx +236 -0
- package/registry/ui/file-dropzone/utils/cn.ts +6 -0
- package/registry/ui/help-info-button/manifest.json +72 -0
- package/registry/ui/help-info-button/overlays/responsive-sheet.footer.tsx +88 -0
- package/registry/ui/help-info-button/overlays/responsive-sheet.layouts.tsx +207 -0
- package/registry/ui/help-info-button/overlays/responsive-sheet.shortcuts.ts +103 -0
- package/registry/ui/help-info-button/overlays/responsive-sheet.tsx +132 -0
- package/registry/ui/help-info-button/primitives/button.tsx +89 -0
- package/registry/ui/help-info-button/primitives/drawer.tsx +99 -0
- package/registry/ui/help-info-button/primitives/help-info-button.tsx +63 -0
- package/registry/ui/help-info-button/primitives/keyboard-shortcut-hint.tsx +40 -0
- package/registry/ui/help-info-button/primitives/sheet.tsx +103 -0
- package/registry/ui/help-info-button/primitives/tooltip.tsx +57 -0
- package/registry/ui/help-info-button/utils/cn.ts +6 -0
- package/registry/ui/help-info-button/utils/use-media-query.ts +28 -0
- package/registry/ui/input/manifest.json +31 -0
- package/registry/ui/input/primitives/input.tsx +19 -0
- package/registry/ui/input/utils/cn.ts +6 -0
- package/registry/ui/keyboard-shortcut-hint/manifest.json +32 -0
- package/registry/ui/keyboard-shortcut-hint/primitives/keyboard-shortcut-hint.tsx +40 -0
- package/registry/ui/keyboard-shortcut-hint/utils/cn.ts +6 -0
- package/registry/ui/label/manifest.json +31 -0
- package/registry/ui/label/primitives/label.tsx +21 -0
- package/registry/ui/label/utils/cn.ts +6 -0
- package/registry/ui/pagination/manifest.json +36 -0
- package/registry/ui/pagination/primitives/button.tsx +89 -0
- package/registry/ui/pagination/primitives/pagination.tsx +143 -0
- package/registry/ui/pagination/utils/cn.ts +6 -0
- package/registry/ui/popover/manifest.json +33 -0
- package/registry/ui/popover/primitives/popover.tsx +46 -0
- package/registry/ui/popover/utils/cn.ts +6 -0
- package/registry/ui/responsive-sheet/manifest.json +66 -0
- package/registry/ui/responsive-sheet/overlays/responsive-sheet.footer.tsx +88 -0
- package/registry/ui/responsive-sheet/overlays/responsive-sheet.layouts.tsx +207 -0
- package/registry/ui/responsive-sheet/overlays/responsive-sheet.shortcuts.ts +103 -0
- package/registry/ui/responsive-sheet/overlays/responsive-sheet.tsx +132 -0
- package/registry/ui/responsive-sheet/primitives/button.tsx +89 -0
- package/registry/ui/responsive-sheet/primitives/drawer.tsx +99 -0
- package/registry/ui/responsive-sheet/primitives/keyboard-shortcut-hint.tsx +40 -0
- package/registry/ui/responsive-sheet/primitives/sheet.tsx +103 -0
- package/registry/ui/responsive-sheet/utils/cn.ts +6 -0
- package/registry/ui/responsive-sheet/utils/use-media-query.ts +28 -0
- package/registry/ui/responsive-sheet.footer/manifest.json +40 -0
- package/registry/ui/responsive-sheet.footer/overlays/responsive-sheet.footer.tsx +88 -0
- package/registry/ui/responsive-sheet.footer/primitives/button.tsx +89 -0
- package/registry/ui/responsive-sheet.footer/primitives/keyboard-shortcut-hint.tsx +40 -0
- package/registry/ui/responsive-sheet.footer/utils/cn.ts +6 -0
- package/registry/ui/responsive-sheet.shortcuts/manifest.json +34 -0
- package/registry/ui/responsive-sheet.shortcuts/overlays/responsive-sheet.shortcuts.ts +103 -0
- package/registry/ui/responsive-sheet.shortcuts/utils/cn.ts +6 -0
- package/registry/ui/scroll-fade-area/manifest.json +31 -0
- package/registry/ui/scroll-fade-area/primitives/scroll-fade-area.tsx +295 -0
- package/registry/ui/scroll-fade-area/utils/cn.ts +6 -0
- package/registry/ui/search/manifest.json +35 -0
- package/registry/ui/search/utils/cn.ts +6 -0
- package/registry/ui/search/utils/search.ts +227 -0
- package/registry/ui/searchable-select/manifest.json +48 -0
- package/registry/ui/searchable-select/primitives/input.tsx +19 -0
- package/registry/ui/searchable-select/search/searchable-select-position.ts +95 -0
- package/registry/ui/searchable-select/search/searchable-select.tsx +431 -0
- package/registry/ui/searchable-select/utils/cn.ts +6 -0
- package/registry/ui/searchable-select/utils/search.ts +227 -0
- package/registry/ui/searchable-select-position/manifest.json +32 -0
- package/registry/ui/searchable-select-position/search/searchable-select-position.ts +95 -0
- package/registry/ui/searchable-select-position/utils/cn.ts +6 -0
- package/registry/ui/segmented-toggle/manifest.json +41 -0
- package/registry/ui/segmented-toggle/primitives/scroll-fade-area.tsx +295 -0
- package/registry/ui/segmented-toggle/primitives/segmented-toggle.tsx +106 -0
- package/registry/ui/segmented-toggle/primitives/tabs.tsx +97 -0
- package/registry/ui/segmented-toggle/utils/cn.ts +6 -0
- package/registry/ui/select/manifest.json +37 -0
- package/registry/ui/select/primitives/select.tsx +142 -0
- package/registry/ui/select/utils/cn.ts +6 -0
- package/registry/ui/sheet/manifest.json +39 -0
- package/registry/ui/sheet/primitives/button.tsx +89 -0
- package/registry/ui/sheet/primitives/sheet.tsx +103 -0
- package/registry/ui/sheet/utils/cn.ts +6 -0
- package/registry/ui/skeleton/manifest.json +31 -0
- package/registry/ui/skeleton/primitives/skeleton.tsx +13 -0
- package/registry/ui/skeleton/utils/cn.ts +6 -0
- package/registry/ui/smart-table/manifest.json +115 -0
- package/registry/ui/smart-table/primitives/button.tsx +89 -0
- package/registry/ui/smart-table/primitives/card.tsx +80 -0
- package/registry/ui/smart-table/primitives/display-date.tsx +20 -0
- package/registry/ui/smart-table/primitives/pagination.tsx +143 -0
- package/registry/ui/smart-table/primitives/skeleton.tsx +13 -0
- package/registry/ui/smart-table/primitives/table.tsx +92 -0
- package/registry/ui/smart-table/primitives/tooltip.tsx +57 -0
- package/registry/ui/smart-table/smart-table/DesktopView.tsx +343 -0
- package/registry/ui/smart-table/smart-table/MobileView.tsx +170 -0
- package/registry/ui/smart-table/smart-table/SmartTable.tsx +85 -0
- package/registry/ui/smart-table/smart-table/SmartTableActions.tsx +71 -0
- package/registry/ui/smart-table/smart-table/TruncatedContent.tsx +147 -0
- package/registry/ui/smart-table/smart-table/index.ts +15 -0
- package/registry/ui/smart-table/smart-table/sorting.ts +148 -0
- package/registry/ui/smart-table/smart-table/truncated-content.utils.ts +22 -0
- package/registry/ui/smart-table/smart-table/types.ts +95 -0
- package/registry/ui/smart-table/smart-table/utils.ts +150 -0
- package/registry/ui/smart-table/utils/cn.ts +6 -0
- package/registry/ui/smart-table/utils/date-display.ts +61 -0
- package/registry/ui/smart-table/utils/use-media-query.ts +28 -0
- package/registry/ui/switch/manifest.json +31 -0
- package/registry/ui/switch/primitives/switch.tsx +31 -0
- package/registry/ui/switch/utils/cn.ts +6 -0
- package/registry/ui/table/manifest.json +38 -0
- package/registry/ui/table/primitives/table.tsx +92 -0
- package/registry/ui/table/utils/cn.ts +6 -0
- package/registry/ui/table-toolbar/manifest.json +93 -0
- package/registry/ui/table-toolbar/overlays/responsive-sheet.footer.tsx +88 -0
- package/registry/ui/table-toolbar/overlays/responsive-sheet.layouts.tsx +207 -0
- package/registry/ui/table-toolbar/overlays/responsive-sheet.shortcuts.ts +103 -0
- package/registry/ui/table-toolbar/overlays/responsive-sheet.tsx +132 -0
- package/registry/ui/table-toolbar/primitives/button.tsx +89 -0
- package/registry/ui/table-toolbar/primitives/drawer.tsx +99 -0
- package/registry/ui/table-toolbar/primitives/input.tsx +19 -0
- package/registry/ui/table-toolbar/primitives/keyboard-shortcut-hint.tsx +40 -0
- package/registry/ui/table-toolbar/primitives/sheet.tsx +103 -0
- package/registry/ui/table-toolbar/search/searchable-select-position.ts +95 -0
- package/registry/ui/table-toolbar/search/searchable-select.tsx +431 -0
- package/registry/ui/table-toolbar/table-toolbar/index.ts +9 -0
- package/registry/ui/table-toolbar/table-toolbar/table-toolbar.tsx +552 -0
- package/registry/ui/table-toolbar/utils/cn.ts +6 -0
- package/registry/ui/table-toolbar/utils/search.ts +227 -0
- package/registry/ui/table-toolbar/utils/use-media-query.ts +28 -0
- package/registry/ui/tabs/manifest.json +40 -0
- package/registry/ui/tabs/primitives/scroll-fade-area.tsx +295 -0
- package/registry/ui/tabs/primitives/tabs.tsx +97 -0
- package/registry/ui/tabs/utils/cn.ts +6 -0
- package/registry/ui/textarea/manifest.json +31 -0
- package/registry/ui/textarea/primitives/textarea.tsx +18 -0
- package/registry/ui/textarea/utils/cn.ts +6 -0
- package/registry/ui/tooltip/manifest.json +34 -0
- package/registry/ui/tooltip/primitives/tooltip.tsx +57 -0
- package/registry/ui/tooltip/utils/cn.ts +6 -0
- package/registry/ui/use-media-query/manifest.json +32 -0
- package/registry/ui/use-media-query/utils/cn.ts +6 -0
- package/registry/ui/use-media-query/utils/use-media-query.ts +28 -0
- package/registry/ui/user-picker/manifest.json +52 -0
- package/registry/ui/user-picker/primitives/avatar.tsx +64 -0
- package/registry/ui/user-picker/primitives/button.tsx +89 -0
- package/registry/ui/user-picker/primitives/input.tsx +19 -0
- package/registry/ui/user-picker/primitives/popover.tsx +46 -0
- package/registry/ui/user-picker/primitives/user-picker-utils.ts +113 -0
- package/registry/ui/user-picker/primitives/user-picker.tsx +226 -0
- package/registry/ui/user-picker/utils/cn.ts +6 -0
- package/registry/ui/user-picker-utils/manifest.json +38 -0
- package/registry/ui/user-picker-utils/primitives/user-picker-utils.ts +113 -0
- package/registry/ui/user-picker-utils/utils/cn.ts +6 -0
- package/registry/ui/button/cn.ts +0 -6
- /package/registry/ui/button/{button.tsx → primitives/button.tsx} +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Drawer as DrawerPrimitive } from 'vaul';
|
|
5
|
+
|
|
6
|
+
import { cn } from '@/lib/utils';
|
|
7
|
+
|
|
8
|
+
function Drawer({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
|
9
|
+
return <DrawerPrimitive.Root data-slot="drawer" {...props} />;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function DrawerPortal({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
|
|
13
|
+
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function DrawerOverlay({
|
|
17
|
+
className,
|
|
18
|
+
...props
|
|
19
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
|
|
20
|
+
return (
|
|
21
|
+
<DrawerPrimitive.Overlay
|
|
22
|
+
data-slot="drawer-overlay"
|
|
23
|
+
className={cn(
|
|
24
|
+
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/10 backdrop-blur-xs',
|
|
25
|
+
className,
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function DrawerContent({
|
|
33
|
+
className,
|
|
34
|
+
children,
|
|
35
|
+
...props
|
|
36
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Content>) {
|
|
37
|
+
return (
|
|
38
|
+
<DrawerPortal data-slot="drawer-portal">
|
|
39
|
+
<DrawerOverlay />
|
|
40
|
+
<DrawerPrimitive.Content
|
|
41
|
+
data-slot="drawer-content"
|
|
42
|
+
className={cn(
|
|
43
|
+
'bg-background group/drawer-content fixed z-50 flex h-auto flex-col overflow-visible text-sm data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-xl data-[vaul-drawer-direction=bottom]:border-t data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:rounded-r-xl data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:rounded-l-xl data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-xl data-[vaul-drawer-direction=top]:border-b data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm',
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...props}
|
|
47
|
+
>
|
|
48
|
+
<div className="bg-muted mx-auto mt-4 hidden h-1 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
|
|
49
|
+
{children}
|
|
50
|
+
</DrawerPrimitive.Content>
|
|
51
|
+
</DrawerPortal>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function DrawerHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
data-slot="drawer-header"
|
|
59
|
+
className={cn(
|
|
60
|
+
'flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-0.5 md:text-left',
|
|
61
|
+
className,
|
|
62
|
+
)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function DrawerTitle({ className, ...props }: React.ComponentProps<typeof DrawerPrimitive.Title>) {
|
|
69
|
+
return (
|
|
70
|
+
<DrawerPrimitive.Title
|
|
71
|
+
data-slot="drawer-title"
|
|
72
|
+
className={cn('text-foreground text-base font-medium', className)}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function DrawerDescription({
|
|
79
|
+
className,
|
|
80
|
+
...props
|
|
81
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
|
|
82
|
+
return (
|
|
83
|
+
<DrawerPrimitive.Description
|
|
84
|
+
data-slot="drawer-description"
|
|
85
|
+
className={cn('text-muted-foreground text-sm', className)}
|
|
86
|
+
{...props}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export {
|
|
92
|
+
Drawer,
|
|
93
|
+
DrawerContent,
|
|
94
|
+
DrawerDescription,
|
|
95
|
+
DrawerHeader,
|
|
96
|
+
DrawerOverlay,
|
|
97
|
+
DrawerPortal,
|
|
98
|
+
DrawerTitle,
|
|
99
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Command } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
import type { ReactNode } from 'react';
|
|
6
|
+
|
|
7
|
+
import { cn } from '@/lib/utils';
|
|
8
|
+
|
|
9
|
+
export function KeyboardKeycap({
|
|
10
|
+
children,
|
|
11
|
+
className,
|
|
12
|
+
}: {
|
|
13
|
+
readonly children: ReactNode;
|
|
14
|
+
readonly className?: string;
|
|
15
|
+
}): React.ReactElement {
|
|
16
|
+
return (
|
|
17
|
+
<span
|
|
18
|
+
className={cn(
|
|
19
|
+
'inline-flex h-4 min-w-4 items-center justify-center rounded-[4px] border px-1 text-[9px] font-semibold leading-none',
|
|
20
|
+
className,
|
|
21
|
+
)}
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
</span>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function ShortcutModifierKeycap({
|
|
29
|
+
modifierLabel,
|
|
30
|
+
className,
|
|
31
|
+
}: {
|
|
32
|
+
readonly modifierLabel: string;
|
|
33
|
+
readonly className?: string;
|
|
34
|
+
}): React.ReactElement {
|
|
35
|
+
return (
|
|
36
|
+
<KeyboardKeycap className={className}>
|
|
37
|
+
{modifierLabel === 'Cmd' ? <Command className="size-[10px]" /> : 'Ctrl'}
|
|
38
|
+
</KeyboardKeycap>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { X } from 'lucide-react';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import { Dialog as SheetPrimitive } from 'radix-ui';
|
|
6
|
+
|
|
7
|
+
import { Button } from '@/components/ui/button';
|
|
8
|
+
import { cn } from '@/lib/utils';
|
|
9
|
+
|
|
10
|
+
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
|
11
|
+
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
|
15
|
+
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function SheetOverlay({
|
|
19
|
+
className,
|
|
20
|
+
...props
|
|
21
|
+
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
|
|
22
|
+
return (
|
|
23
|
+
<SheetPrimitive.Overlay
|
|
24
|
+
data-slot="sheet-overlay"
|
|
25
|
+
className={cn(
|
|
26
|
+
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/10 backdrop-blur-xs duration-100 data-ending-style:opacity-0 data-starting-style:opacity-0',
|
|
27
|
+
className,
|
|
28
|
+
)}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function SheetContent({
|
|
35
|
+
className,
|
|
36
|
+
children,
|
|
37
|
+
side = 'right',
|
|
38
|
+
showCloseButton = true,
|
|
39
|
+
...props
|
|
40
|
+
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
|
41
|
+
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
42
|
+
showCloseButton?: boolean;
|
|
43
|
+
}) {
|
|
44
|
+
return (
|
|
45
|
+
<SheetPortal>
|
|
46
|
+
<SheetOverlay />
|
|
47
|
+
<SheetPrimitive.Content
|
|
48
|
+
data-slot="sheet-content"
|
|
49
|
+
data-side={side}
|
|
50
|
+
className={cn(
|
|
51
|
+
'bg-background data-open:animate-in data-closed:animate-out data-[side=right]:data-closed:slide-out-to-right-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=top]:data-closed:slide-out-to-top-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:fade-out-0 data-open:fade-in-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=bottom]:data-open:slide-in-from-bottom-10 fixed z-50 flex flex-col gap-4 overflow-visible bg-clip-padding text-sm shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:rounded-r-xl data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:rounded-l-xl data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm',
|
|
52
|
+
className,
|
|
53
|
+
)}
|
|
54
|
+
{...props}
|
|
55
|
+
>
|
|
56
|
+
{children}
|
|
57
|
+
{showCloseButton ? (
|
|
58
|
+
<SheetPrimitive.Close data-slot="sheet-close" asChild>
|
|
59
|
+
<Button variant="ghost" className="absolute top-3 right-3" size="icon-sm">
|
|
60
|
+
<X className="size-4" />
|
|
61
|
+
<span className="sr-only">Close</span>
|
|
62
|
+
</Button>
|
|
63
|
+
</SheetPrimitive.Close>
|
|
64
|
+
) : null}
|
|
65
|
+
</SheetPrimitive.Content>
|
|
66
|
+
</SheetPortal>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
71
|
+
return (
|
|
72
|
+
<div
|
|
73
|
+
data-slot="sheet-header"
|
|
74
|
+
className={cn('flex flex-col gap-0.5 p-4', className)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {
|
|
81
|
+
return (
|
|
82
|
+
<SheetPrimitive.Title
|
|
83
|
+
data-slot="sheet-title"
|
|
84
|
+
className={cn('text-foreground text-base font-medium', className)}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function SheetDescription({
|
|
91
|
+
className,
|
|
92
|
+
...props
|
|
93
|
+
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
|
|
94
|
+
return (
|
|
95
|
+
<SheetPrimitive.Description
|
|
96
|
+
data-slot="sheet-description"
|
|
97
|
+
className={cn('text-muted-foreground text-sm', className)}
|
|
98
|
+
{...props}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
export function useMediaQuery(query: string, defaultValue = false): boolean {
|
|
6
|
+
const [matches, setMatches] = useState(defaultValue);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const mediaQuery = window.matchMedia(query);
|
|
10
|
+
|
|
11
|
+
const handleChange = (): void => {
|
|
12
|
+
setMatches(mediaQuery.matches);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
handleChange();
|
|
16
|
+
mediaQuery.addEventListener('change', handleChange);
|
|
17
|
+
|
|
18
|
+
return () => {
|
|
19
|
+
mediaQuery.removeEventListener('change', handleChange);
|
|
20
|
+
};
|
|
21
|
+
}, [query]);
|
|
22
|
+
|
|
23
|
+
return matches;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useIsMobile(maxWidth = 767): boolean {
|
|
27
|
+
return useMediaQuery(`(max-width: ${String(maxWidth)}px)`);
|
|
28
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "responsive-sheet.footer",
|
|
3
|
+
"description": "Editable source registry entry for responsive-sheet.footer.",
|
|
4
|
+
"importPath": "@carefully-built/ui",
|
|
5
|
+
"exports": [
|
|
6
|
+
"DesktopConfirmShortcutHint",
|
|
7
|
+
"SheetActionFooter"
|
|
8
|
+
],
|
|
9
|
+
"dependencies": [
|
|
10
|
+
"class-variance-authority",
|
|
11
|
+
"clsx",
|
|
12
|
+
"tailwind-merge"
|
|
13
|
+
],
|
|
14
|
+
"peerDependencies": [
|
|
15
|
+
"react",
|
|
16
|
+
"react-dom",
|
|
17
|
+
"radix-ui",
|
|
18
|
+
"lucide-react",
|
|
19
|
+
"react-day-picker",
|
|
20
|
+
"vaul"
|
|
21
|
+
],
|
|
22
|
+
"files": [
|
|
23
|
+
{
|
|
24
|
+
"source": "overlays/responsive-sheet.footer.tsx",
|
|
25
|
+
"target": "components/ui/responsive-sheet.footer.tsx"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"source": "primitives/button.tsx",
|
|
29
|
+
"target": "components/ui/button.tsx"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"source": "primitives/keyboard-shortcut-hint.tsx",
|
|
33
|
+
"target": "components/ui/keyboard-shortcut-hint.tsx"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"source": "utils/cn.ts",
|
|
37
|
+
"target": "lib/utils.ts"
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { CornerDownLeft } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
import type { ReactNode } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Button } from '@/components/ui/button';
|
|
8
|
+
import { KeyboardKeycap, ShortcutModifierKeycap } from '@/components/ui/keyboard-shortcut-hint';
|
|
9
|
+
|
|
10
|
+
export function DesktopConfirmShortcutHint({
|
|
11
|
+
desktopModifierLabel,
|
|
12
|
+
}: {
|
|
13
|
+
readonly desktopModifierLabel: string;
|
|
14
|
+
}): React.ReactElement {
|
|
15
|
+
const keycapClassName = 'border-primary-foreground/20 bg-primary-foreground/10';
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<span className="text-primary-foreground/70 inline-flex items-center gap-1">
|
|
19
|
+
<ShortcutModifierKeycap modifierLabel={desktopModifierLabel} className={keycapClassName} />
|
|
20
|
+
<KeyboardKeycap className={keycapClassName}>
|
|
21
|
+
<CornerDownLeft className="size-[10px]" />
|
|
22
|
+
</KeyboardKeycap>
|
|
23
|
+
</span>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface SheetActionFooterProps {
|
|
28
|
+
readonly footer?: ReactNode;
|
|
29
|
+
readonly onCancel?: () => void;
|
|
30
|
+
readonly cancelLabel: ReactNode;
|
|
31
|
+
readonly onConfirm?: () => void;
|
|
32
|
+
readonly confirmLabel: ReactNode;
|
|
33
|
+
readonly confirmDisabled: boolean;
|
|
34
|
+
readonly confirmLoading: boolean;
|
|
35
|
+
readonly desktopConfirmShortcutEnabled?: boolean;
|
|
36
|
+
readonly desktopModifierLabel?: string | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function SheetActionFooter({
|
|
40
|
+
footer,
|
|
41
|
+
onCancel,
|
|
42
|
+
cancelLabel,
|
|
43
|
+
onConfirm,
|
|
44
|
+
confirmLabel,
|
|
45
|
+
confirmDisabled,
|
|
46
|
+
confirmLoading,
|
|
47
|
+
desktopConfirmShortcutEnabled = false,
|
|
48
|
+
desktopModifierLabel = null,
|
|
49
|
+
}: SheetActionFooterProps): React.ReactNode {
|
|
50
|
+
if (footer) return footer;
|
|
51
|
+
|
|
52
|
+
if (!onCancel && !onConfirm) return null;
|
|
53
|
+
|
|
54
|
+
const actionCount = Number(Boolean(onCancel)) + Number(Boolean(onConfirm));
|
|
55
|
+
const footerButtonClassName = actionCount === 1 ? 'w-full' : 'min-w-0 flex-1';
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div className="flex w-full flex-nowrap items-center gap-2">
|
|
59
|
+
{onCancel ? (
|
|
60
|
+
<Button
|
|
61
|
+
type="button"
|
|
62
|
+
variant="outline"
|
|
63
|
+
onClick={onCancel}
|
|
64
|
+
className={footerButtonClassName}
|
|
65
|
+
>
|
|
66
|
+
{cancelLabel}
|
|
67
|
+
</Button>
|
|
68
|
+
) : null}
|
|
69
|
+
{onConfirm ? (
|
|
70
|
+
<Button
|
|
71
|
+
type="button"
|
|
72
|
+
onClick={onConfirm}
|
|
73
|
+
disabled={confirmDisabled}
|
|
74
|
+
className={`${footerButtonClassName} relative`}
|
|
75
|
+
>
|
|
76
|
+
<span className="inline-flex w-full items-center justify-center">
|
|
77
|
+
<span>{confirmLoading ? 'Saving...' : confirmLabel}</span>
|
|
78
|
+
{desktopConfirmShortcutEnabled && desktopModifierLabel ? (
|
|
79
|
+
<span className="absolute top-1/2 right-2 -translate-y-1/2">
|
|
80
|
+
<DesktopConfirmShortcutHint desktopModifierLabel={desktopModifierLabel} />
|
|
81
|
+
</span>
|
|
82
|
+
) : null}
|
|
83
|
+
</span>
|
|
84
|
+
</Button>
|
|
85
|
+
) : null}
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva } from "class-variance-authority";
|
|
3
|
+
import { Slot } from "radix-ui";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
const buttonVariants = cva(
|
|
8
|
+
"group/button inline-flex shrink-0 cursor-pointer items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default:
|
|
13
|
+
"bg-primary text-primary-foreground hover:brightness-90 [a]:hover:bg-primary/80",
|
|
14
|
+
outline:
|
|
15
|
+
"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
16
|
+
secondary:
|
|
17
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
18
|
+
ghost:
|
|
19
|
+
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
20
|
+
destructive:
|
|
21
|
+
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
|
22
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
default:
|
|
26
|
+
"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
27
|
+
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
28
|
+
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
29
|
+
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
30
|
+
icon: "size-8",
|
|
31
|
+
"icon-xs":
|
|
32
|
+
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
|
33
|
+
"icon-sm":
|
|
34
|
+
"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
|
35
|
+
"icon-lg": "size-9",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
defaultVariants: {
|
|
39
|
+
variant: "default",
|
|
40
|
+
size: "default",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
type ButtonVariant =
|
|
46
|
+
| "default"
|
|
47
|
+
| "outline"
|
|
48
|
+
| "secondary"
|
|
49
|
+
| "ghost"
|
|
50
|
+
| "destructive"
|
|
51
|
+
| "link";
|
|
52
|
+
type ButtonSize =
|
|
53
|
+
| "default"
|
|
54
|
+
| "xs"
|
|
55
|
+
| "sm"
|
|
56
|
+
| "lg"
|
|
57
|
+
| "icon"
|
|
58
|
+
| "icon-xs"
|
|
59
|
+
| "icon-sm"
|
|
60
|
+
| "icon-lg";
|
|
61
|
+
|
|
62
|
+
interface ButtonProps extends React.ComponentProps<"button"> {
|
|
63
|
+
readonly asChild?: boolean;
|
|
64
|
+
readonly size?: ButtonSize;
|
|
65
|
+
readonly variant?: ButtonVariant;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function Button({
|
|
69
|
+
className,
|
|
70
|
+
variant = "default",
|
|
71
|
+
size = "default",
|
|
72
|
+
asChild = false,
|
|
73
|
+
...props
|
|
74
|
+
}: ButtonProps) {
|
|
75
|
+
const Comp = asChild ? Slot.Root : "button";
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<Comp
|
|
79
|
+
data-slot="button"
|
|
80
|
+
data-variant={variant}
|
|
81
|
+
data-size={size}
|
|
82
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { Button, buttonVariants };
|
|
89
|
+
export type { ButtonProps, ButtonSize, ButtonVariant };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Command } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
import type { ReactNode } from 'react';
|
|
6
|
+
|
|
7
|
+
import { cn } from '@/lib/utils';
|
|
8
|
+
|
|
9
|
+
export function KeyboardKeycap({
|
|
10
|
+
children,
|
|
11
|
+
className,
|
|
12
|
+
}: {
|
|
13
|
+
readonly children: ReactNode;
|
|
14
|
+
readonly className?: string;
|
|
15
|
+
}): React.ReactElement {
|
|
16
|
+
return (
|
|
17
|
+
<span
|
|
18
|
+
className={cn(
|
|
19
|
+
'inline-flex h-4 min-w-4 items-center justify-center rounded-[4px] border px-1 text-[9px] font-semibold leading-none',
|
|
20
|
+
className,
|
|
21
|
+
)}
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
</span>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function ShortcutModifierKeycap({
|
|
29
|
+
modifierLabel,
|
|
30
|
+
className,
|
|
31
|
+
}: {
|
|
32
|
+
readonly modifierLabel: string;
|
|
33
|
+
readonly className?: string;
|
|
34
|
+
}): React.ReactElement {
|
|
35
|
+
return (
|
|
36
|
+
<KeyboardKeycap className={className}>
|
|
37
|
+
{modifierLabel === 'Cmd' ? <Command className="size-[10px]" /> : 'Ctrl'}
|
|
38
|
+
</KeyboardKeycap>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "responsive-sheet.shortcuts",
|
|
3
|
+
"description": "Editable source registry entry for responsive-sheet.shortcuts.",
|
|
4
|
+
"importPath": "@carefully-built/ui",
|
|
5
|
+
"exports": [
|
|
6
|
+
"getDesktopShortcutModifierLabel",
|
|
7
|
+
"isAllowedConfirmShortcutEvent",
|
|
8
|
+
"useDesktopConfirmShortcut",
|
|
9
|
+
"useDesktopShortcutModifierLabel"
|
|
10
|
+
],
|
|
11
|
+
"dependencies": [
|
|
12
|
+
"class-variance-authority",
|
|
13
|
+
"clsx",
|
|
14
|
+
"tailwind-merge"
|
|
15
|
+
],
|
|
16
|
+
"peerDependencies": [
|
|
17
|
+
"react",
|
|
18
|
+
"react-dom",
|
|
19
|
+
"radix-ui",
|
|
20
|
+
"lucide-react",
|
|
21
|
+
"react-day-picker",
|
|
22
|
+
"vaul"
|
|
23
|
+
],
|
|
24
|
+
"files": [
|
|
25
|
+
{
|
|
26
|
+
"source": "overlays/responsive-sheet.shortcuts.ts",
|
|
27
|
+
"target": "components/ui/responsive-sheet.shortcuts.ts"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"source": "utils/cn.ts",
|
|
31
|
+
"target": "lib/utils.ts"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
interface NavigatorWithUserAgentData extends Navigator {
|
|
4
|
+
readonly userAgentData?: {
|
|
5
|
+
readonly platform?: string;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getDesktopShortcutModifierLabel(): string {
|
|
10
|
+
if (typeof navigator === "undefined") {
|
|
11
|
+
return "Ctrl";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const navigatorWithUserAgentData = navigator as NavigatorWithUserAgentData;
|
|
15
|
+
const platform =
|
|
16
|
+
navigatorWithUserAgentData.userAgentData?.platform ?? navigator.userAgent;
|
|
17
|
+
|
|
18
|
+
return /Mac|iPhone|iPad|iPod/i.test(platform) ? "Cmd" : "Ctrl";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isAllowedConfirmShortcutEvent(
|
|
22
|
+
event: KeyboardEvent,
|
|
23
|
+
desktopModifierLabel: string,
|
|
24
|
+
): boolean {
|
|
25
|
+
if (event.key !== "Enter" || event.repeat || event.isComposing) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const expectsMetaKey = desktopModifierLabel === "Cmd";
|
|
30
|
+
const usedExpectedModifier = expectsMetaKey ? event.metaKey : event.ctrlKey;
|
|
31
|
+
const usedOtherModifier = expectsMetaKey ? event.ctrlKey : event.metaKey;
|
|
32
|
+
const usedShiftModifier = event.shiftKey;
|
|
33
|
+
const usedAltModifier = event.altKey;
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
!usedExpectedModifier ||
|
|
37
|
+
usedOtherModifier ||
|
|
38
|
+
usedShiftModifier ||
|
|
39
|
+
usedAltModifier
|
|
40
|
+
) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function useDesktopShortcutModifierLabel(
|
|
48
|
+
enabled: boolean,
|
|
49
|
+
): string | null {
|
|
50
|
+
const [desktopModifierLabel, setDesktopModifierLabel] = useState<
|
|
51
|
+
string | null
|
|
52
|
+
>(null);
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!enabled) {
|
|
56
|
+
setDesktopModifierLabel(null);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setDesktopModifierLabel(getDesktopShortcutModifierLabel());
|
|
61
|
+
}, [enabled]);
|
|
62
|
+
|
|
63
|
+
return desktopModifierLabel;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface UseDesktopConfirmShortcutOptions {
|
|
67
|
+
readonly open: boolean;
|
|
68
|
+
readonly enabled: boolean;
|
|
69
|
+
readonly confirmDisabled: boolean;
|
|
70
|
+
readonly confirmLoading: boolean;
|
|
71
|
+
readonly onConfirm?: () => void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function useDesktopConfirmShortcut({
|
|
75
|
+
open,
|
|
76
|
+
enabled,
|
|
77
|
+
confirmDisabled,
|
|
78
|
+
confirmLoading,
|
|
79
|
+
onConfirm,
|
|
80
|
+
}: UseDesktopConfirmShortcutOptions): void {
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!open || !enabled || !onConfirm || confirmDisabled || confirmLoading) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const desktopModifierLabel = getDesktopShortcutModifierLabel();
|
|
87
|
+
|
|
88
|
+
const handleKeyDown = (event: KeyboardEvent): void => {
|
|
89
|
+
if (!isAllowedConfirmShortcutEvent(event, desktopModifierLabel)) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
onConfirm();
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
98
|
+
|
|
99
|
+
return () => {
|
|
100
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
101
|
+
};
|
|
102
|
+
}, [confirmDisabled, confirmLoading, enabled, onConfirm, open]);
|
|
103
|
+
}
|