@motor-hero/ui-kit 0.25.0 → 0.26.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/dist/components/base-dialog.d.ts +38 -0
- package/dist/components/base-dialog.d.ts.map +1 -0
- package/dist/components/form-dialog.d.ts +8 -26
- package/dist/components/form-dialog.d.ts.map +1 -1
- package/dist/index.cjs +277 -270
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +276 -270
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/base-dialog.tsx +116 -0
- package/src/components/color-picker.tsx +4 -4
- package/src/components/form-dialog.tsx +20 -108
- package/src/index.ts +3 -1
package/package.json
CHANGED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as Dialog from "@radix-ui/react-dialog"
|
|
2
|
+
import { X } from "lucide-react"
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import type { ReactNode } from "react"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../lib/utils"
|
|
7
|
+
|
|
8
|
+
const sizeClasses = {
|
|
9
|
+
sm: "sm:max-w-sm",
|
|
10
|
+
md: "sm:max-w-md",
|
|
11
|
+
lg: "sm:max-w-lg",
|
|
12
|
+
xl: "sm:max-w-xl",
|
|
13
|
+
"2xl": "sm:max-w-2xl",
|
|
14
|
+
} as const
|
|
15
|
+
|
|
16
|
+
export type BaseDialogSize = keyof typeof sizeClasses
|
|
17
|
+
|
|
18
|
+
export interface BaseDialogProps {
|
|
19
|
+
open: boolean
|
|
20
|
+
onOpenChange: (open: boolean) => void
|
|
21
|
+
title: string
|
|
22
|
+
description?: ReactNode
|
|
23
|
+
children: ReactNode
|
|
24
|
+
footer?: ReactNode
|
|
25
|
+
size?: BaseDialogSize
|
|
26
|
+
className?: string
|
|
27
|
+
/**
|
|
28
|
+
* Advanced: wraps the dialog's inner content (header + body + footer) inside
|
|
29
|
+
* Content. Used by FormDialog to inject a <form>. Defaults to identity.
|
|
30
|
+
*/
|
|
31
|
+
contentWrapper?: (content: ReactNode) => ReactNode
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Standardized, responsive dialog shell:
|
|
36
|
+
* - mobile: bottom sheet (full width, up to 92vh tall)
|
|
37
|
+
* - desktop: centered dialog (up to 90vh tall)
|
|
38
|
+
* Header and footer stay fixed; only the body scrolls.
|
|
39
|
+
*
|
|
40
|
+
* Also fixes the known Radix bug where `body { pointer-events: none }`
|
|
41
|
+
* can persist after closing a dialog that contained a Select/Popover,
|
|
42
|
+
* which would otherwise make the rest of the page unclickable.
|
|
43
|
+
*/
|
|
44
|
+
export function BaseDialog({
|
|
45
|
+
open,
|
|
46
|
+
onOpenChange,
|
|
47
|
+
title,
|
|
48
|
+
description,
|
|
49
|
+
children,
|
|
50
|
+
footer,
|
|
51
|
+
size = "lg",
|
|
52
|
+
className,
|
|
53
|
+
contentWrapper = (content) => content,
|
|
54
|
+
}: BaseDialogProps) {
|
|
55
|
+
React.useEffect(() => {
|
|
56
|
+
if (open) return
|
|
57
|
+
const id = window.setTimeout(() => {
|
|
58
|
+
if (document.body.style.pointerEvents === "none") {
|
|
59
|
+
document.body.style.pointerEvents = ""
|
|
60
|
+
}
|
|
61
|
+
}, 0)
|
|
62
|
+
return () => window.clearTimeout(id)
|
|
63
|
+
}, [open])
|
|
64
|
+
|
|
65
|
+
const body = (
|
|
66
|
+
<>
|
|
67
|
+
<div className="flex shrink-0 items-start justify-between gap-4 border-b px-6 py-3">
|
|
68
|
+
<div className="min-w-0 space-y-1">
|
|
69
|
+
<Dialog.Title className="text-lg font-semibold leading-none tracking-tight">
|
|
70
|
+
{title}
|
|
71
|
+
</Dialog.Title>
|
|
72
|
+
{description && (
|
|
73
|
+
<Dialog.Description className="text-sm text-muted-foreground">
|
|
74
|
+
{description}
|
|
75
|
+
</Dialog.Description>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
<Dialog.Close
|
|
79
|
+
className="-mr-1 shrink-0 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
80
|
+
aria-label="Fechar"
|
|
81
|
+
>
|
|
82
|
+
<X className="h-5 w-5" />
|
|
83
|
+
</Dialog.Close>
|
|
84
|
+
</div>
|
|
85
|
+
<div className="min-h-0 flex-1 overflow-y-auto px-6 py-4">{children}</div>
|
|
86
|
+
{footer && (
|
|
87
|
+
<div className="flex shrink-0 flex-col-reverse gap-2 border-t px-6 py-3 sm:flex-row sm:justify-end">
|
|
88
|
+
{footer}
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
</>
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<Dialog.Root open={open} onOpenChange={onOpenChange}>
|
|
96
|
+
<Dialog.Portal>
|
|
97
|
+
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" />
|
|
98
|
+
<Dialog.Content
|
|
99
|
+
onCloseAutoFocus={() => {
|
|
100
|
+
document.body.style.pointerEvents = ""
|
|
101
|
+
}}
|
|
102
|
+
className={cn(
|
|
103
|
+
"fixed z-50 flex max-h-[92vh] flex-col bg-background shadow-lg outline-none",
|
|
104
|
+
"inset-x-0 bottom-0 rounded-t-2xl",
|
|
105
|
+
"sm:inset-x-auto sm:bottom-auto sm:left-1/2 sm:top-1/2 sm:max-h-[92vh] sm:w-full sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-lg",
|
|
106
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
107
|
+
sizeClasses[size],
|
|
108
|
+
className,
|
|
109
|
+
)}
|
|
110
|
+
>
|
|
111
|
+
{contentWrapper(body)}
|
|
112
|
+
</Dialog.Content>
|
|
113
|
+
</Dialog.Portal>
|
|
114
|
+
</Dialog.Root>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
@@ -39,7 +39,7 @@ export function ColorPicker({
|
|
|
39
39
|
const color = value || ""
|
|
40
40
|
|
|
41
41
|
return (
|
|
42
|
-
<PopoverPrimitive.Root open={open} onOpenChange={setOpen}
|
|
42
|
+
<PopoverPrimitive.Root open={open} onOpenChange={setOpen}>
|
|
43
43
|
<PopoverPrimitive.Trigger asChild>
|
|
44
44
|
<button
|
|
45
45
|
type="button"
|
|
@@ -63,9 +63,9 @@ export function ColorPicker({
|
|
|
63
63
|
color
|
|
64
64
|
? { backgroundColor: color }
|
|
65
65
|
: {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
backgroundImage:
|
|
67
|
+
"linear-gradient(45deg,var(--color-muted) 25%,transparent 25%,transparent 75%,var(--color-muted) 75%),linear-gradient(45deg,var(--color-muted) 25%,transparent 25%,transparent 75%,var(--color-muted) 75%)",
|
|
68
|
+
}
|
|
69
69
|
}
|
|
70
70
|
/>
|
|
71
71
|
<span className={cn("truncate", !color && "text-muted-foreground")}>
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import * as Dialog from "@radix-ui/react-dialog"
|
|
2
|
-
import { X } from "lucide-react"
|
|
3
1
|
import * as React from "react"
|
|
4
2
|
import type { ReactNode } from "react"
|
|
5
3
|
|
|
6
|
-
import {
|
|
4
|
+
import { BaseDialog, type BaseDialogProps, type BaseDialogSize } from "./base-dialog"
|
|
7
5
|
|
|
8
6
|
interface FormDialogLayoutProps {
|
|
9
7
|
title: string
|
|
@@ -52,118 +50,32 @@ export function FormDialogLayout({
|
|
|
52
50
|
)
|
|
53
51
|
}
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
sm: "sm:max-w-sm",
|
|
57
|
-
md: "sm:max-w-md",
|
|
58
|
-
lg: "sm:max-w-lg",
|
|
59
|
-
xl: "sm:max-w-xl",
|
|
60
|
-
"2xl": "sm:max-w-2xl",
|
|
61
|
-
} as const
|
|
53
|
+
export type FormDialogSize = BaseDialogSize
|
|
62
54
|
|
|
63
|
-
export
|
|
64
|
-
|
|
65
|
-
interface FormDialogProps {
|
|
66
|
-
open: boolean
|
|
67
|
-
onOpenChange: (open: boolean) => void
|
|
68
|
-
title: string
|
|
69
|
-
description?: ReactNode
|
|
70
|
-
children: ReactNode
|
|
71
|
-
footer?: ReactNode
|
|
55
|
+
export interface FormDialogProps extends Omit<BaseDialogProps, "contentWrapper"> {
|
|
72
56
|
/** When provided, the dialog renders a <form> wrapping body + footer. */
|
|
73
57
|
onSubmit?: (e: React.FormEvent<HTMLFormElement>) => void
|
|
74
|
-
size?: FormDialogSize
|
|
75
|
-
className?: string
|
|
76
58
|
}
|
|
77
59
|
|
|
78
60
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* Also fixes the known Radix bug where `body { pointer-events: none }`
|
|
85
|
-
* can persist after closing a dialog that contained a Select/Popover,
|
|
86
|
-
* which would otherwise make the rest of the page unclickable.
|
|
61
|
+
* Form-oriented specialization of {@link BaseDialog}: when `onSubmit` is
|
|
62
|
+
* provided, the body and footer are wrapped in a <form> (so footer submit
|
|
63
|
+
* buttons and Enter trigger submission). Without `onSubmit` it behaves like a
|
|
64
|
+
* plain BaseDialog.
|
|
87
65
|
*/
|
|
88
|
-
export function FormDialog({
|
|
89
|
-
open,
|
|
90
|
-
onOpenChange,
|
|
91
|
-
title,
|
|
92
|
-
description,
|
|
93
|
-
children,
|
|
94
|
-
footer,
|
|
95
|
-
onSubmit,
|
|
96
|
-
size = "lg",
|
|
97
|
-
className,
|
|
98
|
-
}: FormDialogProps) {
|
|
99
|
-
React.useEffect(() => {
|
|
100
|
-
if (open) return
|
|
101
|
-
const id = window.setTimeout(() => {
|
|
102
|
-
if (document.body.style.pointerEvents === "none") {
|
|
103
|
-
document.body.style.pointerEvents = ""
|
|
104
|
-
}
|
|
105
|
-
}, 0)
|
|
106
|
-
return () => window.clearTimeout(id)
|
|
107
|
-
}, [open])
|
|
108
|
-
|
|
109
|
-
const body = (
|
|
110
|
-
<>
|
|
111
|
-
<div className="flex shrink-0 items-start justify-between gap-4 border-b px-6 py-3">
|
|
112
|
-
<div className="min-w-0 space-y-1">
|
|
113
|
-
<Dialog.Title className="text-lg font-semibold leading-none tracking-tight">
|
|
114
|
-
{title}
|
|
115
|
-
</Dialog.Title>
|
|
116
|
-
{description && (
|
|
117
|
-
<Dialog.Description className="text-sm text-muted-foreground">
|
|
118
|
-
{description}
|
|
119
|
-
</Dialog.Description>
|
|
120
|
-
)}
|
|
121
|
-
</div>
|
|
122
|
-
<Dialog.Close
|
|
123
|
-
className="-mr-1 shrink-0 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
124
|
-
aria-label="Fechar"
|
|
125
|
-
>
|
|
126
|
-
<X className="h-5 w-5" />
|
|
127
|
-
</Dialog.Close>
|
|
128
|
-
</div>
|
|
129
|
-
<div className="min-h-0 flex-1 overflow-y-auto px-6 py-4">{children}</div>
|
|
130
|
-
{footer && (
|
|
131
|
-
<div className="flex shrink-0 flex-col-reverse gap-2 border-t px-6 py-3 sm:flex-row sm:justify-end">
|
|
132
|
-
{footer}
|
|
133
|
-
</div>
|
|
134
|
-
)}
|
|
135
|
-
</>
|
|
136
|
-
)
|
|
137
|
-
|
|
66
|
+
export function FormDialog({ onSubmit, ...rest }: FormDialogProps) {
|
|
138
67
|
return (
|
|
139
|
-
<
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
sizeClasses[size],
|
|
152
|
-
className,
|
|
153
|
-
)}
|
|
154
|
-
>
|
|
155
|
-
{onSubmit ? (
|
|
156
|
-
<form
|
|
157
|
-
onSubmit={onSubmit}
|
|
158
|
-
className="flex min-h-0 flex-1 flex-col"
|
|
159
|
-
>
|
|
160
|
-
{body}
|
|
161
|
-
</form>
|
|
162
|
-
) : (
|
|
163
|
-
body
|
|
164
|
-
)}
|
|
165
|
-
</Dialog.Content>
|
|
166
|
-
</Dialog.Portal>
|
|
167
|
-
</Dialog.Root>
|
|
68
|
+
<BaseDialog
|
|
69
|
+
{...rest}
|
|
70
|
+
contentWrapper={
|
|
71
|
+
onSubmit
|
|
72
|
+
? (content) => (
|
|
73
|
+
<form onSubmit={onSubmit} className="flex min-h-0 flex-1 flex-col">
|
|
74
|
+
{content}
|
|
75
|
+
</form>
|
|
76
|
+
)
|
|
77
|
+
: undefined
|
|
78
|
+
}
|
|
79
|
+
/>
|
|
168
80
|
)
|
|
169
81
|
}
|
package/src/index.ts
CHANGED
|
@@ -19,8 +19,10 @@ export { StatusBadge } from "./components/status-badge"
|
|
|
19
19
|
export type { StatusBadgeVariant } from "./components/status-badge"
|
|
20
20
|
export { ProgressBar } from "./components/progress-bar"
|
|
21
21
|
export { FormField } from "./components/form-field"
|
|
22
|
+
export { BaseDialog } from "./components/base-dialog"
|
|
23
|
+
export type { BaseDialogProps, BaseDialogSize } from "./components/base-dialog"
|
|
22
24
|
export { FormDialogLayout, FormDialog } from "./components/form-dialog"
|
|
23
|
-
export type { FormDialogSize } from "./components/form-dialog"
|
|
25
|
+
export type { FormDialogProps, FormDialogSize } from "./components/form-dialog"
|
|
24
26
|
export { AuthCard } from "./components/auth-card"
|
|
25
27
|
export { Pagination } from "./components/pagination"
|
|
26
28
|
export { TableSkeleton } from "./components/table-skeleton"
|