@motor-hero/ui-kit 0.5.1
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 +52 -0
- package/dist/components/auth-card.d.ts +10 -0
- package/dist/components/auth-card.d.ts.map +1 -0
- package/dist/components/confirm-dialog.d.ts +15 -0
- package/dist/components/confirm-dialog.d.ts.map +1 -0
- package/dist/components/data-table-wrapper.d.ts +16 -0
- package/dist/components/data-table-wrapper.d.ts.map +1 -0
- package/dist/components/empty-state.d.ts +11 -0
- package/dist/components/empty-state.d.ts.map +1 -0
- package/dist/components/form-dialog.d.ts +14 -0
- package/dist/components/form-dialog.d.ts.map +1 -0
- package/dist/components/form-field.d.ts +12 -0
- package/dist/components/form-field.d.ts.map +1 -0
- package/dist/components/mobile-card-list.d.ts +12 -0
- package/dist/components/mobile-card-list.d.ts.map +1 -0
- package/dist/components/mode-toggle.d.ts +2 -0
- package/dist/components/mode-toggle.d.ts.map +1 -0
- package/dist/components/page-header.d.ts +10 -0
- package/dist/components/page-header.d.ts.map +1 -0
- package/dist/components/pagination.d.ts +10 -0
- package/dist/components/pagination.d.ts.map +1 -0
- package/dist/components/responsive-data-view.d.ts +14 -0
- package/dist/components/responsive-data-view.d.ts.map +1 -0
- package/dist/components/search-input.d.ts +7 -0
- package/dist/components/search-input.d.ts.map +1 -0
- package/dist/components/stat-card.d.ts +11 -0
- package/dist/components/stat-card.d.ts.map +1 -0
- package/dist/components/status-dot.d.ts +8 -0
- package/dist/components/status-dot.d.ts.map +1 -0
- package/dist/components/table-skeleton.d.ts +7 -0
- package/dist/components/table-skeleton.d.ts.map +1 -0
- package/dist/components/theme-provider.d.ts +14 -0
- package/dist/components/theme-provider.d.ts.map +1 -0
- package/dist/components/toaster.d.ts +5 -0
- package/dist/components/toaster.d.ts.map +1 -0
- package/dist/hooks/use-disclosure.d.ts +8 -0
- package/dist/hooks/use-disclosure.d.ts.map +1 -0
- package/dist/hooks/use-toast.d.ts +5 -0
- package/dist/hooks/use-toast.d.ts.map +1 -0
- package/dist/index.cjs +620 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +561 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-error.d.ts +2 -0
- package/dist/lib/api-error.d.ts.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/styles.css +45 -0
- package/package.json +80 -0
- package/src/components/auth-card.tsx +25 -0
- package/src/components/confirm-dialog.tsx +58 -0
- package/src/components/data-table-wrapper.tsx +65 -0
- package/src/components/empty-state.tsx +22 -0
- package/src/components/form-dialog.tsx +48 -0
- package/src/components/form-field.tsx +26 -0
- package/src/components/mobile-card-list.tsx +54 -0
- package/src/components/mode-toggle.tsx +47 -0
- package/src/components/page-header.tsx +22 -0
- package/src/components/pagination.tsx +31 -0
- package/src/components/responsive-data-view.tsx +31 -0
- package/src/components/search-input.tsx +36 -0
- package/src/components/stat-card.tsx +35 -0
- package/src/components/status-dot.tsx +16 -0
- package/src/components/table-skeleton.tsx +20 -0
- package/src/components/theme-provider.tsx +69 -0
- package/src/components/toaster.tsx +31 -0
- package/src/hooks/use-disclosure.ts +9 -0
- package/src/hooks/use-toast.ts +30 -0
- package/src/index.ts +29 -0
- package/src/lib/api-error.ts +7 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles.css +45 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
interface TableSkeletonProps {
|
|
2
|
+
rows?: number
|
|
3
|
+
columns?: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function TableSkeleton({ rows = 5, columns = 4 }: TableSkeletonProps) {
|
|
7
|
+
return (
|
|
8
|
+
<>
|
|
9
|
+
{Array.from({ length: rows }).map((_, i) => (
|
|
10
|
+
<tr key={i} className="border-b transition-colors">
|
|
11
|
+
{Array.from({ length: columns }).map((_, j) => (
|
|
12
|
+
<td key={j} className="p-4 align-middle">
|
|
13
|
+
<div className="h-5 w-full animate-pulse rounded bg-muted" />
|
|
14
|
+
</td>
|
|
15
|
+
))}
|
|
16
|
+
</tr>
|
|
17
|
+
))}
|
|
18
|
+
</>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createContext, useContext, useEffect, useState } from "react"
|
|
2
|
+
|
|
3
|
+
type Theme = "dark" | "light" | "system"
|
|
4
|
+
|
|
5
|
+
type ThemeProviderProps = {
|
|
6
|
+
children: React.ReactNode
|
|
7
|
+
defaultTheme?: Theme
|
|
8
|
+
storageKey?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type ThemeProviderState = {
|
|
12
|
+
theme: Theme
|
|
13
|
+
setTheme: (theme: Theme) => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const initialState: ThemeProviderState = {
|
|
17
|
+
theme: "system",
|
|
18
|
+
setTheme: () => null,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const ThemeProviderContext = createContext<ThemeProviderState>(initialState)
|
|
22
|
+
|
|
23
|
+
export function ThemeProvider({
|
|
24
|
+
children,
|
|
25
|
+
defaultTheme = "system",
|
|
26
|
+
storageKey = "ui-theme",
|
|
27
|
+
...props
|
|
28
|
+
}: ThemeProviderProps) {
|
|
29
|
+
const [theme, setTheme] = useState<Theme>(
|
|
30
|
+
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const root = window.document.documentElement
|
|
35
|
+
root.classList.remove("light", "dark")
|
|
36
|
+
if (theme === "system") {
|
|
37
|
+
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
38
|
+
? "dark"
|
|
39
|
+
: "light"
|
|
40
|
+
root.classList.add(systemTheme)
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
root.classList.add(theme)
|
|
44
|
+
}, [theme])
|
|
45
|
+
|
|
46
|
+
const value = {
|
|
47
|
+
theme,
|
|
48
|
+
setTheme: (theme: Theme) => {
|
|
49
|
+
localStorage.setItem(storageKey, theme)
|
|
50
|
+
setTheme(theme)
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<ThemeProviderContext.Provider {...props} value={value}>
|
|
56
|
+
{children}
|
|
57
|
+
</ThemeProviderContext.Provider>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function useTheme() {
|
|
62
|
+
const context = useContext(ThemeProviderContext)
|
|
63
|
+
if (context === undefined) {
|
|
64
|
+
throw new Error("useTheme must be used within a ThemeProvider")
|
|
65
|
+
}
|
|
66
|
+
return context
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type { Theme, ThemeProviderProps, ThemeProviderState }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useTheme } from "./theme-provider"
|
|
2
|
+
import { Toaster as Sonner } from "sonner"
|
|
3
|
+
|
|
4
|
+
type ToasterProps = React.ComponentProps<typeof Sonner>
|
|
5
|
+
|
|
6
|
+
export function Toaster(props: ToasterProps) {
|
|
7
|
+
const { theme = "system" } = useTheme()
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<Sonner
|
|
11
|
+
theme={theme as ToasterProps["theme"]}
|
|
12
|
+
className="toaster group"
|
|
13
|
+
toastOptions={{
|
|
14
|
+
classNames: {
|
|
15
|
+
toast:
|
|
16
|
+
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-md",
|
|
17
|
+
description: "group-[.toast]:text-muted-foreground",
|
|
18
|
+
actionButton:
|
|
19
|
+
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
|
20
|
+
cancelButton:
|
|
21
|
+
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
|
22
|
+
success:
|
|
23
|
+
"group-[.toaster]:!bg-background group-[.toaster]:!text-foreground group-[.toaster]:!border-success/40",
|
|
24
|
+
error:
|
|
25
|
+
"group-[.toaster]:!bg-background group-[.toaster]:!text-foreground group-[.toaster]:!border-destructive/40",
|
|
26
|
+
},
|
|
27
|
+
}}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { useCallback, useState } from "react"
|
|
2
|
+
|
|
3
|
+
export function useDisclosure(initial = false) {
|
|
4
|
+
const [open, setOpen] = useState(initial)
|
|
5
|
+
const onOpen = useCallback(() => setOpen(true), [])
|
|
6
|
+
const onClose = useCallback(() => setOpen(false), [])
|
|
7
|
+
const onToggle = useCallback(() => setOpen((v) => !v), [])
|
|
8
|
+
return { open, onOpen, onClose, onToggle, setOpen }
|
|
9
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { toast } from "sonner"
|
|
2
|
+
import { useCallback } from "react"
|
|
3
|
+
|
|
4
|
+
type ToastStatus = "success" | "error" | "info" | "warning"
|
|
5
|
+
|
|
6
|
+
export function useCustomToast() {
|
|
7
|
+
const showToast = useCallback(
|
|
8
|
+
(title: string, description?: string, status: ToastStatus = "success") => {
|
|
9
|
+
switch (status) {
|
|
10
|
+
case "success":
|
|
11
|
+
toast.success(title, { description })
|
|
12
|
+
break
|
|
13
|
+
case "error":
|
|
14
|
+
toast.error(title, { description })
|
|
15
|
+
break
|
|
16
|
+
case "info":
|
|
17
|
+
toast.info(title, { description })
|
|
18
|
+
break
|
|
19
|
+
case "warning":
|
|
20
|
+
toast.warning(title, { description })
|
|
21
|
+
break
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
[],
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
return showToast
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { toast }
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Theme
|
|
2
|
+
export { ThemeProvider, useTheme } from "./components/theme-provider"
|
|
3
|
+
export type { Theme, ThemeProviderProps, ThemeProviderState } from "./components/theme-provider"
|
|
4
|
+
|
|
5
|
+
// Components
|
|
6
|
+
export { ModeToggle } from "./components/mode-toggle"
|
|
7
|
+
export { EmptyState } from "./components/empty-state"
|
|
8
|
+
export { ConfirmDialog } from "./components/confirm-dialog"
|
|
9
|
+
export { PageHeader } from "./components/page-header"
|
|
10
|
+
export { StatusDot } from "./components/status-dot"
|
|
11
|
+
export { FormField } from "./components/form-field"
|
|
12
|
+
export { FormDialogLayout } from "./components/form-dialog"
|
|
13
|
+
export { AuthCard } from "./components/auth-card"
|
|
14
|
+
export { Pagination } from "./components/pagination"
|
|
15
|
+
export { TableSkeleton } from "./components/table-skeleton"
|
|
16
|
+
export { SearchInput } from "./components/search-input"
|
|
17
|
+
export { StatCard } from "./components/stat-card"
|
|
18
|
+
export { DataTableWrapper } from "./components/data-table-wrapper"
|
|
19
|
+
export { MobileCardList } from "./components/mobile-card-list"
|
|
20
|
+
export { ResponsiveDataView } from "./components/responsive-data-view"
|
|
21
|
+
export { Toaster } from "./components/toaster"
|
|
22
|
+
|
|
23
|
+
// Utilities
|
|
24
|
+
export { cn } from "./lib/utils"
|
|
25
|
+
export { extractApiError } from "./lib/api-error"
|
|
26
|
+
|
|
27
|
+
// Hooks
|
|
28
|
+
export { useDisclosure } from "./hooks/use-disclosure"
|
|
29
|
+
export { useCustomToast, toast } from "./hooks/use-toast"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function extractApiError(err: any, fallbackMessage = "Ocorreu um erro inesperado."): string {
|
|
2
|
+
const detail = err?.body?.detail || err?.message
|
|
3
|
+
if (Array.isArray(detail) && detail.length > 0) {
|
|
4
|
+
return detail[0].msg
|
|
5
|
+
}
|
|
6
|
+
return detail || fallbackMessage
|
|
7
|
+
}
|
package/src/lib/utils.ts
ADDED
package/src/styles.css
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* @motor-hero/ui-kit - Zinc theme tokens */
|
|
2
|
+
:root {
|
|
3
|
+
--background: 0 0% 100%;
|
|
4
|
+
--foreground: 240 10% 3.9%;
|
|
5
|
+
--card: 0 0% 100%;
|
|
6
|
+
--card-foreground: 240 10% 3.9%;
|
|
7
|
+
--popover: 0 0% 100%;
|
|
8
|
+
--popover-foreground: 240 10% 3.9%;
|
|
9
|
+
--primary: 240 5.9% 10%;
|
|
10
|
+
--primary-foreground: 0 0% 98%;
|
|
11
|
+
--secondary: 240 4.8% 95.9%;
|
|
12
|
+
--secondary-foreground: 240 5.9% 10%;
|
|
13
|
+
--muted: 240 4.8% 95.9%;
|
|
14
|
+
--muted-foreground: 240 3.8% 46.1%;
|
|
15
|
+
--accent: 240 4.8% 95.9%;
|
|
16
|
+
--accent-foreground: 240 5.9% 10%;
|
|
17
|
+
--destructive: 0 84.2% 60.2%;
|
|
18
|
+
--destructive-foreground: 0 0% 98%;
|
|
19
|
+
--border: 240 5.9% 90%;
|
|
20
|
+
--input: 240 5.9% 90%;
|
|
21
|
+
--ring: 240 5.9% 10%;
|
|
22
|
+
--radius: 0.5rem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.dark {
|
|
26
|
+
--background: 240 10% 3.9%;
|
|
27
|
+
--foreground: 0 0% 98%;
|
|
28
|
+
--card: 240 10% 3.9%;
|
|
29
|
+
--card-foreground: 0 0% 98%;
|
|
30
|
+
--popover: 240 10% 3.9%;
|
|
31
|
+
--popover-foreground: 0 0% 98%;
|
|
32
|
+
--primary: 0 0% 98%;
|
|
33
|
+
--primary-foreground: 240 5.9% 10%;
|
|
34
|
+
--secondary: 240 3.7% 15.9%;
|
|
35
|
+
--secondary-foreground: 0 0% 98%;
|
|
36
|
+
--muted: 240 3.7% 15.9%;
|
|
37
|
+
--muted-foreground: 240 5% 64.9%;
|
|
38
|
+
--accent: 240 3.7% 15.9%;
|
|
39
|
+
--accent-foreground: 0 0% 98%;
|
|
40
|
+
--destructive: 0 62.8% 30.6%;
|
|
41
|
+
--destructive-foreground: 0 0% 98%;
|
|
42
|
+
--border: 240 3.7% 15.9%;
|
|
43
|
+
--input: 240 3.7% 15.9%;
|
|
44
|
+
--ring: 240 4.9% 83.9%;
|
|
45
|
+
}
|