@pulog/ui 0.0.0-stage → 0.1.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,3 +1,96 @@
1
- # Temporary Holding Version
1
+ # @pulog/ui
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The complete shared UI kit for TMS apps: every shadcn/Radix primitive that lived in `tms_billing/src/components/ui`
4
+ (52 files, moved verbatim), plus `PortalContainerContext`, `EmptyStateIllustration`, `StatusPage`, `Forbidden`,
5
+ `Can` and `SmartSelect`.
6
+
7
+ ```ts
8
+ import { Button, Popover, PopoverContent, PopoverTrigger } from '@pulog/ui'
9
+ import { Carousel } from '@pulog/ui/carousel' // heavy-library components have their own entry, see below
10
+ ```
11
+
12
+ ## What is in it
13
+
14
+ **Main entry `@pulog/ui`** (needs only the common peers):
15
+ `Accordion`, `Alert`, `AlertDialog`, `AspectRatio`, `Avatar`, `Badge`, `Breadcrumb`, `Button`, `ButtonGroup`, `Calendar`, `Card`,
16
+ `Checkbox`, `Collapsible`, `Command`, `ContextMenu`, `Dialog`, `DropdownMenu`, `Empty`, `Field`, `HoverCard`, `Input`, `InputGroup`,
17
+ `Item`, `Kbd`, `Label`, `Menubar`, `MonthPicker`, `NavigationMenu`, `Pagination`, `Popover`, `Progress`, `RadioGroup`, `ScrollArea`,
18
+ `Select`, `Separator`, `Sheet`, `Skeleton`, `Slider`, `Spinner`, `Switch`, `Table`, `Tabs`, `Textarea`, `Toggle`, `ToggleGroup`,
19
+ `Tooltip`, plus `PortalContainerContext` / `usePortalContainer`, `EmptyStateIllustration`, `StatusPage`, `Forbidden`, `Can`, `SmartSelect`.
20
+
21
+ **Subpath entries** - each needs an extra library that is an *optional* peer, so apps that never use the component never
22
+ install it:
23
+
24
+ | Import | Components | Optional peer |
25
+ |---|---|---|
26
+ | `@pulog/ui/carousel` | `Carousel`, ... | `embla-carousel-react` |
27
+ | `@pulog/ui/chart` | `ChartContainer`, `ChartTooltip`, ... | `recharts` |
28
+ | `@pulog/ui/drawer` | `Drawer`, ... | `vaul` |
29
+ | `@pulog/ui/input-otp` | `InputOTP`, ... | `input-otp` |
30
+ | `@pulog/ui/resizable` | `ResizablePanelGroup`, ... | `react-resizable-panels` |
31
+ | `@pulog/ui/sonner` | `Toaster` | `sonner`, `next-themes` |
32
+
33
+ All entries share one build, so shared modules (`cn`, `PortalContainerContext`, `Button`, ...) exist exactly once.
34
+ Note `sonner` is a Module Federation singleton and the **host** owns the `<Toaster>`; remotes emit through the toast
35
+ bridge (see the remote's `SONNER_MFE_SETUP.md`) - do not mount a second Toaster in a remote just because it is available here.
36
+
37
+ Required peers of the main entry: `react`, `react-dom`, `radix-ui`, `lucide-react`, `react-day-picker`, `cmdk`.
38
+ Depends on `@pulog/utils` (`cn`) and `class-variance-authority`.
39
+
40
+ ## Page-level pieces (context-free)
41
+
42
+ These came from `tms_billing` and had app-context dependencies; the package takes them as **props** instead, so it
43
+ imports nothing from the app:
44
+
45
+ | Export | Injected by the app | Notes |
46
+ |---|---|---|
47
+ | `StatusPage` | `onNavigate` (optional, shows the "Kembali ke beranda" button), `homeLabel` | 404/403 style full-page view |
48
+ | `Forbidden` | `onNavigate`, `homeLabel`, `message`, `detail` | 403 built on `StatusPage`; default texts are Indonesian |
49
+ | `Can` | `can` (required) | renders children when `can(...I)` allows (`all` = every permission). No default policy on purpose |
50
+
51
+ Apps keep tiny wrappers that bind their own context, so call sites do not change:
52
+
53
+ ```tsx
54
+ // Forbidden: onNavigate from the remote auth context
55
+ const { onNavigate } = useRemoteAuth()
56
+ return <Forbidden onNavigate={onNavigate} />
57
+ // Can: the check comes from the app's own useMenuAccess()
58
+ const { can } = useMenuAccess()
59
+ return <Can I="DELETE" can={can}>...</Can>
60
+ ```
61
+
62
+ `useMenuAccess`, `MenuAccessContext` and `lib/menu-access` (access policy, enforcement flag, role handling) stay in the app:
63
+ they read env and the remote auth context, which the package must not do. `NotFound` was not moved.
64
+
65
+ ## SmartSelect
66
+
67
+ Searchable single/multi select built on `Popover` + `Command` (moved verbatim from `tms_billing`). Fully
68
+ self-contained - it only needs props, no app context.
69
+
70
+ ```tsx
71
+ import { SmartSelect } from '@pulog/ui'
72
+
73
+ <SmartSelect options={opts} value={v} onChange={setV} placeholder="Pilih..." /> {/* local filtering */}
74
+ <SmartSelect multiple displayMode="chips" maxVisibleChips={2} value={vs} onChange={setVs} options={opts} />
75
+ <SmartSelect value={v} onChange={setV} onSearch={(q) => fetchOptions(q)} minSearchLength={2} /> {/* server search, debounced */}
76
+ ```
77
+
78
+ Props (`SmartSelectProps`): `options`, `value` (string or string[]), `onChange`, `multiple`, `placeholder`,
79
+ `disabled`, `onSearch` (async -> server mode), `displayMode` (`'count'` | `'chips'`), `maxVisibleChips`,
80
+ `minSearchLength`, `className`, `id`. Option shape: `{ label, value }` (`SmartSelectOption`). Selected labels are cached so they stay
81
+ correct when a server search replaces the option list. Default texts ("Select...") come from props, not i18n.
82
+
83
+ The `Command` / `Dialog` / `InputGroup` / `Textarea` primitives moved with it because SmartSelect depends on them.
84
+
85
+ ## Rules
86
+
87
+ - **One `PortalContainerContext` per app.** Popover, Select and DropdownMenu portal into the container this context
88
+ provides (the remote's Shadow DOM root). Apps keep `src/context/PortalContainerContext.tsx` only as a re-export of
89
+ this package's context, and provide it at the Shadow DOM root.
90
+ - No CSS ships. Apps add `@source "../node_modules/@pulog";` so Tailwind generates the classes used here.
91
+ - Every primitive lives here now. Do not fork one into an app - extend it here. Adding a primitive that needs a heavy,
92
+ rarely used library: give it its own subpath entry (see `docs/CONTRIBUTING.md`).
93
+ - Apps keep `src/components/ui/<x>.tsx` as one-line re-exports so existing imports keep working; migrate call sites to
94
+ `@pulog/ui` gradually.
95
+ - `Calendar` carries a legacy `table` class key (react-day-picker v8 name) marked `@ts-expect-error` to keep the
96
+ rendered output identical to billing's; remove it deliberately, with a visual check.
@@ -0,0 +1,16 @@
1
+ import * as class_variance_authority_types from 'class-variance-authority/types';
2
+ import * as React from 'react';
3
+ import { VariantProps } from 'class-variance-authority';
4
+
5
+ declare const buttonVariants: (props?: {
6
+ variant?: "default" | "outline" | "secondary" | "ghost" | "destructive" | "link";
7
+ size?: "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg";
8
+ } & class_variance_authority_types.ClassProp) => string;
9
+ declare const Button: React.ForwardRefExoticComponent<Omit<React.ClassAttributes<HTMLButtonElement> & React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<(props?: {
10
+ variant?: "default" | "outline" | "secondary" | "ghost" | "destructive" | "link";
11
+ size?: "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg";
12
+ } & class_variance_authority_types.ClassProp) => string> & {
13
+ asChild?: boolean;
14
+ }, "ref"> & React.RefAttributes<HTMLButtonElement>>;
15
+
16
+ export { Button as B, buttonVariants as b };
@@ -0,0 +1,34 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+ import useEmblaCarousel, { UseEmblaCarouselType } from 'embla-carousel-react';
4
+ import { Button } from './index.js';
5
+ import 'class-variance-authority/types';
6
+ import 'class-variance-authority';
7
+
8
+ type CarouselApi = UseEmblaCarouselType[1];
9
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
10
+ type CarouselOptions = UseCarouselParameters[0];
11
+ type CarouselPlugin = UseCarouselParameters[1];
12
+ type CarouselProps = {
13
+ opts?: CarouselOptions;
14
+ plugins?: CarouselPlugin;
15
+ orientation?: "horizontal" | "vertical";
16
+ setApi?: (api: CarouselApi) => void;
17
+ };
18
+ type CarouselContextProps = {
19
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0];
20
+ api: ReturnType<typeof useEmblaCarousel>[1];
21
+ scrollPrev: () => void;
22
+ scrollNext: () => void;
23
+ canScrollPrev: boolean;
24
+ canScrollNext: boolean;
25
+ } & CarouselProps;
26
+ declare function useCarousel(): CarouselContextProps;
27
+ declare function Carousel({ orientation, opts, setApi, plugins, className, children, ...props }: React.ComponentProps<"div"> & CarouselProps): react_jsx_runtime.JSX.Element;
28
+ declare function CarouselContent({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
29
+ declare function CarouselItem({ className, ...props }: React.ComponentProps<"div">): react_jsx_runtime.JSX.Element;
30
+ declare function CarouselPrevious({ className, variant, size, ...props }: React.ComponentProps<typeof Button>): react_jsx_runtime.JSX.Element;
31
+ declare function CarouselNext({ className, variant, size, ...props }: React.ComponentProps<typeof Button>): react_jsx_runtime.JSX.Element;
32
+
33
+ export { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, useCarousel };
34
+ export type { CarouselApi };
@@ -0,0 +1,196 @@
1
+ import { Button } from './chunk-LIIDSNMG.js';
2
+ import * as React from 'react';
3
+ import useEmblaCarousel from 'embla-carousel-react';
4
+ import { cn } from '@pulog/utils';
5
+ import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
6
+ import { jsx, jsxs } from 'react/jsx-runtime';
7
+
8
+ var CarouselContext = React.createContext(null);
9
+ function useCarousel() {
10
+ const context = React.useContext(CarouselContext);
11
+ if (!context) {
12
+ throw new Error("useCarousel must be used within a <Carousel />");
13
+ }
14
+ return context;
15
+ }
16
+ function Carousel({
17
+ orientation = "horizontal",
18
+ opts,
19
+ setApi,
20
+ plugins,
21
+ className,
22
+ children,
23
+ ...props
24
+ }) {
25
+ const [carouselRef, api] = useEmblaCarousel(
26
+ {
27
+ ...opts,
28
+ axis: orientation === "horizontal" ? "x" : "y"
29
+ },
30
+ plugins
31
+ );
32
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
33
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
34
+ const onSelect = React.useCallback((api2) => {
35
+ if (!api2) return;
36
+ setCanScrollPrev(api2.canScrollPrev());
37
+ setCanScrollNext(api2.canScrollNext());
38
+ }, []);
39
+ const scrollPrev = React.useCallback(() => {
40
+ api?.scrollPrev();
41
+ }, [api]);
42
+ const scrollNext = React.useCallback(() => {
43
+ api?.scrollNext();
44
+ }, [api]);
45
+ const handleKeyDown = React.useCallback(
46
+ (event) => {
47
+ if (event.key === "ArrowLeft") {
48
+ event.preventDefault();
49
+ scrollPrev();
50
+ } else if (event.key === "ArrowRight") {
51
+ event.preventDefault();
52
+ scrollNext();
53
+ }
54
+ },
55
+ [scrollPrev, scrollNext]
56
+ );
57
+ React.useEffect(() => {
58
+ if (!api || !setApi) return;
59
+ setApi(api);
60
+ }, [api, setApi]);
61
+ React.useEffect(() => {
62
+ if (!api) return;
63
+ onSelect(api);
64
+ api.on("reInit", onSelect);
65
+ api.on("select", onSelect);
66
+ return () => {
67
+ api?.off("select", onSelect);
68
+ };
69
+ }, [api, onSelect]);
70
+ return /* @__PURE__ */ jsx(
71
+ CarouselContext.Provider,
72
+ {
73
+ value: {
74
+ carouselRef,
75
+ api,
76
+ opts,
77
+ orientation: orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
78
+ scrollPrev,
79
+ scrollNext,
80
+ canScrollPrev,
81
+ canScrollNext
82
+ },
83
+ children: /* @__PURE__ */ jsx(
84
+ "div",
85
+ {
86
+ onKeyDownCapture: handleKeyDown,
87
+ className: cn("relative", className),
88
+ role: "region",
89
+ "aria-roledescription": "carousel",
90
+ "data-slot": "carousel",
91
+ ...props,
92
+ children
93
+ }
94
+ )
95
+ }
96
+ );
97
+ }
98
+ function CarouselContent({ className, ...props }) {
99
+ const { carouselRef, orientation } = useCarousel();
100
+ return /* @__PURE__ */ jsx(
101
+ "div",
102
+ {
103
+ ref: carouselRef,
104
+ className: "overflow-hidden",
105
+ "data-slot": "carousel-content",
106
+ children: /* @__PURE__ */ jsx(
107
+ "div",
108
+ {
109
+ className: cn(
110
+ "flex",
111
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
112
+ className
113
+ ),
114
+ ...props
115
+ }
116
+ )
117
+ }
118
+ );
119
+ }
120
+ function CarouselItem({ className, ...props }) {
121
+ const { orientation } = useCarousel();
122
+ return /* @__PURE__ */ jsx(
123
+ "div",
124
+ {
125
+ role: "group",
126
+ "aria-roledescription": "slide",
127
+ "data-slot": "carousel-item",
128
+ className: cn(
129
+ "min-w-0 shrink-0 grow-0 basis-full",
130
+ orientation === "horizontal" ? "pl-4" : "pt-4",
131
+ className
132
+ ),
133
+ ...props
134
+ }
135
+ );
136
+ }
137
+ function CarouselPrevious({
138
+ className,
139
+ variant = "outline",
140
+ size = "icon-sm",
141
+ ...props
142
+ }) {
143
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
144
+ return /* @__PURE__ */ jsxs(
145
+ Button,
146
+ {
147
+ "data-slot": "carousel-previous",
148
+ variant,
149
+ size,
150
+ className: cn(
151
+ "absolute touch-manipulation rounded-full",
152
+ orientation === "horizontal" ? "top-1/2 -left-12 -translate-y-1/2" : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
153
+ className
154
+ ),
155
+ disabled: !canScrollPrev,
156
+ onClick: scrollPrev,
157
+ ...props,
158
+ children: [
159
+ /* @__PURE__ */ jsx(ChevronLeftIcon, {}),
160
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Previous slide" })
161
+ ]
162
+ }
163
+ );
164
+ }
165
+ function CarouselNext({
166
+ className,
167
+ variant = "outline",
168
+ size = "icon-sm",
169
+ ...props
170
+ }) {
171
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
172
+ return /* @__PURE__ */ jsxs(
173
+ Button,
174
+ {
175
+ "data-slot": "carousel-next",
176
+ variant,
177
+ size,
178
+ className: cn(
179
+ "absolute touch-manipulation rounded-full",
180
+ orientation === "horizontal" ? "top-1/2 -right-12 -translate-y-1/2" : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
181
+ className
182
+ ),
183
+ disabled: !canScrollNext,
184
+ onClick: scrollNext,
185
+ ...props,
186
+ children: [
187
+ /* @__PURE__ */ jsx(ChevronRightIcon, {}),
188
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Next slide" })
189
+ ]
190
+ }
191
+ );
192
+ }
193
+
194
+ export { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, useCarousel };
195
+ //# sourceMappingURL=carousel.js.map
196
+ //# sourceMappingURL=carousel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/carousel.tsx"],"names":["api"],"mappings":"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","file":"carousel.js","sourcesContent":["import * as React from \"react\"\r\nimport useEmblaCarousel, {\r\n type UseEmblaCarouselType,\r\n} from \"embla-carousel-react\"\r\n\r\nimport { cn } from \"@pulog/utils\"\r\nimport { Button } from \"./button\"\r\nimport { ChevronLeftIcon, ChevronRightIcon } from \"lucide-react\"\r\n\r\ntype CarouselApi = UseEmblaCarouselType[1]\r\ntype UseCarouselParameters = Parameters<typeof useEmblaCarousel>\r\ntype CarouselOptions = UseCarouselParameters[0]\r\ntype CarouselPlugin = UseCarouselParameters[1]\r\n\r\ntype CarouselProps = {\r\n opts?: CarouselOptions\r\n plugins?: CarouselPlugin\r\n orientation?: \"horizontal\" | \"vertical\"\r\n setApi?: (api: CarouselApi) => void\r\n}\r\n\r\ntype CarouselContextProps = {\r\n carouselRef: ReturnType<typeof useEmblaCarousel>[0]\r\n api: ReturnType<typeof useEmblaCarousel>[1]\r\n scrollPrev: () => void\r\n scrollNext: () => void\r\n canScrollPrev: boolean\r\n canScrollNext: boolean\r\n} & CarouselProps\r\n\r\nconst CarouselContext = React.createContext<CarouselContextProps | null>(null)\r\n\r\nfunction useCarousel() {\r\n const context = React.useContext(CarouselContext)\r\n\r\n if (!context) {\r\n throw new Error(\"useCarousel must be used within a <Carousel />\")\r\n }\r\n\r\n return context\r\n}\r\n\r\nfunction Carousel({\r\n orientation = \"horizontal\",\r\n opts,\r\n setApi,\r\n plugins,\r\n className,\r\n children,\r\n ...props\r\n}: React.ComponentProps<\"div\"> & CarouselProps) {\r\n const [carouselRef, api] = useEmblaCarousel(\r\n {\r\n ...opts,\r\n axis: orientation === \"horizontal\" ? \"x\" : \"y\",\r\n },\r\n plugins\r\n )\r\n const [canScrollPrev, setCanScrollPrev] = React.useState(false)\r\n const [canScrollNext, setCanScrollNext] = React.useState(false)\r\n\r\n const onSelect = React.useCallback((api: CarouselApi) => {\r\n if (!api) return\r\n setCanScrollPrev(api.canScrollPrev())\r\n setCanScrollNext(api.canScrollNext())\r\n }, [])\r\n\r\n const scrollPrev = React.useCallback(() => {\r\n api?.scrollPrev()\r\n }, [api])\r\n\r\n const scrollNext = React.useCallback(() => {\r\n api?.scrollNext()\r\n }, [api])\r\n\r\n const handleKeyDown = React.useCallback(\r\n (event: React.KeyboardEvent<HTMLDivElement>) => {\r\n if (event.key === \"ArrowLeft\") {\r\n event.preventDefault()\r\n scrollPrev()\r\n } else if (event.key === \"ArrowRight\") {\r\n event.preventDefault()\r\n scrollNext()\r\n }\r\n },\r\n [scrollPrev, scrollNext]\r\n )\r\n\r\n React.useEffect(() => {\r\n if (!api || !setApi) return\r\n setApi(api)\r\n }, [api, setApi])\r\n\r\n React.useEffect(() => {\r\n if (!api) return\r\n onSelect(api)\r\n api.on(\"reInit\", onSelect)\r\n api.on(\"select\", onSelect)\r\n\r\n return () => {\r\n api?.off(\"select\", onSelect)\r\n }\r\n }, [api, onSelect])\r\n\r\n return (\r\n <CarouselContext.Provider\r\n value={{\r\n carouselRef,\r\n api: api,\r\n opts,\r\n orientation:\r\n orientation || (opts?.axis === \"y\" ? \"vertical\" : \"horizontal\"),\r\n scrollPrev,\r\n scrollNext,\r\n canScrollPrev,\r\n canScrollNext,\r\n }}\r\n >\r\n <div\r\n onKeyDownCapture={handleKeyDown}\r\n className={cn(\"relative\", className)}\r\n role=\"region\"\r\n aria-roledescription=\"carousel\"\r\n data-slot=\"carousel\"\r\n {...props}\r\n >\r\n {children}\r\n </div>\r\n </CarouselContext.Provider>\r\n )\r\n}\r\n\r\nfunction CarouselContent({ className, ...props }: React.ComponentProps<\"div\">) {\r\n const { carouselRef, orientation } = useCarousel()\r\n\r\n return (\r\n <div\r\n ref={carouselRef}\r\n className=\"overflow-hidden\"\r\n data-slot=\"carousel-content\"\r\n >\r\n <div\r\n className={cn(\r\n \"flex\",\r\n orientation === \"horizontal\" ? \"-ml-4\" : \"-mt-4 flex-col\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nfunction CarouselItem({ className, ...props }: React.ComponentProps<\"div\">) {\r\n const { orientation } = useCarousel()\r\n\r\n return (\r\n <div\r\n role=\"group\"\r\n aria-roledescription=\"slide\"\r\n data-slot=\"carousel-item\"\r\n className={cn(\r\n \"min-w-0 shrink-0 grow-0 basis-full\",\r\n orientation === \"horizontal\" ? \"pl-4\" : \"pt-4\",\r\n className\r\n )}\r\n {...props}\r\n />\r\n )\r\n}\r\n\r\nfunction CarouselPrevious({\r\n className,\r\n variant = \"outline\",\r\n size = \"icon-sm\",\r\n ...props\r\n}: React.ComponentProps<typeof Button>) {\r\n const { orientation, scrollPrev, canScrollPrev } = useCarousel()\r\n\r\n return (\r\n <Button\r\n data-slot=\"carousel-previous\"\r\n variant={variant}\r\n size={size}\r\n className={cn(\r\n \"absolute touch-manipulation rounded-full\",\r\n orientation === \"horizontal\"\r\n ? \"top-1/2 -left-12 -translate-y-1/2\"\r\n : \"-top-12 left-1/2 -translate-x-1/2 rotate-90\",\r\n className\r\n )}\r\n disabled={!canScrollPrev}\r\n onClick={scrollPrev}\r\n {...props}\r\n >\r\n <ChevronLeftIcon />\r\n <span className=\"sr-only\">Previous slide</span>\r\n </Button>\r\n )\r\n}\r\n\r\nfunction CarouselNext({\r\n className,\r\n variant = \"outline\",\r\n size = \"icon-sm\",\r\n ...props\r\n}: React.ComponentProps<typeof Button>) {\r\n const { orientation, scrollNext, canScrollNext } = useCarousel()\r\n\r\n return (\r\n <Button\r\n data-slot=\"carousel-next\"\r\n variant={variant}\r\n size={size}\r\n className={cn(\r\n \"absolute touch-manipulation rounded-full\",\r\n orientation === \"horizontal\"\r\n ? \"top-1/2 -right-12 -translate-y-1/2\"\r\n : \"-bottom-12 left-1/2 -translate-x-1/2 rotate-90\",\r\n className\r\n )}\r\n disabled={!canScrollNext}\r\n onClick={scrollNext}\r\n {...props}\r\n >\r\n <ChevronRightIcon />\r\n <span className=\"sr-only\">Next slide</span>\r\n </Button>\r\n )\r\n}\r\n\r\nexport {\r\n type CarouselApi,\r\n Carousel,\r\n CarouselContent,\r\n CarouselItem,\r\n CarouselPrevious,\r\n CarouselNext,\r\n useCarousel,\r\n}\r\n"]}
@@ -0,0 +1,48 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+ import * as RechartsPrimitive from 'recharts';
4
+ import { TooltipValueType } from 'recharts';
5
+
6
+ declare const THEMES: {
7
+ readonly light: "";
8
+ readonly dark: ".dark";
9
+ };
10
+ type TooltipNameType = number | string;
11
+ type ChartConfig = Record<string, {
12
+ label?: React.ReactNode;
13
+ icon?: React.ComponentType;
14
+ } & ({
15
+ color?: string;
16
+ theme?: never;
17
+ } | {
18
+ color?: never;
19
+ theme: Record<keyof typeof THEMES, string>;
20
+ })>;
21
+ declare function ChartContainer({ id, className, children, config, initialDimension, ...props }: React.ComponentProps<"div"> & {
22
+ config: ChartConfig;
23
+ children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>["children"];
24
+ initialDimension?: {
25
+ width: number;
26
+ height: number;
27
+ };
28
+ }): react_jsx_runtime.JSX.Element;
29
+ declare const ChartStyle: ({ id, config }: {
30
+ id: string;
31
+ config: ChartConfig;
32
+ }) => react_jsx_runtime.JSX.Element;
33
+ declare const ChartTooltip: typeof RechartsPrimitive.Tooltip;
34
+ declare function ChartTooltipContent({ active, payload, className, indicator, hideLabel, hideIndicator, label, labelFormatter, labelClassName, formatter, color, nameKey, labelKey, }: React.ComponentProps<typeof RechartsPrimitive.Tooltip> & React.ComponentProps<"div"> & {
35
+ hideLabel?: boolean;
36
+ hideIndicator?: boolean;
37
+ indicator?: "line" | "dot" | "dashed";
38
+ nameKey?: string;
39
+ labelKey?: string;
40
+ } & Omit<RechartsPrimitive.DefaultTooltipContentProps<TooltipValueType, TooltipNameType>, "accessibilityLayer">): react_jsx_runtime.JSX.Element;
41
+ declare const ChartLegend: React.MemoExoticComponent<(outsideProps: RechartsPrimitive.LegendProps) => React.ReactPortal | null>;
42
+ declare function ChartLegendContent({ className, hideIcon, payload, verticalAlign, nameKey, }: React.ComponentProps<"div"> & {
43
+ hideIcon?: boolean;
44
+ nameKey?: string;
45
+ } & RechartsPrimitive.DefaultLegendContentProps): react_jsx_runtime.JSX.Element;
46
+
47
+ export { ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent };
48
+ export type { ChartConfig };
package/dist/chart.js ADDED
@@ -0,0 +1,248 @@
1
+ import * as React from 'react';
2
+ import * as RechartsPrimitive from 'recharts';
3
+ import { cn } from '@pulog/utils';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+
6
+ // src/chart.tsx
7
+ var THEMES = { light: "", dark: ".dark" };
8
+ var INITIAL_DIMENSION = { width: 320, height: 200 };
9
+ var ChartContext = React.createContext(null);
10
+ function useChart() {
11
+ const context = React.useContext(ChartContext);
12
+ if (!context) {
13
+ throw new Error("useChart must be used within a <ChartContainer />");
14
+ }
15
+ return context;
16
+ }
17
+ function ChartContainer({
18
+ id,
19
+ className,
20
+ children,
21
+ config,
22
+ initialDimension = INITIAL_DIMENSION,
23
+ ...props
24
+ }) {
25
+ const uniqueId = React.useId();
26
+ const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}`;
27
+ return /* @__PURE__ */ jsx(ChartContext.Provider, { value: { config }, children: /* @__PURE__ */ jsxs(
28
+ "div",
29
+ {
30
+ "data-slot": "chart",
31
+ "data-chart": chartId,
32
+ className: cn(
33
+ "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
34
+ className
35
+ ),
36
+ ...props,
37
+ children: [
38
+ /* @__PURE__ */ jsx(ChartStyle, { id: chartId, config }),
39
+ /* @__PURE__ */ jsx(
40
+ RechartsPrimitive.ResponsiveContainer,
41
+ {
42
+ initialDimension,
43
+ children
44
+ }
45
+ )
46
+ ]
47
+ }
48
+ ) });
49
+ }
50
+ var ChartStyle = ({ id, config }) => {
51
+ const colorConfig = Object.entries(config).filter(
52
+ ([, config2]) => config2.theme ?? config2.color
53
+ );
54
+ if (!colorConfig.length) {
55
+ return null;
56
+ }
57
+ return /* @__PURE__ */ jsx(
58
+ "style",
59
+ {
60
+ dangerouslySetInnerHTML: {
61
+ __html: Object.entries(THEMES).map(
62
+ ([theme, prefix]) => `
63
+ ${prefix} [data-chart=${id}] {
64
+ ${colorConfig.map(([key, itemConfig]) => {
65
+ const color = itemConfig.theme?.[theme] ?? itemConfig.color;
66
+ return color ? ` --color-${key}: ${color};` : null;
67
+ }).join("\n")}
68
+ }
69
+ `
70
+ ).join("\n")
71
+ }
72
+ }
73
+ );
74
+ };
75
+ var ChartTooltip = RechartsPrimitive.Tooltip;
76
+ function ChartTooltipContent({
77
+ active,
78
+ payload,
79
+ className,
80
+ indicator = "dot",
81
+ hideLabel = false,
82
+ hideIndicator = false,
83
+ label,
84
+ labelFormatter,
85
+ labelClassName,
86
+ formatter,
87
+ color,
88
+ nameKey,
89
+ labelKey
90
+ }) {
91
+ const { config } = useChart();
92
+ const tooltipLabel = React.useMemo(() => {
93
+ if (hideLabel || !payload?.length) {
94
+ return null;
95
+ }
96
+ const [item] = payload;
97
+ const key = `${labelKey ?? item?.dataKey ?? item?.name ?? "value"}`;
98
+ const itemConfig = getPayloadConfigFromPayload(config, item, key);
99
+ const value = !labelKey && typeof label === "string" ? config[label]?.label ?? label : itemConfig?.label;
100
+ if (labelFormatter) {
101
+ return /* @__PURE__ */ jsx("div", { className: cn("font-medium", labelClassName), children: labelFormatter(value, payload) });
102
+ }
103
+ if (!value) {
104
+ return null;
105
+ }
106
+ return /* @__PURE__ */ jsx("div", { className: cn("font-medium", labelClassName), children: value });
107
+ }, [
108
+ label,
109
+ labelFormatter,
110
+ payload,
111
+ hideLabel,
112
+ labelClassName,
113
+ config,
114
+ labelKey
115
+ ]);
116
+ if (!active || !payload?.length) {
117
+ return null;
118
+ }
119
+ const nestLabel = payload.length === 1 && indicator !== "dot";
120
+ return /* @__PURE__ */ jsxs(
121
+ "div",
122
+ {
123
+ className: cn(
124
+ "grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
125
+ className
126
+ ),
127
+ children: [
128
+ !nestLabel ? tooltipLabel : null,
129
+ /* @__PURE__ */ jsx("div", { className: "grid gap-1.5", children: payload.filter((item) => item.type !== "none").map((item, index) => {
130
+ const key = `${nameKey ?? item.name ?? item.dataKey ?? "value"}`;
131
+ const itemConfig = getPayloadConfigFromPayload(config, item, key);
132
+ const indicatorColor = color ?? item.payload?.fill ?? item.color;
133
+ return /* @__PURE__ */ jsx(
134
+ "div",
135
+ {
136
+ className: cn(
137
+ "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
138
+ indicator === "dot" && "items-center"
139
+ ),
140
+ children: formatter && item?.value !== void 0 && item.name ? formatter(item.value, item.name, item, index, item.payload) : /* @__PURE__ */ jsxs(Fragment, { children: [
141
+ itemConfig?.icon ? /* @__PURE__ */ jsx(itemConfig.icon, {}) : !hideIndicator && /* @__PURE__ */ jsx(
142
+ "div",
143
+ {
144
+ className: cn(
145
+ "shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
146
+ {
147
+ "h-2.5 w-2.5": indicator === "dot",
148
+ "w-1": indicator === "line",
149
+ "w-0 border-[1.5px] border-dashed bg-transparent": indicator === "dashed",
150
+ "my-0.5": nestLabel && indicator === "dashed"
151
+ }
152
+ ),
153
+ style: {
154
+ "--color-bg": indicatorColor,
155
+ "--color-border": indicatorColor
156
+ }
157
+ }
158
+ ),
159
+ /* @__PURE__ */ jsxs(
160
+ "div",
161
+ {
162
+ className: cn(
163
+ "flex flex-1 justify-between leading-none",
164
+ nestLabel ? "items-end" : "items-center"
165
+ ),
166
+ children: [
167
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-1.5", children: [
168
+ nestLabel ? tooltipLabel : null,
169
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: itemConfig?.label ?? item.name })
170
+ ] }),
171
+ item.value != null && /* @__PURE__ */ jsx("span", { className: "font-mono font-medium text-foreground tabular-nums", children: typeof item.value === "number" ? item.value.toLocaleString() : String(item.value) })
172
+ ]
173
+ }
174
+ )
175
+ ] })
176
+ },
177
+ index
178
+ );
179
+ }) })
180
+ ]
181
+ }
182
+ );
183
+ }
184
+ var ChartLegend = RechartsPrimitive.Legend;
185
+ function ChartLegendContent({
186
+ className,
187
+ hideIcon = false,
188
+ payload,
189
+ verticalAlign = "bottom",
190
+ nameKey
191
+ }) {
192
+ const { config } = useChart();
193
+ if (!payload?.length) {
194
+ return null;
195
+ }
196
+ return /* @__PURE__ */ jsx(
197
+ "div",
198
+ {
199
+ className: cn(
200
+ "flex items-center justify-center gap-4",
201
+ verticalAlign === "top" ? "pb-3" : "pt-3",
202
+ className
203
+ ),
204
+ children: payload.filter((item) => item.type !== "none").map((item, index) => {
205
+ const key = `${nameKey ?? item.dataKey ?? "value"}`;
206
+ const itemConfig = getPayloadConfigFromPayload(config, item, key);
207
+ return /* @__PURE__ */ jsxs(
208
+ "div",
209
+ {
210
+ className: cn(
211
+ "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
212
+ ),
213
+ children: [
214
+ itemConfig?.icon && !hideIcon ? /* @__PURE__ */ jsx(itemConfig.icon, {}) : /* @__PURE__ */ jsx(
215
+ "div",
216
+ {
217
+ className: "h-2 w-2 shrink-0 rounded-[2px]",
218
+ style: {
219
+ backgroundColor: item.color
220
+ }
221
+ }
222
+ ),
223
+ itemConfig?.label
224
+ ]
225
+ },
226
+ index
227
+ );
228
+ })
229
+ }
230
+ );
231
+ }
232
+ function getPayloadConfigFromPayload(config, payload, key) {
233
+ if (typeof payload !== "object" || payload === null) {
234
+ return void 0;
235
+ }
236
+ const payloadPayload = "payload" in payload && typeof payload.payload === "object" && payload.payload !== null ? payload.payload : void 0;
237
+ let configLabelKey = key;
238
+ if (key in payload && typeof payload[key] === "string") {
239
+ configLabelKey = payload[key];
240
+ } else if (payloadPayload && key in payloadPayload && typeof payloadPayload[key] === "string") {
241
+ configLabelKey = payloadPayload[key];
242
+ }
243
+ return configLabelKey in config ? config[configLabelKey] : config[key];
244
+ }
245
+
246
+ export { ChartContainer, ChartLegend, ChartLegendContent, ChartStyle, ChartTooltip, ChartTooltipContent };
247
+ //# sourceMappingURL=chart.js.map
248
+ //# sourceMappingURL=chart.js.map