@classytic/fluid 0.1.2 → 0.2.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @classytic/fluid
2
2
 
3
- Reusable UI components built on shadcn/ui for Next.js projects.
3
+ Reusable UI components built on shadcn/ui and Base UI for Next.js projects.
4
4
 
5
5
  ## Install
6
6
 
@@ -11,47 +11,92 @@ npm install @classytic/fluid
11
11
  ## Usage
12
12
 
13
13
  ```tsx
14
- import { DialogWrapper, FormInput, DataTable } from "@classytic/fluid";
15
- import { PageHeader, InsetSidebar } from "@classytic/fluid/dashboard";
14
+ // Main components
15
+ import {
16
+ DialogWrapper,
17
+ FormInput,
18
+ DataTable,
19
+ EmptyState,
20
+ } from "@classytic/fluid";
21
+
22
+ // Dashboard components (sidebar, header, project switcher)
23
+ import {
24
+ PageHeader,
25
+ InsetSidebar,
26
+ SidebarNav,
27
+ } from "@classytic/fluid/dashboard";
28
+
29
+ // Layout utilities
30
+ import { Section, Container } from "@classytic/fluid/layout";
31
+
32
+ // Compact form components (floating labels)
33
+ import { CompactInput, CompactSelect, Field } from "@classytic/fluid/compact";
34
+
35
+ // Composable search system
36
+ import { Search, SearchProvider, useSearch } from "@classytic/fluid/search";
16
37
  ```
17
38
 
18
39
  ## Components
19
40
 
20
- | Category | Components |
21
- |----------|-----------|
22
- | **Dialogs/Sheets** | DialogWrapper, FormDialog, SheetWrapper, FormSheet, ConfirmDialog |
23
- | **Forms** | FormInput, FormTextarea, SelectInput, CheckboxInput, RadioInput, SwitchInput, DateInput, TagInput, ComboboxInput, SlugField, FormErrorSummary, DateRangeFilter |
24
- | **Tables** | DataTable, TableWrapper, SimpleTable |
25
- | **Layout** | CardWrapper, CollapsibleWrapper, ResponsiveSplitLayout, TabsWrapper |
26
- | **Display** | Pill, InfoRow, CopyButton, Thumbnail, DisplayHeading |
27
- | **Navigation** | ApiPagination, CustomPagination |
28
- | **Dashboard** | PageHeader, HeaderSection, InsetSidebar, DualSidebar, ProjectSwitcher, SidebarNav, SidebarUserMenu |
29
- | **Other** | ModeToggle, TooltipWrapper, DropdownWrapper, AccordionWrapper |
41
+ | Category | Components |
42
+ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
43
+ | **Dialogs/Sheets** | DialogWrapper, FormDialog, SheetWrapper, FormSheet, ConfirmDialog, ConfirmSheet, DeleteConfirmDialog |
44
+ | **Forms** | FormInput, FormTextarea, SelectInput, CheckboxInput, RadioInput, SwitchInput, DateInput, DateRangeInput, TagInput, TagChoiceInput, ComboboxInput, SlugField, PhoneInput, FormErrorSummary, DateRangeFilter |
45
+ | **Compact Forms** | CompactInput, CompactTextarea, CompactSelect, CompactNumberInput, CompactTagChoice, CompactSlugField, Field |
46
+ | **Tables** | DataTable, TableWrapper, SimpleTable |
47
+ | **Layout** | CardWrapper, DataCard, StatsCard, CollapsibleWrapper, CollapsibleCard, ResponsiveSplitLayout, TabsWrapper, DynamicTabs, Section, Container |
48
+ | **Display** | Pill (+ PillAvatar, PillStatus, PillDelta, PillIcon), InfoRow, CopyButton, CopyText, CopyCodeBlock, Thumbnail, DisplayHeading |
49
+ | **Feedback** | EmptyState (+ NoResults, NoData, NotFound presets), LoadingState, LoadingOverlay, ErrorState, StatusBanner |
50
+ | **Navigation** | ApiPagination, CustomPagination |
51
+ | **Search** | Search.Root, Search.Input, Search.TypeInput, Search.Filters, Search.Actions, Search.Container, SearchProvider |
52
+ | **Dashboard** | PageHeader, HeaderSection, InsetSidebar, DualSidebar, ProjectSwitcher, SidebarNav, SidebarBrand, SidebarUserMenu |
53
+ | **Other** | ModeToggle, TooltipWrapper, ButtonTooltip, IconTooltip, DropdownWrapper, ActionDropdown, SelectDropdown, CheckboxDropdown, RadioDropdown, AccordionSection, FaqAccordion, EventCalendar |
54
+
55
+ ## Hooks
56
+
57
+ | Hook | Description |
58
+ | ---------------------- | ------------------------------------------ |
59
+ | `useDebounce` | Debounce a value (e.g., search input) |
60
+ | `useDebouncedCallback` | Debounce a function callback |
61
+ | `useCopyToClipboard` | Copy text to clipboard with feedback state |
62
+ | `useBaseSearch` | Full search state management with filters |
63
+ | `useIsMobile` | Responsive breakpoint detection |
64
+ | `useMediaQuery` | Generic media query hook |
65
+ | `useScrollDetection` | Detect scroll position/direction |
30
66
 
