tembro 4.2.1 → 6.0.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/CHANGELOG.md +29 -0
- package/dist/components/charts/progress-circle.d.ts +12 -3
- package/dist/components/data-table/data-table.d.ts +2 -2
- package/dist/components/feedback/index.d.ts +0 -2
- package/dist/components/layout/section.d.ts +1 -8
- package/dist/components/patterns/index.d.ts +0 -8
- package/dist/components/patterns/public.d.ts +0 -8
- package/dist/components/ui/hover-card/index.cjs +1 -0
- package/dist/components/ui/hover-card/index.d.ts +11 -0
- package/dist/components/ui/hover-card/index.js +1 -0
- package/dist/components/ui/menubar/index.cjs +1 -0
- package/dist/components/ui/menubar/index.d.ts +42 -0
- package/dist/components/ui/menubar/index.js +1 -0
- package/dist/components/ui/navigation-menu/index.cjs +1 -0
- package/dist/components/ui/navigation-menu/index.d.ts +20 -0
- package/dist/components/ui/navigation-menu/index.js +1 -0
- package/dist/components/ui/number-field/index.cjs +1 -0
- package/dist/components/ui/number-field/index.d.ts +18 -0
- package/dist/components/ui/number-field/index.js +1 -0
- package/dist/components/ui/toggle-group/index.cjs +1 -0
- package/dist/components/ui/toggle-group/index.d.ts +13 -0
- package/dist/components/ui/toggle-group/index.js +1 -0
- package/dist/components/ui/toolbar/index.cjs +1 -0
- package/dist/components/ui/toolbar/index.d.ts +14 -0
- package/dist/components/ui/toolbar/index.js +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/src/components/charts/progress-circle.cjs +1 -1
- package/dist/src/components/charts/progress-circle.js +57 -3
- package/dist/src/components/data-table/data-table.cjs +1 -1
- package/dist/src/components/data-table/data-table.js +102 -100
- package/dist/src/components/feedback/index.cjs +1 -1
- package/dist/src/components/feedback/index.js +2 -4
- package/dist/src/components/layout/index.cjs +1 -1
- package/dist/src/components/layout/index.js +3 -3
- package/dist/src/components/layout/public.cjs +1 -1
- package/dist/src/components/layout/public.js +3 -3
- package/dist/src/components/layout/section.cjs +1 -1
- package/dist/src/components/layout/section.js +2 -26
- package/dist/src/components/patterns/index.cjs +1 -1
- package/dist/src/components/patterns/index.js +2 -10
- package/dist/src/components/patterns/public.cjs +1 -1
- package/dist/src/components/patterns/public.js +2 -10
- package/dist/src/components/ui/hover-card/index.cjs +1 -0
- package/dist/src/components/ui/hover-card/index.js +50 -0
- package/dist/src/components/ui/menubar/index.cjs +1 -0
- package/dist/src/components/ui/menubar/index.js +132 -0
- package/dist/src/components/ui/navigation-menu/index.cjs +1 -0
- package/dist/src/components/ui/navigation-menu/index.js +73 -0
- package/dist/src/components/ui/number-field/index.cjs +1 -0
- package/dist/src/components/ui/number-field/index.js +73 -0
- package/dist/src/components/ui/toggle-group/index.cjs +1 -0
- package/dist/src/components/ui/toggle-group/index.js +32 -0
- package/dist/src/components/ui/toolbar/index.cjs +1 -0
- package/dist/src/components/ui/toolbar/index.js +50 -0
- package/dist/src/index.cjs +1 -1
- package/dist/src/index.js +92 -95
- package/dist/src/public-component-surface.cjs +1 -1
- package/dist/src/public-component-surface.js +31 -42
- package/package.json +1 -1
- package/packages/cli/dist/index.cjs +112 -112
- package/packages/cli/vendor/src/components/charts/progress-circle.tsx +78 -3
- package/packages/cli/vendor/src/components/data-table/data-table.tsx +4 -4
- package/packages/cli/vendor/src/components/feedback/index.ts +0 -2
- package/packages/cli/vendor/src/components/layout/section.tsx +1 -28
- package/packages/cli/vendor/src/components/patterns/index.ts +0 -8
- package/packages/cli/vendor/src/components/patterns/public.ts +0 -8
- package/packages/cli/vendor/src/components/ui/hover-card/index.tsx +72 -0
- package/packages/cli/vendor/src/components/ui/menubar/index.tsx +140 -0
- package/packages/cli/vendor/src/components/ui/navigation-menu/index.tsx +100 -0
- package/packages/cli/vendor/src/components/ui/number-field/index.tsx +89 -0
- package/packages/cli/vendor/src/components/ui/toggle-group/index.tsx +72 -0
- package/packages/cli/vendor/src/components/ui/toolbar/index.tsx +67 -0
- package/packages/cli/vendor/src/index.ts +12 -5
- package/packages/cli/vendor/src/public-component-surface.ts +7 -10
- package/packages/cli/vendor/templates/showcase/src/showcase/data/registry.ts +14 -12
- package/packages/cli/vendor/templates/showcase/src/showcase/layout/HeroSection.tsx +2 -2
- package/packages/cli/vendor/templates/showcase/src/showcase/layout/WorkbenchSidebar.tsx +1 -1
- package/packages/cli/vendor/templates/showcase/src/showcase/sections/ComponentLivePreview.tsx +2 -2
- package/packages/cli/vendor/templates/showcase/src/showcase/sections/FormsSection.tsx +2 -2
- package/packages/cli/vendor/templates/styles/globals.css +5 -5
- package/registry.json +2630 -233
- package/dist/components/charts/progress-ring.cjs +0 -1
- package/dist/components/charts/progress-ring.d.ts +0 -13
- package/dist/components/charts/progress-ring.js +0 -1
- package/dist/components/feedback/loading-state.cjs +0 -1
- package/dist/components/feedback/loading-state.d.ts +0 -9
- package/dist/components/feedback/loading-state.js +0 -1
- package/dist/components/feedback/page-state.cjs +0 -1
- package/dist/components/feedback/page-state.d.ts +0 -18
- package/dist/components/feedback/page-state.js +0 -1
- package/dist/components/layout/page-container.cjs +0 -1
- package/dist/components/layout/page-container.d.ts +0 -7
- package/dist/components/layout/page-container.js +0 -1
- package/dist/components/layout/page-header.cjs +0 -1
- package/dist/components/layout/page-header.d.ts +0 -23
- package/dist/components/layout/page-header.js +0 -1
- package/dist/components/layout/section-header.cjs +0 -1
- package/dist/components/layout/section-header.d.ts +0 -14
- package/dist/components/layout/section-header.js +0 -1
- package/dist/components/layout/sidebar-nav.cjs +0 -1
- package/dist/components/layout/sidebar-nav.d.ts +0 -37
- package/dist/components/layout/sidebar-nav.js +0 -1
- package/dist/components/layout/sticky-footer-bar.cjs +0 -1
- package/dist/components/layout/sticky-footer-bar.d.ts +0 -7
- package/dist/components/layout/sticky-footer-bar.js +0 -1
- package/dist/components/patterns/bulk-action-bar.cjs +0 -1
- package/dist/components/patterns/bulk-action-bar.d.ts +0 -10
- package/dist/components/patterns/bulk-action-bar.js +0 -1
- package/dist/components/patterns/data-view.cjs +0 -1
- package/dist/components/patterns/data-view.d.ts +0 -17
- package/dist/components/patterns/data-view.js +0 -1
- package/dist/components/patterns/detail-layout.cjs +0 -1
- package/dist/components/patterns/detail-layout.d.ts +0 -16
- package/dist/components/patterns/detail-layout.js +0 -1
- package/dist/components/patterns/form-builder.cjs +0 -1
- package/dist/components/patterns/form-builder.d.ts +0 -110
- package/dist/components/patterns/form-builder.js +0 -1
- package/dist/components/patterns/page-toolbar.cjs +0 -1
- package/dist/components/patterns/page-toolbar.d.ts +0 -11
- package/dist/components/patterns/page-toolbar.js +0 -1
- package/dist/components/patterns/resource-detail-page.cjs +0 -1
- package/dist/components/patterns/resource-detail-page.d.ts +0 -39
- package/dist/components/patterns/resource-detail-page.js +0 -1
- package/dist/components/patterns/resource-page.cjs +0 -1
- package/dist/components/patterns/resource-page.d.ts +0 -30
- package/dist/components/patterns/resource-page.js +0 -1
- package/dist/components/patterns/settings-page.cjs +0 -1
- package/dist/components/patterns/settings-page.d.ts +0 -21
- package/dist/components/patterns/settings-page.js +0 -1
- package/dist/src/components/charts/progress-ring.cjs +0 -1
- package/dist/src/components/charts/progress-ring.js +0 -57
- package/dist/src/components/feedback/loading-state.cjs +0 -1
- package/dist/src/components/feedback/loading-state.js +0 -15
- package/dist/src/components/feedback/page-state.cjs +0 -1
- package/dist/src/components/feedback/page-state.js +0 -28
- package/dist/src/components/layout/page-container.cjs +0 -1
- package/dist/src/components/layout/page-container.js +0 -21
- package/dist/src/components/layout/page-header.cjs +0 -1
- package/dist/src/components/layout/page-header.js +0 -92
- package/dist/src/components/layout/section-header.cjs +0 -1
- package/dist/src/components/layout/section-header.js +0 -44
- package/dist/src/components/layout/sidebar-nav.cjs +0 -1
- package/dist/src/components/layout/sidebar-nav.js +0 -220
- package/dist/src/components/layout/sticky-footer-bar.cjs +0 -1
- package/dist/src/components/layout/sticky-footer-bar.js +0 -20
- package/dist/src/components/patterns/bulk-action-bar.cjs +0 -1
- package/dist/src/components/patterns/bulk-action-bar.js +0 -34
- package/dist/src/components/patterns/data-view.cjs +0 -1
- package/dist/src/components/patterns/data-view.js +0 -47
- package/dist/src/components/patterns/detail-layout.cjs +0 -1
- package/dist/src/components/patterns/detail-layout.js +0 -65
- package/dist/src/components/patterns/form-builder.cjs +0 -1
- package/dist/src/components/patterns/form-builder.js +0 -263
- package/dist/src/components/patterns/page-toolbar.cjs +0 -1
- package/dist/src/components/patterns/page-toolbar.js +0 -41
- package/dist/src/components/patterns/resource-detail-page.cjs +0 -1
- package/dist/src/components/patterns/resource-detail-page.js +0 -100
- package/dist/src/components/patterns/resource-page.cjs +0 -1
- package/dist/src/components/patterns/resource-page.js +0 -88
- package/dist/src/components/patterns/settings-page.cjs +0 -1
- package/dist/src/components/patterns/settings-page.js +0 -64
- package/packages/cli/vendor/src/components/charts/progress-ring.tsx +0 -56
- package/packages/cli/vendor/src/components/feedback/loading-state.tsx +0 -15
- package/packages/cli/vendor/src/components/feedback/page-state.tsx +0 -29
- package/packages/cli/vendor/src/components/layout/page-container.tsx +0 -30
- package/packages/cli/vendor/src/components/layout/page-header.tsx +0 -104
- package/packages/cli/vendor/src/components/layout/section-header.tsx +0 -56
- package/packages/cli/vendor/src/components/layout/sidebar-nav.tsx +0 -377
- package/packages/cli/vendor/src/components/layout/sticky-footer-bar.tsx +0 -23
- package/packages/cli/vendor/src/components/patterns/bulk-action-bar.tsx +0 -53
- package/packages/cli/vendor/src/components/patterns/data-view.tsx +0 -64
- package/packages/cli/vendor/src/components/patterns/detail-layout.tsx +0 -75
- package/packages/cli/vendor/src/components/patterns/form-builder.tsx +0 -472
- package/packages/cli/vendor/src/components/patterns/page-toolbar.tsx +0 -61
- package/packages/cli/vendor/src/components/patterns/resource-detail-page.tsx +0 -164
- package/packages/cli/vendor/src/components/patterns/resource-page.tsx +0 -149
- package/packages/cli/vendor/src/components/patterns/settings-page.tsx +0 -84
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
|
|
3
|
-
import { DataTable, type DataTableProps } from "@/components/data-table/data-table"
|
|
4
|
-
import { EmptyState } from "@/components/patterns/empty-state"
|
|
5
|
-
import { PageToolbar } from "@/components/patterns/page-toolbar"
|
|
6
|
-
import { cn } from "@/lib/utils"
|
|
7
|
-
|
|
8
|
-
export type DataViewProps<TData, TValue = unknown> = Omit<React.ComponentProps<"div">, "children"> & {
|
|
9
|
-
title?: React.ReactNode
|
|
10
|
-
description?: React.ReactNode
|
|
11
|
-
toolbar?: React.ComponentProps<typeof PageToolbar>
|
|
12
|
-
table?: DataTableProps<TData, TValue>
|
|
13
|
-
count?: number
|
|
14
|
-
loading?: boolean
|
|
15
|
-
error?: React.ReactNode
|
|
16
|
-
emptyTitle?: React.ReactNode
|
|
17
|
-
emptyDescription?: React.ReactNode
|
|
18
|
-
children?: React.ReactNode
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function DataView<TData, TValue = unknown>({
|
|
22
|
-
className,
|
|
23
|
-
title,
|
|
24
|
-
description,
|
|
25
|
-
toolbar,
|
|
26
|
-
table,
|
|
27
|
-
count,
|
|
28
|
-
loading = false,
|
|
29
|
-
error,
|
|
30
|
-
emptyTitle = "No results",
|
|
31
|
-
emptyDescription = "Try adjusting filters or create a new item.",
|
|
32
|
-
children,
|
|
33
|
-
...props
|
|
34
|
-
}: DataViewProps<TData, TValue>) {
|
|
35
|
-
const hasHeader = Boolean(title || description)
|
|
36
|
-
const hasToolbar = Boolean(toolbar)
|
|
37
|
-
const hasData = count === undefined ? Boolean(children || table) : count > 0
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<div data-slot="data-view" className={cn("grid min-w-0 gap-4", className)} {...props}>
|
|
41
|
-
{hasHeader && (
|
|
42
|
-
<div data-slot="data-view-header" className="grid gap-1">
|
|
43
|
-
{title && <h2 className="text-xl font-semibold tracking-tight">{title}</h2>}
|
|
44
|
-
{description && <p className="text-sm leading-6 text-muted-foreground">{description}</p>}
|
|
45
|
-
</div>
|
|
46
|
-
)}
|
|
47
|
-
{hasToolbar && <PageToolbar {...toolbar} />}
|
|
48
|
-
{error ? (
|
|
49
|
-
<EmptyState tone="error" title="Something went wrong" description={error} />
|
|
50
|
-
) : loading ? (
|
|
51
|
-
<EmptyState tone="info" title="Loading data" description="Please wait while the latest data is prepared." />
|
|
52
|
-
) : hasData ? (
|
|
53
|
-
<div data-slot="data-view-content" className="min-w-0">
|
|
54
|
-
{children}
|
|
55
|
-
{table && <DataTable {...table} />}
|
|
56
|
-
</div>
|
|
57
|
-
) : (
|
|
58
|
-
<EmptyState title={emptyTitle} description={emptyDescription} />
|
|
59
|
-
)}
|
|
60
|
-
</div>
|
|
61
|
-
)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export { DataView }
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
|
|
3
|
-
import { cn } from "@/lib/utils"
|
|
4
|
-
|
|
5
|
-
export type DetailLayoutProps = React.ComponentProps<"div"> & {
|
|
6
|
-
title?: React.ReactNode
|
|
7
|
-
description?: React.ReactNode
|
|
8
|
-
eyebrow?: React.ReactNode
|
|
9
|
-
actions?: React.ReactNode
|
|
10
|
-
breadcrumbs?: React.ReactNode
|
|
11
|
-
summary?: React.ReactNode
|
|
12
|
-
aside?: React.ReactNode
|
|
13
|
-
footer?: React.ReactNode
|
|
14
|
-
headerClassName?: string
|
|
15
|
-
contentClassName?: string
|
|
16
|
-
asideClassName?: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function DetailLayout({
|
|
20
|
-
className,
|
|
21
|
-
title,
|
|
22
|
-
description,
|
|
23
|
-
eyebrow,
|
|
24
|
-
actions,
|
|
25
|
-
breadcrumbs,
|
|
26
|
-
summary,
|
|
27
|
-
aside,
|
|
28
|
-
footer,
|
|
29
|
-
headerClassName,
|
|
30
|
-
contentClassName,
|
|
31
|
-
asideClassName,
|
|
32
|
-
children,
|
|
33
|
-
...props
|
|
34
|
-
}: DetailLayoutProps) {
|
|
35
|
-
const hasHeader = Boolean(title || description || eyebrow || actions)
|
|
36
|
-
const hasContent = Boolean(children || aside)
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<div data-slot="detail-layout" className={cn("grid min-w-0 gap-4", className)} {...props}>
|
|
40
|
-
{breadcrumbs && <div data-slot="detail-layout-breadcrumbs">{breadcrumbs}</div>}
|
|
41
|
-
{hasHeader && (
|
|
42
|
-
<div
|
|
43
|
-
data-slot="detail-layout-header"
|
|
44
|
-
className={cn("flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between", headerClassName)}
|
|
45
|
-
>
|
|
46
|
-
<div className="grid min-w-0 gap-1">
|
|
47
|
-
{eyebrow && <div className="text-xs font-medium uppercase tracking-wide text-muted-foreground">{eyebrow}</div>}
|
|
48
|
-
{title && <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>}
|
|
49
|
-
{description && <p className="max-w-3xl text-sm leading-6 text-muted-foreground">{description}</p>}
|
|
50
|
-
</div>
|
|
51
|
-
{actions && <div className="flex shrink-0 flex-wrap items-center gap-2">{actions}</div>}
|
|
52
|
-
</div>
|
|
53
|
-
)}
|
|
54
|
-
{summary && <div data-slot="detail-layout-summary">{summary}</div>}
|
|
55
|
-
{hasContent && (
|
|
56
|
-
<div
|
|
57
|
-
data-slot="detail-layout-content"
|
|
58
|
-
className={cn("grid min-w-0 gap-4", aside && "xl:grid-cols-[minmax(0,1fr)_20rem]", contentClassName)}
|
|
59
|
-
>
|
|
60
|
-
<main data-slot="detail-layout-main" className="min-w-0">
|
|
61
|
-
{children}
|
|
62
|
-
</main>
|
|
63
|
-
{aside && (
|
|
64
|
-
<aside data-slot="detail-layout-aside" className={cn("min-w-0", asideClassName)}>
|
|
65
|
-
{aside}
|
|
66
|
-
</aside>
|
|
67
|
-
)}
|
|
68
|
-
</div>
|
|
69
|
-
)}
|
|
70
|
-
{footer && <div data-slot="detail-layout-footer">{footer}</div>}
|
|
71
|
-
</div>
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export { DetailLayout }
|
|
@@ -1,472 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import type { Control, FieldPath, FieldValues } from "react-hook-form"
|
|
3
|
-
|
|
4
|
-
import { Button } from "@/components/ui/button"
|
|
5
|
-
import {
|
|
6
|
-
FormInput,
|
|
7
|
-
type FormInputDateVariantProps,
|
|
8
|
-
type FormInputNumberVariantProps,
|
|
9
|
-
type FormInputPhoneVariantProps,
|
|
10
|
-
type FormInputProps,
|
|
11
|
-
} from "@/components/form/form-input"
|
|
12
|
-
import { FormTextarea, type FormTextareaProps } from "@/components/form/form-textarea"
|
|
13
|
-
import {
|
|
14
|
-
FormSelect,
|
|
15
|
-
type FormSelectAsyncVariantProps,
|
|
16
|
-
type FormSelectProps,
|
|
17
|
-
} from "@/components/form/form-select"
|
|
18
|
-
import { FormSwitch, type FormSwitchProps } from "@/components/form/form-switch"
|
|
19
|
-
import {
|
|
20
|
-
FormDateRangeInput,
|
|
21
|
-
type FormDateRangeInputProps,
|
|
22
|
-
} from "@/components/form/form-date-range-input"
|
|
23
|
-
import { cn } from "@/lib/utils"
|
|
24
|
-
|
|
25
|
-
export type FormBuilderLayout = "grid" | "stack"
|
|
26
|
-
export type FormBuilderDensity = "compact" | "default" | "comfortable"
|
|
27
|
-
|
|
28
|
-
type DistributiveOmit<T, TKey extends PropertyKey> = T extends unknown ? Omit<T, TKey> : never
|
|
29
|
-
|
|
30
|
-
export type FormBuilderFieldRenderContext<TFieldValues extends FieldValues> = {
|
|
31
|
-
control: Control<TFieldValues>
|
|
32
|
-
disabled?: boolean
|
|
33
|
-
readOnly?: boolean
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type BaseFormBuilderField = {
|
|
37
|
-
id: string
|
|
38
|
-
hidden?: boolean
|
|
39
|
-
className?: string
|
|
40
|
-
colSpan?: 1 | 2 | 3 | 4 | "full"
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export type FormBuilderCustomField<TFieldValues extends FieldValues> = BaseFormBuilderField & {
|
|
44
|
-
type: "custom"
|
|
45
|
-
render: (context: FormBuilderFieldRenderContext<TFieldValues>) => React.ReactNode
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export type FormBuilderInputField<
|
|
49
|
-
TFieldValues extends FieldValues,
|
|
50
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
51
|
-
> = BaseFormBuilderField & {
|
|
52
|
-
type: "input"
|
|
53
|
-
props: DistributiveOmit<FormInputProps<TFieldValues, TName>, "control">
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export type FormBuilderTextareaField<
|
|
57
|
-
TFieldValues extends FieldValues,
|
|
58
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
59
|
-
> = BaseFormBuilderField & {
|
|
60
|
-
type: "textarea"
|
|
61
|
-
props: Omit<FormTextareaProps<TFieldValues, TName>, "control">
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export type FormBuilderSelectField<
|
|
65
|
-
TFieldValues extends FieldValues,
|
|
66
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
67
|
-
> = BaseFormBuilderField & {
|
|
68
|
-
type: "select"
|
|
69
|
-
props: DistributiveOmit<FormSelectProps<TFieldValues, TName>, "control">
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export type FormBuilderAsyncSelectField<
|
|
73
|
-
TFieldValues extends FieldValues,
|
|
74
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
75
|
-
> = BaseFormBuilderField & {
|
|
76
|
-
type: "async-select"
|
|
77
|
-
props: Omit<FormSelectAsyncVariantProps<TFieldValues, TName>, "control" | "kind">
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export type FormBuilderSwitchField<
|
|
81
|
-
TFieldValues extends FieldValues,
|
|
82
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
83
|
-
> = BaseFormBuilderField & {
|
|
84
|
-
type: "switch"
|
|
85
|
-
props: Omit<FormSwitchProps<TFieldValues, TName>, "control">
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export type FormBuilderNumberField<
|
|
89
|
-
TFieldValues extends FieldValues,
|
|
90
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
91
|
-
> = BaseFormBuilderField & {
|
|
92
|
-
type: "number"
|
|
93
|
-
props: Omit<FormInputNumberVariantProps<TFieldValues, TName>, "control" | "kind">
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export type FormBuilderPhoneField<
|
|
97
|
-
TFieldValues extends FieldValues,
|
|
98
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
99
|
-
> = BaseFormBuilderField & {
|
|
100
|
-
type: "phone"
|
|
101
|
-
props: Omit<FormInputPhoneVariantProps<TFieldValues, TName>, "control" | "kind">
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export type FormBuilderDateField<
|
|
105
|
-
TFieldValues extends FieldValues,
|
|
106
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
107
|
-
> = BaseFormBuilderField & {
|
|
108
|
-
type: "date"
|
|
109
|
-
props: Omit<FormInputDateVariantProps<TFieldValues, TName>, "control" | "kind">
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export type FormBuilderDateRangeField<
|
|
113
|
-
TFieldValues extends FieldValues,
|
|
114
|
-
TFromName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
115
|
-
TToName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
116
|
-
> = BaseFormBuilderField & {
|
|
117
|
-
type: "date-range"
|
|
118
|
-
props: Omit<FormDateRangeInputProps<TFieldValues, TFromName, TToName>, "control">
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
type FormBuilderStoredField = BaseFormBuilderField & {
|
|
122
|
-
type: "input" | "textarea" | "select" | "async-select" | "switch" | "number" | "phone" | "date" | "date-range"
|
|
123
|
-
props: object
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export type FormBuilderField<TFieldValues extends FieldValues> =
|
|
127
|
-
| FormBuilderCustomField<TFieldValues>
|
|
128
|
-
| FormBuilderStoredField
|
|
129
|
-
|
|
130
|
-
export type FormBuilderSection<TFieldValues extends FieldValues> = {
|
|
131
|
-
id: string
|
|
132
|
-
title?: React.ReactNode
|
|
133
|
-
description?: React.ReactNode
|
|
134
|
-
actions?: React.ReactNode
|
|
135
|
-
hidden?: boolean
|
|
136
|
-
className?: string
|
|
137
|
-
fields: FormBuilderField<TFieldValues>[]
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export type FormBuilderProps<TFieldValues extends FieldValues> = Omit<
|
|
141
|
-
React.ComponentProps<"form">,
|
|
142
|
-
"children"
|
|
143
|
-
> & {
|
|
144
|
-
control: Control<TFieldValues>
|
|
145
|
-
fields?: FormBuilderField<TFieldValues>[]
|
|
146
|
-
sections?: FormBuilderSection<TFieldValues>[]
|
|
147
|
-
layout?: FormBuilderLayout
|
|
148
|
-
density?: FormBuilderDensity
|
|
149
|
-
columns?: 1 | 2 | 3 | 4
|
|
150
|
-
disabled?: boolean
|
|
151
|
-
readOnly?: boolean
|
|
152
|
-
footer?: React.ReactNode
|
|
153
|
-
submitLabel?: React.ReactNode
|
|
154
|
-
resetLabel?: React.ReactNode
|
|
155
|
-
onResetClick?: () => void
|
|
156
|
-
isSubmitting?: boolean
|
|
157
|
-
sectionClassName?: string
|
|
158
|
-
fieldClassName?: string
|
|
159
|
-
footerClassName?: string
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const densityClassName: Record<FormBuilderDensity, string> = {
|
|
163
|
-
compact: "gap-3",
|
|
164
|
-
default: "gap-4",
|
|
165
|
-
comfortable: "gap-6",
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const columnsClassName: Record<1 | 2 | 3 | 4, string> = {
|
|
169
|
-
1: "grid-cols-1",
|
|
170
|
-
2: "grid-cols-1 md:grid-cols-2",
|
|
171
|
-
3: "grid-cols-1 md:grid-cols-2 xl:grid-cols-3",
|
|
172
|
-
4: "grid-cols-1 md:grid-cols-2 xl:grid-cols-4",
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const colSpanClassName: Record<NonNullable<BaseFormBuilderField["colSpan"]>, string> = {
|
|
176
|
-
1: "col-span-1",
|
|
177
|
-
2: "md:col-span-2",
|
|
178
|
-
3: "xl:col-span-3",
|
|
179
|
-
4: "xl:col-span-4",
|
|
180
|
-
full: "col-span-full",
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
type FieldPresetOptions<TProps> = BaseFormBuilderField & {
|
|
184
|
-
props: TProps
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function splitFieldOptions<TProps>(options: FieldPresetOptions<TProps>) {
|
|
188
|
-
const { id, hidden, className, colSpan, props } = options
|
|
189
|
-
return { base: { id, hidden, className, colSpan }, props }
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function inputField<
|
|
193
|
-
TFieldValues extends FieldValues,
|
|
194
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
195
|
-
>(options: FieldPresetOptions<FormBuilderInputField<TFieldValues, TName>["props"]>): FormBuilderInputField<TFieldValues, TName> {
|
|
196
|
-
const { base, props } = splitFieldOptions(options)
|
|
197
|
-
return { ...base, type: "input", props }
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function textareaField<
|
|
201
|
-
TFieldValues extends FieldValues,
|
|
202
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
203
|
-
>(options: FieldPresetOptions<FormBuilderTextareaField<TFieldValues, TName>["props"]>): FormBuilderTextareaField<TFieldValues, TName> {
|
|
204
|
-
const { base, props } = splitFieldOptions(options)
|
|
205
|
-
return { ...base, type: "textarea", props }
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function selectField<
|
|
209
|
-
TFieldValues extends FieldValues,
|
|
210
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
211
|
-
>(options: FieldPresetOptions<FormBuilderSelectField<TFieldValues, TName>["props"]>): FormBuilderSelectField<TFieldValues, TName> {
|
|
212
|
-
const { base, props } = splitFieldOptions(options)
|
|
213
|
-
return { ...base, type: "select", props }
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function asyncSelectField<
|
|
217
|
-
TFieldValues extends FieldValues,
|
|
218
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
219
|
-
>(options: FieldPresetOptions<FormBuilderAsyncSelectField<TFieldValues, TName>["props"]>): FormBuilderAsyncSelectField<TFieldValues, TName> {
|
|
220
|
-
const { base, props } = splitFieldOptions(options)
|
|
221
|
-
return { ...base, type: "async-select", props }
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function switchField<
|
|
225
|
-
TFieldValues extends FieldValues,
|
|
226
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
227
|
-
>(options: FieldPresetOptions<FormBuilderSwitchField<TFieldValues, TName>["props"]>): FormBuilderSwitchField<TFieldValues, TName> {
|
|
228
|
-
const { base, props } = splitFieldOptions(options)
|
|
229
|
-
return { ...base, type: "switch", props }
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function numberField<
|
|
233
|
-
TFieldValues extends FieldValues,
|
|
234
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
235
|
-
>(options: FieldPresetOptions<FormBuilderNumberField<TFieldValues, TName>["props"]>): FormBuilderNumberField<TFieldValues, TName> {
|
|
236
|
-
const { base, props } = splitFieldOptions(options)
|
|
237
|
-
return { ...base, type: "number", props }
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function phoneField<
|
|
241
|
-
TFieldValues extends FieldValues,
|
|
242
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
243
|
-
>(options: FieldPresetOptions<FormBuilderPhoneField<TFieldValues, TName>["props"]>): FormBuilderPhoneField<TFieldValues, TName> {
|
|
244
|
-
const { base, props } = splitFieldOptions(options)
|
|
245
|
-
return { ...base, type: "phone", props }
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function dateField<
|
|
249
|
-
TFieldValues extends FieldValues,
|
|
250
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
251
|
-
>(options: FieldPresetOptions<FormBuilderDateField<TFieldValues, TName>["props"]>): FormBuilderDateField<TFieldValues, TName> {
|
|
252
|
-
const { base, props } = splitFieldOptions(options)
|
|
253
|
-
return { ...base, type: "date", props }
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function dateRangeField<
|
|
257
|
-
TFieldValues extends FieldValues,
|
|
258
|
-
TFromName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
259
|
-
TToName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
260
|
-
>(options: FieldPresetOptions<FormBuilderDateRangeField<TFieldValues, TFromName, TToName>["props"]>): FormBuilderDateRangeField<TFieldValues, TFromName, TToName> {
|
|
261
|
-
const { base, props } = splitFieldOptions(options)
|
|
262
|
-
return { ...base, type: "date-range", props }
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function customField<TFieldValues extends FieldValues>(
|
|
266
|
-
options: BaseFormBuilderField & Pick<FormBuilderCustomField<TFieldValues>, "render">
|
|
267
|
-
): FormBuilderCustomField<TFieldValues> {
|
|
268
|
-
const { id, hidden, className, colSpan, render } = options
|
|
269
|
-
return { id, hidden, className, colSpan, type: "custom", render }
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function formSection<TFieldValues extends FieldValues>(
|
|
273
|
-
section: FormBuilderSection<TFieldValues>
|
|
274
|
-
): FormBuilderSection<TFieldValues> {
|
|
275
|
-
return section
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function renderFormBuilderField<TFieldValues extends FieldValues>(
|
|
279
|
-
field: FormBuilderField<TFieldValues>,
|
|
280
|
-
context: FormBuilderFieldRenderContext<TFieldValues>
|
|
281
|
-
) {
|
|
282
|
-
const FormInputComponent = FormInput as unknown as React.ComponentType<Record<string, unknown>>
|
|
283
|
-
const FormSelectComponent = FormSelect as unknown as React.ComponentType<Record<string, unknown>>
|
|
284
|
-
const FormTextareaComponent = FormTextarea as unknown as React.ComponentType<Record<string, unknown>>
|
|
285
|
-
const FormSwitchComponent = FormSwitch as unknown as React.ComponentType<Record<string, unknown>>
|
|
286
|
-
const FormDateRangeInputComponent = FormDateRangeInput as unknown as React.ComponentType<Record<string, unknown>>
|
|
287
|
-
|
|
288
|
-
switch (field.type) {
|
|
289
|
-
case "custom":
|
|
290
|
-
return field.render(context)
|
|
291
|
-
case "input":
|
|
292
|
-
return React.createElement(FormInputComponent, {
|
|
293
|
-
control: context.control,
|
|
294
|
-
disabled: context.disabled,
|
|
295
|
-
readOnly: context.readOnly,
|
|
296
|
-
...(field.props as Omit<FormInputProps<TFieldValues, FieldPath<TFieldValues>>, "control">),
|
|
297
|
-
})
|
|
298
|
-
case "textarea":
|
|
299
|
-
return React.createElement(FormTextareaComponent, { control: context.control, disabled: context.disabled, readOnly: context.readOnly, ...field.props })
|
|
300
|
-
case "select":
|
|
301
|
-
return React.createElement(FormSelectComponent, {
|
|
302
|
-
control: context.control,
|
|
303
|
-
disabled: context.disabled,
|
|
304
|
-
...(field.props as Omit<FormSelectProps<TFieldValues, FieldPath<TFieldValues>>, "control">),
|
|
305
|
-
})
|
|
306
|
-
case "async-select":
|
|
307
|
-
return React.createElement(FormSelectComponent, {
|
|
308
|
-
control: context.control,
|
|
309
|
-
disabled: context.disabled,
|
|
310
|
-
kind: "async",
|
|
311
|
-
...field.props,
|
|
312
|
-
})
|
|
313
|
-
case "switch":
|
|
314
|
-
return React.createElement(FormSwitchComponent, { control: context.control, disabled: context.disabled, ...field.props })
|
|
315
|
-
case "number":
|
|
316
|
-
return React.createElement(FormInputComponent, {
|
|
317
|
-
control: context.control,
|
|
318
|
-
disabled: context.disabled,
|
|
319
|
-
readOnly: context.readOnly,
|
|
320
|
-
kind: "number",
|
|
321
|
-
...field.props,
|
|
322
|
-
})
|
|
323
|
-
case "phone":
|
|
324
|
-
return React.createElement(FormInputComponent, {
|
|
325
|
-
control: context.control,
|
|
326
|
-
disabled: context.disabled,
|
|
327
|
-
readOnly: context.readOnly,
|
|
328
|
-
kind: "phone",
|
|
329
|
-
...field.props,
|
|
330
|
-
})
|
|
331
|
-
case "date":
|
|
332
|
-
return React.createElement(FormInputComponent, {
|
|
333
|
-
control: context.control,
|
|
334
|
-
disabled: context.disabled,
|
|
335
|
-
readOnly: context.readOnly,
|
|
336
|
-
kind: "date",
|
|
337
|
-
...field.props,
|
|
338
|
-
})
|
|
339
|
-
case "date-range":
|
|
340
|
-
return React.createElement(FormDateRangeInputComponent, {
|
|
341
|
-
control: context.control,
|
|
342
|
-
fromInputProps: { disabled: context.disabled, readOnly: context.readOnly },
|
|
343
|
-
toInputProps: { disabled: context.disabled, readOnly: context.readOnly },
|
|
344
|
-
...field.props,
|
|
345
|
-
})
|
|
346
|
-
default:
|
|
347
|
-
return null
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function normalizeSections<TFieldValues extends FieldValues>({
|
|
352
|
-
fields,
|
|
353
|
-
sections,
|
|
354
|
-
}: Pick<FormBuilderProps<TFieldValues>, "fields" | "sections">): FormBuilderSection<TFieldValues>[] {
|
|
355
|
-
if (sections?.length) return sections
|
|
356
|
-
return [{ id: "default", fields: fields ?? [] }]
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
function FormBuilder<TFieldValues extends FieldValues>({
|
|
360
|
-
className,
|
|
361
|
-
control,
|
|
362
|
-
fields,
|
|
363
|
-
sections,
|
|
364
|
-
layout = "grid",
|
|
365
|
-
density = "default",
|
|
366
|
-
columns = 2,
|
|
367
|
-
disabled,
|
|
368
|
-
readOnly,
|
|
369
|
-
footer,
|
|
370
|
-
submitLabel,
|
|
371
|
-
resetLabel,
|
|
372
|
-
onResetClick,
|
|
373
|
-
isSubmitting,
|
|
374
|
-
sectionClassName,
|
|
375
|
-
fieldClassName,
|
|
376
|
-
footerClassName,
|
|
377
|
-
...props
|
|
378
|
-
}: FormBuilderProps<TFieldValues>) {
|
|
379
|
-
const normalizedSections = normalizeSections({ fields, sections })
|
|
380
|
-
const context = React.useMemo(
|
|
381
|
-
() => ({ control, disabled, readOnly }),
|
|
382
|
-
[control, disabled, readOnly]
|
|
383
|
-
)
|
|
384
|
-
|
|
385
|
-
return (
|
|
386
|
-
<form data-slot="form-builder" className={cn("grid", densityClassName[density], className)} {...props}>
|
|
387
|
-
{normalizedSections
|
|
388
|
-
.filter((section) => !section.hidden)
|
|
389
|
-
.map((section) => (
|
|
390
|
-
<section
|
|
391
|
-
key={section.id}
|
|
392
|
-
data-slot="form-builder-section"
|
|
393
|
-
className={cn("grid gap-4", section.className, sectionClassName)}
|
|
394
|
-
>
|
|
395
|
-
{(section.title || section.description || section.actions) && (
|
|
396
|
-
<div
|
|
397
|
-
data-slot="form-builder-section-header"
|
|
398
|
-
className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between"
|
|
399
|
-
>
|
|
400
|
-
<div className="min-w-0 space-y-1">
|
|
401
|
-
{section.title && <h2 className="text-base font-semibold leading-none tracking-tight">{section.title}</h2>}
|
|
402
|
-
{section.description && <p className="text-sm text-muted-foreground">{section.description}</p>}
|
|
403
|
-
</div>
|
|
404
|
-
{section.actions && <div className="flex shrink-0 flex-wrap items-center gap-2">{section.actions}</div>}
|
|
405
|
-
</div>
|
|
406
|
-
)}
|
|
407
|
-
|
|
408
|
-
<div
|
|
409
|
-
data-slot="form-builder-fields"
|
|
410
|
-
data-layout={layout}
|
|
411
|
-
className={cn(
|
|
412
|
-
"grid",
|
|
413
|
-
densityClassName[density],
|
|
414
|
-
layout === "grid" ? columnsClassName[columns] : "grid-cols-1"
|
|
415
|
-
)}
|
|
416
|
-
>
|
|
417
|
-
{section.fields
|
|
418
|
-
.filter((field) => !field.hidden)
|
|
419
|
-
.map((field) => (
|
|
420
|
-
<div
|
|
421
|
-
key={field.id}
|
|
422
|
-
data-slot="form-builder-field"
|
|
423
|
-
className={cn(
|
|
424
|
-
field.colSpan && colSpanClassName[field.colSpan],
|
|
425
|
-
field.className,
|
|
426
|
-
fieldClassName
|
|
427
|
-
)}
|
|
428
|
-
>
|
|
429
|
-
{renderFormBuilderField(field, context)}
|
|
430
|
-
</div>
|
|
431
|
-
))}
|
|
432
|
-
</div>
|
|
433
|
-
</section>
|
|
434
|
-
))}
|
|
435
|
-
|
|
436
|
-
{(footer || submitLabel || resetLabel) && (
|
|
437
|
-
<div data-slot="form-builder-footer" className={cn("flex flex-wrap items-center justify-end gap-2", footerClassName)}>
|
|
438
|
-
{footer ?? (
|
|
439
|
-
<>
|
|
440
|
-
{resetLabel && (
|
|
441
|
-
<Button type="button" variant="outline" disabled={disabled || isSubmitting} onClick={onResetClick}>
|
|
442
|
-
{resetLabel}
|
|
443
|
-
</Button>
|
|
444
|
-
)}
|
|
445
|
-
{submitLabel && (
|
|
446
|
-
<Button type="submit" disabled={disabled || isSubmitting}>
|
|
447
|
-
{submitLabel}
|
|
448
|
-
</Button>
|
|
449
|
-
)}
|
|
450
|
-
</>
|
|
451
|
-
)}
|
|
452
|
-
</div>
|
|
453
|
-
)}
|
|
454
|
-
</form>
|
|
455
|
-
)
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
export {
|
|
459
|
-
FormBuilder,
|
|
460
|
-
asyncSelectField,
|
|
461
|
-
customField,
|
|
462
|
-
dateField,
|
|
463
|
-
dateRangeField,
|
|
464
|
-
formSection,
|
|
465
|
-
inputField,
|
|
466
|
-
numberField,
|
|
467
|
-
phoneField,
|
|
468
|
-
renderFormBuilderField,
|
|
469
|
-
selectField,
|
|
470
|
-
switchField,
|
|
471
|
-
textareaField,
|
|
472
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
|
|
3
|
-
import { cn } from "@/lib/utils"
|
|
4
|
-
|
|
5
|
-
export type PageToolbarProps = React.ComponentProps<"div"> & {
|
|
6
|
-
search?: React.ReactNode
|
|
7
|
-
filters?: React.ReactNode
|
|
8
|
-
viewOptions?: React.ReactNode
|
|
9
|
-
actions?: React.ReactNode
|
|
10
|
-
summary?: React.ReactNode
|
|
11
|
-
sticky?: boolean
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function PageToolbar({
|
|
15
|
-
className,
|
|
16
|
-
search,
|
|
17
|
-
filters,
|
|
18
|
-
viewOptions,
|
|
19
|
-
actions,
|
|
20
|
-
summary,
|
|
21
|
-
sticky = false,
|
|
22
|
-
children,
|
|
23
|
-
...props
|
|
24
|
-
}: PageToolbarProps) {
|
|
25
|
-
const hasPrimary = Boolean(search || filters || children)
|
|
26
|
-
const hasSecondary = Boolean(summary || viewOptions || actions)
|
|
27
|
-
|
|
28
|
-
if (!hasPrimary && !hasSecondary) return null
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<div
|
|
32
|
-
data-slot="page-toolbar"
|
|
33
|
-
data-sticky={sticky || undefined}
|
|
34
|
-
className={cn(
|
|
35
|
-
"flex flex-col gap-3 rounded-xl border bg-card p-3 shadow-sm",
|
|
36
|
-
sticky && "sticky top-0 z-10",
|
|
37
|
-
className
|
|
38
|
-
)}
|
|
39
|
-
{...props}
|
|
40
|
-
>
|
|
41
|
-
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
|
|
42
|
-
{hasPrimary && (
|
|
43
|
-
<div data-slot="page-toolbar-primary" className="flex min-w-0 flex-1 flex-col gap-2 sm:flex-row sm:items-center">
|
|
44
|
-
{search && <div className="min-w-0 flex-1">{search}</div>}
|
|
45
|
-
{filters}
|
|
46
|
-
{children}
|
|
47
|
-
</div>
|
|
48
|
-
)}
|
|
49
|
-
{hasSecondary && (
|
|
50
|
-
<div data-slot="page-toolbar-secondary" className="flex shrink-0 flex-wrap items-center gap-2">
|
|
51
|
-
{summary && <div className="text-sm text-muted-foreground">{summary}</div>}
|
|
52
|
-
{viewOptions}
|
|
53
|
-
{actions}
|
|
54
|
-
</div>
|
|
55
|
-
)}
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export { PageToolbar }
|