31
67
  ## Exports
32
68
 
33
69
  ```tsx
34
- // Main components
70
+ // Main — all components, hooks, and utilities
35
71
  import { ... } from "@classytic/fluid";
36
72
 
37
- // Dashboard components (sidebar, header)
73
+ // Dashboard sidebar layouts, headers, navigation
38
74
  import { ... } from "@classytic/fluid/dashboard";
39
75
 
40
- // Layout utilities
76
+ // Layout — Section, Container
41
77
  import { ... } from "@classytic/fluid/layout";
78
+
79
+ // Compact — space-efficient form fields
80
+ import { ... } from "@classytic/fluid/compact";
81
+
82
+ // Search — composable search UI
83
+ import { ... } from "@classytic/fluid/search";
42
84
  ```
43
85
 
44
86
  ## Requirements
45
87
 
46
- - Next.js with `@/` path alias
88
+ - React 18+ (Next.js optional required for dashboard/routing components)
47
89
  - shadcn/ui components at `@/components/ui/*`
90
+ - Tailwind CSS configured in your project
48
91
 
49
92
  ## Dev
50
93
 
51
94
  ```bash
52
95
  npm run build # Build package
53
96
  npm run dev # Watch mode
97
+ npm run clean # Remove dist
54
98
  ```
99
+
55
100
  ## License
56
101
 
57
102
  **UNLICENSED**
@@ -59,4 +104,4 @@ npm run dev # Watch mode
59
104
  Copyright © 2026 Classytic. All rights reserved.
60
105
 
61
106
  This software is the confidential and proprietary information of Classytic.
62
- Unauthorized copying of this software, via any medium is strictly prohibited.
107
+ Unauthorized copying of this software, via any medium is strictly prohibited.
package/animations.css ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @classytic/fluid — Animation System
3
+ *
4
+ * Opt-in file for consumers who want Tailwind animate-* utility classes.
5
+ * If you already import styles.css, you do NOT need this file —
6
+ * styles.css includes both the keyframes and theme tokens.
7
+ *
8
+ * Usage: @import "@classytic/fluid/animations.css";
9
+ */
10
+
11
+ @theme inline {
12
+ --animate-fade-in: fade-in 0.6s ease-out forwards;
13
+ --animate-fade-in-up: fade-in-up 0.6s ease-out forwards;
14
+ --animate-scale-in: scale-in 0.6s ease-out forwards;
15
+ --animate-slide-in-left: slide-in-left 0.6s ease-out forwards;
16
+ --animate-slide-in-right: slide-in-right 0.6s ease-out forwards;
17
+ --animate-slide-in-up: slide-in-up 0.6s ease-out forwards;
18
+ --animate-slide-in-down: slide-in-down 0.6s ease-out forwards;
19
+ --animate-pulse-soft: pulse-soft 3s ease-in-out infinite;
20
+ --animate-float: float 6s ease-in-out infinite;
21
+
22
+ @keyframes fade-in {
23
+ from { opacity: 0; }
24
+ to { opacity: 1; }
25
+ }
26
+
27
+ @keyframes fade-in-up {
28
+ from { opacity: 0; transform: translateY(20px); }
29
+ to { opacity: 1; transform: translateY(0); }
30
+ }
31
+
32
+ @keyframes scale-in {
33
+ from { opacity: 0; transform: scale(0.95); }
34
+ to { opacity: 1; transform: scale(1); }
35
+ }
36
+
37
+ @keyframes slide-in-left {
38
+ from { opacity: 0; transform: translateX(-30px); }
39
+ to { opacity: 1; transform: translateX(0); }
40
+ }
41
+
42
+ @keyframes slide-in-right {
43
+ from { opacity: 0; transform: translateX(30px); }
44
+ to { opacity: 1; transform: translateX(0); }
45
+ }
46
+
47
+ @keyframes slide-in-up {
48
+ from { opacity: 0; transform: translateY(30px); }
49
+ to { opacity: 1; transform: translateY(0); }
50
+ }
51
+
52
+ @keyframes slide-in-down {
53
+ from { opacity: 0; transform: translateY(-30px); }
54
+ to { opacity: 1; transform: translateY(0); }
55
+ }
56
+
57
+ @keyframes pulse-soft {
58
+ 0%, 100% { opacity: 1; }
59
+ 50% { opacity: 0.8; }
60
+ }
61
+
62
+ @keyframes float {
63
+ 0%, 100% { transform: translateY(0); }
64
+ 50% { transform: translateY(-10px); }
65
+ }
66
+ }
67
+
68
+ @media (prefers-reduced-motion: reduce) {
69
+ [data-fluid-animate] {
70
+ animation: none !important;
71
+ opacity: 1 !important;
72
+ transform: none !important;
73
+ }
74
+ }
@@ -0,0 +1,15 @@
1
+ import { clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ var __defProp = Object.defineProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ function cn(...inputs) {
10
+ return twMerge(clsx(inputs));
11
+ }
12
+
13
+ export { __export, cn };
14
+ //# sourceMappingURL=chunk-GUHK2DTW.js.map
15
+ //# sourceMappingURL=chunk-GUHK2DTW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils.ts"],"names":[],"mappings":";;;;;;;;AAGO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B","file":"chunk-GUHK2DTW.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n return twMerge(clsx(inputs));\r\n}\r\n"]}
@@ -0,0 +1,57 @@
1
+ import { cn } from './chunk-GUHK2DTW.js';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ var backgrounds = {
5
+ default: "bg-background",
6
+ muted: "bg-muted",
7
+ primary: "bg-primary text-primary-foreground",
8
+ transparent: "bg-transparent"
9
+ };
10
+ var paddings = {
11
+ none: "",
12
+ sm: "py-8 md:py-12",
13
+ md: "py-12 md:py-16",
14
+ lg: "py-16 md:py-24",
15
+ xl: "py-24 md:py-32"
16
+ };
17
+ function Section({
18
+ id,
19
+ children,
20
+ className,
21
+ background = "default",
22
+ padding = "sm"
23
+ }) {
24
+ return /* @__PURE__ */ jsx(
25
+ "section",
26
+ {
27
+ id,
28
+ className: cn(backgrounds[background], paddings[padding], className),
29
+ children
30
+ }
31
+ );
32
+ }
33
+ var maxWidthClasses = {
34
+ sm: "max-w-screen-sm",
35
+ md: "max-w-screen-md",
36
+ lg: "max-w-screen-lg",
37
+ xl: "max-w-screen-xl",
38
+ "2xl": "max-w-screen-2xl",
39
+ "4xl": "max-w-4xl",
40
+ "5xl": "max-w-5xl",
41
+ "6xl": "max-w-6xl",
42
+ "7xl": "max-w-7xl",
43
+ full: "max-w-full"
44
+ };
45
+ function Container({
46
+ children,
47
+ className,
48
+ maxWidth = "7xl"
49
+ }) {
50
+ const isFullWidth = maxWidth === "full";
51
+ const baseClass = isFullWidth ? "w-full px-4 sm:px-6 lg:px-10" : "mx-auto px-4 sm:px-6 lg:px-8";
52
+ return /* @__PURE__ */ jsx("div", { className: cn(baseClass, !isFullWidth && maxWidthClasses[maxWidth], className), children });
53
+ }
54
+
55
+ export { Container, Section };
56
+ //# sourceMappingURL=chunk-H3NFL3GJ.js.map
57
+ //# sourceMappingURL=chunk-H3NFL3GJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/layout/section.tsx","../src/layout/container.tsx"],"names":["jsx"],"mappings":";;;AAcA,IAAM,WAAA,GAA0C;AAAA,EAC9C,OAAA,EAAS,eAAA;AAAA,EACT,KAAA,EAAO,UAAA;AAAA,EACP,OAAA,EAAS,oCAAA;AAAA,EACT,WAAA,EAAa;AACf,CAAA;AAEA,IAAM,QAAA,GAAoC;AAAA,EACxC,IAAA,EAAM,EAAA;AAAA,EACN,EAAA,EAAI,eAAA;AAAA,EACJ,EAAA,EAAI,gBAAA;AAAA,EACJ,EAAA,EAAI,gBAAA;AAAA,EACJ,EAAA,EAAI;AACN,CAAA;AAEO,SAAS,OAAA,CAAQ;AAAA,EACtB,EAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,UAAA,GAAa,SAAA;AAAA,EACb,OAAA,GAAU;AACZ,CAAA,EAAiB;AACf,EAAA,uBACE,GAAA;AAAA,IAAC,SAAA;AAAA,IAAA;AAAA,MACC,EAAA;AAAA,MACA,SAAA,EAAW,GAAG,WAAA,CAAY,UAAU,GAAG,QAAA,CAAS,OAAO,GAAG,SAAS,CAAA;AAAA,MAElE;AAAA;AAAA,GACH;AAEJ;ACjCA,IAAM,eAAA,GAA4C;AAAA,EAChD,EAAA,EAAI,iBAAA;AAAA,EACJ,EAAA,EAAI,iBAAA;AAAA,EACJ,EAAA,EAAI,iBAAA;AAAA,EACJ,EAAA,EAAI,iBAAA;AAAA,EACJ,KAAA,EAAO,kBAAA;AAAA,EACP,KAAA,EAAO,WAAA;AAAA,EACP,KAAA,EAAO,WAAA;AAAA,EACP,KAAA,EAAO,WAAA;AAAA,EACP,KAAA,EAAO,WAAA;AAAA,EACP,IAAA,EAAM;AACR,CAAA;AAEO,SAAS,SAAA,CAAU;AAAA,EACxB,QAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA,GAAW;AACb,CAAA,EAAmB;AACjB,EAAA,MAAM,cAAc,QAAA,KAAa,MAAA;AACjC,EAAA,MAAM,SAAA,GAAY,cACd,8BAAA,GACA,8BAAA;AAEJ,EAAA,uBACEA,GAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,EAAA,CAAG,SAAA,EAAW,CAAC,WAAA,IAAe,eAAA,CAAgB,QAAQ,CAAA,EAAG,SAAS,GAC/E,QAAA,EACH,CAAA;AAEJ","file":"chunk-H3NFL3GJ.js","sourcesContent":["import { cn } from \"../utils\";\r\nimport type { ReactNode } from \"react\";\r\n\r\ntype Background = \"default\" | \"muted\" | \"primary\" | \"transparent\";\r\ntype Padding = \"none\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\r\n\r\nexport interface SectionProps {\r\n id?: string;\r\n children: ReactNode;\r\n className?: string;\r\n background?: Background;\r\n padding?: Padding;\r\n}\r\n\r\nconst backgrounds: Record<Background, string> = {\r\n default: \"bg-background\",\r\n muted: \"bg-muted\",\r\n primary: \"bg-primary text-primary-foreground\",\r\n transparent: \"bg-transparent\",\r\n};\r\n\r\nconst paddings: Record<Padding, string> = {\r\n none: \"\",\r\n sm: \"py-8 md:py-12\",\r\n md: \"py-12 md:py-16\",\r\n lg: \"py-16 md:py-24\",\r\n xl: \"py-24 md:py-32\",\r\n};\r\n\r\nexport function Section({\r\n id,\r\n children,\r\n className,\r\n background = \"default\",\r\n padding = \"sm\",\r\n}: SectionProps) {\r\n return (\r\n <section\r\n id={id}\r\n className={cn(backgrounds[background], paddings[padding], className)}\r\n >\r\n {children}\r\n </section>\r\n );\r\n}\r\n","import { cn } from \"../utils\";\r\nimport type { ReactNode } from \"react\";\r\n\r\ntype MaxWidth = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\" | \"4xl\" | \"5xl\" | \"6xl\" | \"7xl\" | \"full\";\r\n\r\nexport interface ContainerProps {\r\n children: ReactNode;\r\n className?: string;\r\n maxWidth?: MaxWidth;\r\n}\r\n\r\nconst maxWidthClasses: Record<MaxWidth, string> = {\r\n sm: \"max-w-screen-sm\",\r\n md: \"max-w-screen-md\",\r\n lg: \"max-w-screen-lg\",\r\n xl: \"max-w-screen-xl\",\r\n \"2xl\": \"max-w-screen-2xl\",\r\n \"4xl\": \"max-w-4xl\",\r\n \"5xl\": \"max-w-5xl\",\r\n \"6xl\": \"max-w-6xl\",\r\n \"7xl\": \"max-w-7xl\",\r\n full: \"max-w-full\",\r\n};\r\n\r\nexport function Container({\r\n children,\r\n className,\r\n maxWidth = \"7xl\",\r\n}: ContainerProps) {\r\n const isFullWidth = maxWidth === \"full\";\r\n const baseClass = isFullWidth\r\n ? \"w-full px-4 sm:px-6 lg:px-10\"\r\n : \"mx-auto px-4 sm:px-6 lg:px-8\";\r\n\r\n return (\r\n <div className={cn(baseClass, !isFullWidth && maxWidthClasses[maxWidth], className)}>\r\n {children}\r\n </div>\r\n );\r\n}\r\n"]}
@@ -0,0 +1,293 @@
1
+ import { cn } from './chunk-GUHK2DTW.js';
2
+ import * as React from 'react';
3
+ import { memo, useMemo, useCallback } from 'react';
4
+ import { LoaderIcon } from 'lucide-react';
5
+ import { Button } from '@/components/ui/button';
6
+ import { jsx, jsxs } from 'react/jsx-runtime';
7
+ import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetFooter } from '@/components/ui/sheet';
8
+
9
+ var MOBILE_BREAKPOINT = 768;
10
+ function useIsMobile() {
11
+ const [isMobile, setIsMobile] = React.useState(void 0);
12
+ React.useEffect(() => {
13
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
14
+ const onChange = () => {
15
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
16
+ };
17
+ mql.addEventListener("change", onChange);
18
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
19
+ return () => mql.removeEventListener("change", onChange);
20
+ }, []);
21
+ return !!isMobile;
22
+ }
23
+ function ClientSubmitButton({
24
+ children,
25
+ disabled,
26
+ loading = false,
27
+ loadingText,
28
+ className,
29
+ variant,
30
+ size,
31
+ form,
32
+ ...props
33
+ }) {
34
+ const isDisabled = loading || disabled;
35
+ const content = loading && loadingText ? loadingText : children;
36
+ return /* @__PURE__ */ jsxs(
37
+ Button,
38
+ {
39
+ type: "submit",
40
+ form,
41
+ "aria-disabled": isDisabled,
42
+ "aria-busy": loading,
43
+ className: cn("relative", className),
44
+ disabled: isDisabled,
45
+ variant,
46
+ size,
47
+ ...props,
48
+ children: [
49
+ content,
50
+ loading && /* @__PURE__ */ jsx("span", { className: "animate-spin absolute right-4", children: /* @__PURE__ */ jsx(LoaderIcon, {}) }),
51
+ /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", role: "status", children: loading ? "Loading" : "Submit form" })
52
+ ]
53
+ }
54
+ );
55
+ }
56
+ var SIZE_VARIANTS = {
57
+ sm: "sm:!max-w-md",
58
+ default: "w-full sm:!max-w-md md:!max-w-lg",
59
+ lg: "w-full sm:!max-w-lg md:!max-w-2xl lg:!max-w-4xl",
60
+ xl: "w-full sm:!max-w-2xl md:!max-w-4xl lg:!max-w-5xl",
61
+ full: "w-full !max-w-full",
62
+ mobile: "w-[85%] !max-w-sm",
63
+ "mobile-nav": "!w-[300px] sm:!w-[350px]"
64
+ };
65
+ var getPadding = (size, type = "default") => {
66
+ const isFullSize = size === "full";
67
+ if (type === "header" || type === "footer") {
68
+ return isFullSize ? "px-6 lg:px-8" : "px-4";
69
+ }
70
+ return isFullSize ? "p-6 lg:p-8" : "p-4";
71
+ };
72
+ var SheetWrapper = memo(function SheetWrapper2({
73
+ open,
74
+ onOpenChange,
75
+ title,
76
+ description,
77
+ children,
78
+ footer,
79
+ header,
80
+ side = "right",
81
+ size = "default",
82
+ modal = true,
83
+ className,
84
+ headerClassName,
85
+ contentClassName,
86
+ footerClassName,
87
+ innerClassName,
88
+ hideHeader = false,
89
+ hideTitle = false,
90
+ hideDescription = false,
91
+ hideCloseButton = false,
92
+ disableContentPadding = false
93
+ }) {
94
+ const computedClasses = useMemo(
95
+ () => ({
96
+ header: cn(
97
+ "border-b pb-4 pt-6",
98
+ getPadding(size, "header"),
99
+ headerClassName
100
+ ),
101
+ inner: cn(
102
+ "flex-1 overflow-y-auto",
103
+ !disableContentPadding && getPadding(size),
104
+ innerClassName
105
+ ),
106
+ footer: cn(
107
+ "border-t bg-muted/30 pt-4 pb-6 mt-auto",
108
+ getPadding(size, "footer"),
109
+ footerClassName
110
+ )
111
+ }),
112
+ [
113
+ size,
114
+ headerClassName,
115
+ innerClassName,
116
+ footerClassName,
117
+ disableContentPadding
118
+ ]
119
+ );
120
+ const shouldHideTitle = !!header || hideTitle;
121
+ const shouldHideDescription = !!header || hideDescription;
122
+ return /* @__PURE__ */ jsx(Sheet, { open, onOpenChange, modal, children: /* @__PURE__ */ jsxs(
123
+ SheetContent,
124
+ {
125
+ side,
126
+ showCloseButton: !hideCloseButton,
127
+ className: cn(
128
+ SIZE_VARIANTS[size],
129
+ "flex flex-col p-0",
130
+ contentClassName,
131
+ className
132
+ ),
133
+ children: [
134
+ !hideHeader && /* @__PURE__ */ jsxs(SheetHeader, { className: computedClasses.header, children: [
135
+ /* @__PURE__ */ jsx(SheetTitle, { className: shouldHideTitle ? "sr-only" : "", children: title || "Sheet" }),
136
+ description && /* @__PURE__ */ jsx(
137
+ SheetDescription,
138
+ {
139
+ className: shouldHideDescription ? "sr-only" : "",
140
+ children: description
141
+ }
142
+ ),
143
+ header
144
+ ] }),
145
+ /* @__PURE__ */ jsx("div", { className: computedClasses.inner, children }),
146
+ footer && /* @__PURE__ */ jsx(SheetFooter, { className: computedClasses.footer, children: footer })
147
+ ]
148
+ }
149
+ ) });
150
+ });
151
+ var FormSheet = memo(function FormSheet2({
152
+ open,
153
+ onOpenChange,
154
+ title,
155
+ description,
156
+ children,
157
+ onSubmit,
158
+ onCancel,
159
+ submitLabel = "Submit",
160
+ cancelLabel = "Cancel",
161
+ submitDisabled = false,
162
+ submitLoading = false,
163
+ formId,
164
+ size = "lg",
165
+ ...props
166
+ }) {
167
+ const handleCancel = useCallback(() => {
168
+ onCancel?.();
169
+ onOpenChange?.(false);
170
+ }, [onCancel, onOpenChange]);
171
+ const footer = useMemo(
172
+ () => /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-2 w-full", children: [
173
+ /* @__PURE__ */ jsx(
174
+ Button,
175
+ {
176
+ type: "button",
177
+ variant: "outline",
178
+ className: "flex-1",
179
+ onClick: handleCancel,
180
+ disabled: submitDisabled || submitLoading,
181
+ children: cancelLabel
182
+ }
183
+ ),
184
+ /* @__PURE__ */ jsx(
185
+ ClientSubmitButton,
186
+ {
187
+ form: formId,
188
+ className: "flex-1",
189
+ disabled: submitDisabled,
190
+ loading: submitLoading,
191
+ loadingText: "Saving...",
192
+ children: submitLabel
193
+ }
194
+ )
195
+ ] }),
196
+ [
197
+ cancelLabel,
198
+ submitLabel,
199
+ submitDisabled,
200
+ submitLoading,
201
+ formId,
202
+ handleCancel
203
+ ]
204
+ );
205
+ return /* @__PURE__ */ jsx(
206
+ SheetWrapper,
207
+ {
208
+ open,
209
+ onOpenChange,
210
+ title,
211
+ description,
212
+ size,
213
+ footer,
214
+ ...props,
215
+ children
216
+ }
217
+ );
218
+ });
219
+ var ConfirmSheet = memo(function ConfirmSheet2({
220
+ open,
221
+ onOpenChange,
222
+ title = "Confirm Action",
223
+ description,
224
+ children,
225
+ onConfirm,
226
+ onCancel,
227
+ confirmLabel = "Confirm",
228
+ cancelLabel = "Cancel",
229
+ confirmVariant = "default",
230
+ confirmDisabled = false,
231
+ confirmLoading = false,
232
+ size = "sm",
233
+ ...props
234
+ }) {
235
+ const handleConfirm = useCallback(() => {
236
+ onConfirm?.();
237
+ }, [onConfirm]);
238
+ const handleCancel = useCallback(() => {
239
+ onCancel?.();
240
+ onOpenChange?.(false);
241
+ }, [onCancel, onOpenChange]);
242
+ const footer = useMemo(
243
+ () => /* @__PURE__ */ jsxs("div", { className: "flex gap-2 w-full", children: [
244
+ /* @__PURE__ */ jsx(
245
+ Button,
246
+ {
247
+ type: "button",
248
+ variant: "outline",
249
+ className: "flex-1",
250
+ onClick: handleCancel,
251
+ children: cancelLabel
252
+ }
253
+ ),
254
+ /* @__PURE__ */ jsx(
255
+ Button,
256
+ {
257
+ type: "button",
258
+ variant: confirmVariant,
259
+ className: "flex-1",
260
+ onClick: handleConfirm,
261
+ disabled: confirmDisabled || confirmLoading,
262
+ children: confirmLoading ? "Loading..." : confirmLabel
263
+ }
264
+ )
265
+ ] }),
266
+ [
267
+ cancelLabel,
268
+ confirmLabel,
269
+ confirmVariant,
270
+ confirmDisabled,
271
+ confirmLoading,
272
+ handleConfirm,
273
+ handleCancel
274
+ ]
275
+ );
276
+ return /* @__PURE__ */ jsx(
277
+ SheetWrapper,
278
+ {
279
+ open,
280
+ onOpenChange,
281
+ title,
282
+ description,
283
+ size,
284
+ footer,
285
+ ...props,
286
+ children
287
+ }
288
+ );
289
+ });
290
+
291
+ export { ClientSubmitButton, ConfirmSheet, FormSheet, SheetWrapper, useIsMobile };
292
+ //# sourceMappingURL=chunk-J2YRTQE4.js.map
293
+ //# sourceMappingURL=chunk-J2YRTQE4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/use-mobile.ts","../src/components/client-submit-button.tsx","../src/components/sheet-wrapper.tsx"],"names":["SheetWrapper","jsx","jsxs","FormSheet","Button","ConfirmSheet"],"mappings":"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","file":"chunk-J2YRTQE4.js","sourcesContent":["\"use client\";\r\n\r\nimport * as React from \"react\";\r\n\r\nconst MOBILE_BREAKPOINT = 768;\r\n\r\nexport function useIsMobile() {\r\n const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined);\r\n\r\n React.useEffect(() => {\r\n const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\r\n const onChange = () => {\r\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\r\n };\r\n mql.addEventListener(\"change\", onChange);\r\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\r\n return () => mql.removeEventListener(\"change\", onChange);\r\n }, []);\r\n\r\n return !!isMobile;\r\n}\r\n","\"use client\";\r\n\r\nimport { ReactNode } from \"react\";\r\nimport { LoaderIcon } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { cn } from \"../utils\";\r\n\r\nexport interface ClientSubmitButtonProps {\r\n children: ReactNode;\r\n disabled?: boolean;\r\n loading?: boolean;\r\n loadingText?: string;\r\n className?: string;\r\n variant?: \"default\" | \"destructive\" | \"outline\" | \"secondary\" | \"ghost\" | \"link\";\r\n size?: \"default\" | \"sm\" | \"lg\" | \"icon\";\r\n form?: string;\r\n}\r\n\r\nexport function ClientSubmitButton({\r\n children,\r\n disabled,\r\n loading = false,\r\n loadingText,\r\n className,\r\n variant,\r\n size,\r\n form,\r\n ...props\r\n}: ClientSubmitButtonProps) {\r\n const isDisabled = loading || disabled;\r\n const content = loading && loadingText ? loadingText : children;\r\n\r\n return (\r\n <Button\r\n type=\"submit\"\r\n form={form}\r\n aria-disabled={isDisabled}\r\n aria-busy={loading}\r\n className={cn(\"relative\", className)}\r\n disabled={isDisabled}\r\n variant={variant}\r\n size={size}\r\n {...props}\r\n >\r\n {content}\r\n {loading && (\r\n <span className=\"animate-spin absolute right-4\">\r\n <LoaderIcon />\r\n </span>\r\n )}\r\n <span aria-live=\"polite\" className=\"sr-only\" role=\"status\">\r\n {loading ? \"Loading\" : \"Submit form\"}\r\n </span>\r\n </Button>\r\n );\r\n}\r\n","\"use client\";\r\n\r\nimport { memo, useMemo, useCallback, type ReactNode } from \"react\";\r\nimport {\r\n Sheet,\r\n SheetContent,\r\n SheetHeader,\r\n SheetTitle,\r\n SheetDescription,\r\n SheetFooter,\r\n} from \"@/components/ui/sheet\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { ClientSubmitButton } from \"./client-submit-button\";\r\nimport { cn } from \"../utils\";\r\n\r\n// Size variants configuration\r\n// !important is required here because the base SheetContent uses data-attribute\r\n// selectors (data-[side=right]:sm:max-w-sm) which add CSS specificity that plain\r\n// utility classes cannot override via tailwind-merge.\r\nconst SIZE_VARIANTS = {\r\n sm: \"sm:!max-w-md\",\r\n default: \"w-full sm:!max-w-md md:!max-w-lg\",\r\n lg: \"w-full sm:!max-w-lg md:!max-w-2xl lg:!max-w-4xl\",\r\n xl: \"w-full sm:!max-w-2xl md:!max-w-4xl lg:!max-w-5xl\",\r\n full: \"w-full !max-w-full\",\r\n mobile: \"w-[85%] !max-w-sm\",\r\n \"mobile-nav\": \"!w-[300px] sm:!w-[350px]\",\r\n} as const;\r\n\r\ntype SizeVariant = keyof typeof SIZE_VARIANTS;\r\n\r\nconst getPadding = (\r\n size: SizeVariant,\r\n type: \"default\" | \"header\" | \"footer\" = \"default\",\r\n) => {\r\n const isFullSize = size === \"full\";\r\n\r\n if (type === \"header\" || type === \"footer\") {\r\n return isFullSize ? \"px-6 lg:px-8\" : \"px-4\";\r\n }\r\n\r\n return isFullSize ? \"p-6 lg:p-8\" : \"p-4\";\r\n};\r\n\r\nexport interface SheetWrapperProps {\r\n open: boolean;\r\n onOpenChange: (open: boolean) => void;\r\n title?: string;\r\n description?: string;\r\n children?: ReactNode;\r\n footer?: ReactNode;\r\n header?: ReactNode;\r\n side?: \"top\" | \"bottom\" | \"left\" | \"right\";\r\n size?: SizeVariant;\r\n modal?: boolean;\r\n className?: string;\r\n headerClassName?: string;\r\n contentClassName?: string;\r\n footerClassName?: string;\r\n innerClassName?: string;\r\n hideHeader?: boolean;\r\n hideTitle?: boolean;\r\n hideDescription?: boolean;\r\n hideCloseButton?: boolean;\r\n disableContentPadding?: boolean;\r\n}\r\n\r\nexport const SheetWrapper = memo(function SheetWrapper({\r\n open,\r\n onOpenChange,\r\n title,\r\n description,\r\n children,\r\n footer,\r\n header,\r\n side = \"right\",\r\n size = \"default\",\r\n modal = true,\r\n className,\r\n headerClassName,\r\n contentClassName,\r\n footerClassName,\r\n innerClassName,\r\n hideHeader = false,\r\n hideTitle = false,\r\n hideDescription = false,\r\n hideCloseButton = false,\r\n disableContentPadding = false,\r\n}: SheetWrapperProps) {\r\n const computedClasses = useMemo(\r\n () => ({\r\n header: cn(\r\n \"border-b pb-4 pt-6\",\r\n getPadding(size, \"header\"),\r\n headerClassName,\r\n ),\r\n inner: cn(\r\n \"flex-1 overflow-y-auto\",\r\n !disableContentPadding && getPadding(size),\r\n innerClassName,\r\n ),\r\n footer: cn(\r\n \"border-t bg-muted/30 pt-4 pb-6 mt-auto\",\r\n getPadding(size, \"footer\"),\r\n footerClassName,\r\n ),\r\n }),\r\n [\r\n size,\r\n headerClassName,\r\n innerClassName,\r\n footerClassName,\r\n disableContentPadding,\r\n ],\r\n );\r\n\r\n const shouldHideTitle = !!header || hideTitle;\r\n const shouldHideDescription = !!header || hideDescription;\r\n\r\n return (\r\n <Sheet open={open} onOpenChange={onOpenChange} modal={modal}>\r\n <SheetContent\r\n side={side}\r\n showCloseButton={!hideCloseButton}\r\n className={cn(\r\n SIZE_VARIANTS[size],\r\n \"flex flex-col p-0\",\r\n contentClassName,\r\n className,\r\n )}\r\n >\r\n {!hideHeader && (\r\n <SheetHeader className={computedClasses.header}>\r\n <SheetTitle className={shouldHideTitle ? \"sr-only\" : \"\"}>\r\n {title || \"Sheet\"}\r\n </SheetTitle>\r\n {description && (\r\n <SheetDescription\r\n className={shouldHideDescription ? \"sr-only\" : \"\"}\r\n >\r\n {description}\r\n </SheetDescription>\r\n )}\r\n {header}\r\n </SheetHeader>\r\n )}\r\n\r\n <div className={computedClasses.inner}>{children}</div>\r\n\r\n {footer && (\r\n <SheetFooter className={computedClasses.footer}>{footer}</SheetFooter>\r\n )}\r\n </SheetContent>\r\n </Sheet>\r\n );\r\n});\r\n\r\nexport interface FormSheetProps extends Omit<SheetWrapperProps, \"footer\"> {\r\n onSubmit?: () => void;\r\n onCancel?: () => void;\r\n submitLabel?: string;\r\n cancelLabel?: string;\r\n submitDisabled?: boolean;\r\n submitLoading?: boolean;\r\n formId?: string;\r\n}\r\n\r\nexport const FormSheet = memo(function FormSheet({\r\n open,\r\n onOpenChange,\r\n title,\r\n description,\r\n children,\r\n onSubmit,\r\n onCancel,\r\n submitLabel = \"Submit\",\r\n cancelLabel = \"Cancel\",\r\n submitDisabled = false,\r\n submitLoading = false,\r\n formId,\r\n size = \"lg\",\r\n ...props\r\n}: FormSheetProps) {\r\n const handleCancel = useCallback(() => {\r\n onCancel?.();\r\n onOpenChange?.(false);\r\n }, [onCancel, onOpenChange]);\r\n\r\n const footer = useMemo(\r\n () => (\r\n <div className=\"flex flex-col sm:flex-row gap-2 w-full\">\r\n <Button\r\n type=\"button\"\r\n variant=\"outline\"\r\n className=\"flex-1\"\r\n onClick={handleCancel}\r\n disabled={submitDisabled || submitLoading}\r\n >\r\n {cancelLabel}\r\n </Button>\r\n <ClientSubmitButton\r\n form={formId}\r\n className=\"flex-1\"\r\n disabled={submitDisabled}\r\n loading={submitLoading}\r\n loadingText=\"Saving...\"\r\n >\r\n {submitLabel}\r\n </ClientSubmitButton>\r\n </div>\r\n ),\r\n [\r\n cancelLabel,\r\n submitLabel,\r\n submitDisabled,\r\n submitLoading,\r\n formId,\r\n handleCancel,\r\n ],\r\n );\r\n\r\n return (\r\n <SheetWrapper\r\n open={open}\r\n onOpenChange={onOpenChange}\r\n title={title}\r\n description={description}\r\n size={size}\r\n footer={footer}\r\n {...props}\r\n >\r\n {children}\r\n </SheetWrapper>\r\n );\r\n});\r\n\r\nexport interface ConfirmSheetProps extends Omit<SheetWrapperProps, \"footer\"> {\r\n onConfirm?: () => void;\r\n onCancel?: () => void;\r\n confirmLabel?: string;\r\n cancelLabel?: string;\r\n confirmVariant?:\r\n | \"default\"\r\n | \"destructive\"\r\n | \"outline\"\r\n | \"secondary\"\r\n | \"ghost\"\r\n | \"link\";\r\n confirmDisabled?: boolean;\r\n confirmLoading?: boolean;\r\n}\r\n\r\nexport const ConfirmSheet = memo(function ConfirmSheet({\r\n open,\r\n onOpenChange,\r\n title = \"Confirm Action\",\r\n description,\r\n children,\r\n onConfirm,\r\n onCancel,\r\n confirmLabel = \"Confirm\",\r\n cancelLabel = \"Cancel\",\r\n confirmVariant = \"default\",\r\n confirmDisabled = false,\r\n confirmLoading = false,\r\n size = \"sm\",\r\n ...props\r\n}: ConfirmSheetProps) {\r\n const handleConfirm = useCallback(() => {\r\n onConfirm?.();\r\n }, [onConfirm]);\r\n\r\n const handleCancel = useCallback(() => {\r\n onCancel?.();\r\n onOpenChange?.(false);\r\n }, [onCancel, onOpenChange]);\r\n\r\n const footer = useMemo(\r\n () => (\r\n <div className=\"flex gap-2 w-full\">\r\n <Button\r\n type=\"button\"\r\n variant=\"outline\"\r\n className=\"flex-1\"\r\n onClick={handleCancel}\r\n >\r\n {cancelLabel}\r\n </Button>\r\n <Button\r\n type=\"button\"\r\n variant={confirmVariant}\r\n className=\"flex-1\"\r\n onClick={handleConfirm}\r\n disabled={confirmDisabled || confirmLoading}\r\n >\r\n {confirmLoading ? \"Loading...\" : confirmLabel}\r\n </Button>\r\n </div>\r\n ),\r\n [\r\n cancelLabel,\r\n confirmLabel,\r\n confirmVariant,\r\n confirmDisabled,\r\n confirmLoading,\r\n handleConfirm,\r\n handleCancel,\r\n ],\r\n );\r\n\r\n return (\r\n <SheetWrapper\r\n open={open}\r\n onOpenChange={onOpenChange}\r\n title={title}\r\n description={description}\r\n size={size}\r\n footer={footer}\r\n {...props}\r\n >\r\n {children}\r\n </SheetWrapper>\r\n );\r\n});\r\n"]}