@optilogic/core 1.3.7 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1002,17 +1002,39 @@ interface ModalProps {
1002
1002
  */
1003
1003
  footer?: React$1.ReactNode;
1004
1004
  /**
1005
- * Size variant
1005
+ * Size variant. Maps to a `max-width` on the modal frame. For arbitrary
1006
+ * widths, use `contentClassName` to override.
1006
1007
  */
1007
- size?: "sm" | "md" | "lg";
1008
+ size?: "sm" | "md" | "lg" | "xl" | "2xl" | "full";
1008
1009
  /**
1009
1010
  * Z-index for stacking modals (default: 50)
1010
1011
  */
1011
1012
  zIndex?: number;
1012
1013
  /**
1013
- * Additional class names for modal content
1014
+ * Additional class names for the modal body (the scrollable content area).
1014
1015
  */
1015
1016
  className?: string;
1017
+ /**
1018
+ * Additional class names for the modal frame (the outer card containing
1019
+ * header, body, and footer). Merges with the `size` class — pass width
1020
+ * utilities here to override the named size, e.g. `"max-w-[1200px]"` or
1021
+ * `"w-[80vw] max-w-none"`.
1022
+ */
1023
+ contentClassName?: string;
1024
+ /**
1025
+ * Opt into mobile-friendly rendering. When `true`, the modal renders full-
1026
+ * screen (no border, no rounded corners, fills the viewport) under
1027
+ * `@media (pointer: coarse) and (hover: none)` — i.e. on touch devices
1028
+ * without hover. On desktop / hybrid input devices the named `size` is
1029
+ * preserved. Default: `false`.
1030
+ */
1031
+ responsive?: boolean;
1032
+ /**
1033
+ * Force mobile (full-screen) rendering regardless of device. Useful for
1034
+ * Storybook, manual QA, and the rare case where sheet-style rendering is
1035
+ * desired on desktop. Overrides `responsive`. Default: `false`.
1036
+ */
1037
+ forceMobile?: boolean;
1016
1038
  }
1017
1039
  /**
1018
1040
  * Modal component
@@ -1033,8 +1055,31 @@ interface ModalProps {
1033
1055
  * <Button variant="primary" onClick={handleConfirm}>Confirm</Button>
1034
1056
  * </footer>
1035
1057
  * </Modal>
1058
+ *
1059
+ * @example
1060
+ * // Custom width override
1061
+ * <Modal
1062
+ * isOpen={open}
1063
+ * onClose={() => setOpen(false)}
1064
+ * title="Report"
1065
+ * contentClassName="max-w-[1200px]"
1066
+ * >
1067
+ * ...
1068
+ * </Modal>
1069
+ *
1070
+ * @example
1071
+ * // Full-screen on mobile (touch devices), `lg` width on desktop
1072
+ * <Modal
1073
+ * isOpen={open}
1074
+ * onClose={() => setOpen(false)}
1075
+ * title="New Database"
1076
+ * size="lg"
1077
+ * responsive
1078
+ * >
1079
+ * ...
1080
+ * </Modal>
1036
1081
  */
1037
- declare function Modal({ isOpen, onClose, title, children, footer, size, zIndex, className, }: ModalProps): react_jsx_runtime.JSX.Element | null;
1082
+ declare function Modal({ isOpen, onClose, title, children, footer, size, zIndex, className, contentClassName, responsive, forceMobile, }: ModalProps): react_jsx_runtime.JSX.Element | null;
1038
1083
  /**
1039
1084
  * ModalButton component
1040
1085
  * @deprecated Use Button component from @optilogic/core instead
package/dist/index.d.ts CHANGED
@@ -1002,17 +1002,39 @@ interface ModalProps {
1002
1002
  */
1003
1003
  footer?: React$1.ReactNode;
1004
1004
  /**
1005
- * Size variant
1005
+ * Size variant. Maps to a `max-width` on the modal frame. For arbitrary
1006
+ * widths, use `contentClassName` to override.
1006
1007
  */
1007
- size?: "sm" | "md" | "lg";
1008
+ size?: "sm" | "md" | "lg" | "xl" | "2xl" | "full";
1008
1009
  /**
1009
1010
  * Z-index for stacking modals (default: 50)
1010
1011
  */
1011
1012
  zIndex?: number;
1012
1013
  /**
1013
- * Additional class names for modal content
1014
+ * Additional class names for the modal body (the scrollable content area).
1014
1015
  */
1015
1016
  className?: string;
1017
+ /**
1018
+ * Additional class names for the modal frame (the outer card containing
1019
+ * header, body, and footer). Merges with the `size` class — pass width
1020
+ * utilities here to override the named size, e.g. `"max-w-[1200px]"` or
1021
+ * `"w-[80vw] max-w-none"`.
1022
+ */
1023
+ contentClassName?: string;
1024
+ /**
1025
+ * Opt into mobile-friendly rendering. When `true`, the modal renders full-
1026
+ * screen (no border, no rounded corners, fills the viewport) under
1027
+ * `@media (pointer: coarse) and (hover: none)` — i.e. on touch devices
1028
+ * without hover. On desktop / hybrid input devices the named `size` is
1029
+ * preserved. Default: `false`.
1030
+ */
1031
+ responsive?: boolean;
1032
+ /**
1033
+ * Force mobile (full-screen) rendering regardless of device. Useful for
1034
+ * Storybook, manual QA, and the rare case where sheet-style rendering is
1035
+ * desired on desktop. Overrides `responsive`. Default: `false`.
1036
+ */
1037
+ forceMobile?: boolean;
1016
1038
  }
1017
1039
  /**
1018
1040
  * Modal component
@@ -1033,8 +1055,31 @@ interface ModalProps {
1033
1055
  * <Button variant="primary" onClick={handleConfirm}>Confirm</Button>
1034
1056
  * </footer>
1035
1057
  * </Modal>
1058
+ *
1059
+ * @example
1060
+ * // Custom width override
1061
+ * <Modal
1062
+ * isOpen={open}
1063
+ * onClose={() => setOpen(false)}
1064
+ * title="Report"
1065
+ * contentClassName="max-w-[1200px]"
1066
+ * >
1067
+ * ...
1068
+ * </Modal>
1069
+ *
1070
+ * @example
1071
+ * // Full-screen on mobile (touch devices), `lg` width on desktop
1072
+ * <Modal
1073
+ * isOpen={open}
1074
+ * onClose={() => setOpen(false)}
1075
+ * title="New Database"
1076
+ * size="lg"
1077
+ * responsive
1078
+ * >
1079
+ * ...
1080
+ * </Modal>
1036
1081
  */
1037
- declare function Modal({ isOpen, onClose, title, children, footer, size, zIndex, className, }: ModalProps): react_jsx_runtime.JSX.Element | null;
1082
+ declare function Modal({ isOpen, onClose, title, children, footer, size, zIndex, className, contentClassName, responsive, forceMobile, }: ModalProps): react_jsx_runtime.JSX.Element | null;
1038
1083
  /**
1039
1084
  * ModalButton component
1040
1085
  * @deprecated Use Button component from @optilogic/core instead
package/dist/index.js CHANGED
@@ -2091,7 +2091,10 @@ function Modal({
2091
2091
  footer,
2092
2092
  size = "md",
2093
2093
  zIndex = 50,
2094
- className
2094
+ className,
2095
+ contentClassName,
2096
+ responsive = false,
2097
+ forceMobile = false
2095
2098
  }) {
2096
2099
  React20.useEffect(() => {
2097
2100
  if (!isOpen) return;
@@ -2117,12 +2120,23 @@ function Modal({
2117
2120
  const sizeClasses = {
2118
2121
  sm: "max-w-md",
2119
2122
  md: "max-w-lg",
2120
- lg: "max-w-2xl"
2123
+ lg: "max-w-2xl",
2124
+ xl: "max-w-4xl",
2125
+ "2xl": "max-w-6xl",
2126
+ full: "max-w-[95vw]"
2121
2127
  };
2128
+ const responsiveOuter = "[@media(pointer:coarse)and(hover:none)]:!p-0";
2129
+ const responsiveFrame = "[@media(pointer:coarse)and(hover:none)]:!w-screen [@media(pointer:coarse)and(hover:none)]:!h-[100dvh] [@media(pointer:coarse)and(hover:none)]:!max-h-[100dvh] [@media(pointer:coarse)and(hover:none)]:!max-w-none [@media(pointer:coarse)and(hover:none)]:!rounded-none [@media(pointer:coarse)and(hover:none)]:!border-0";
2130
+ const forcedOuter = "!p-0";
2131
+ const forcedFrame = "!w-screen !h-[100dvh] !max-h-[100dvh] !max-w-none !rounded-none !border-0";
2122
2132
  return /* @__PURE__ */ jsxs(
2123
2133
  "div",
2124
2134
  {
2125
- className: "fixed inset-0 flex items-center justify-center p-4",
2135
+ className: cn(
2136
+ "fixed inset-0 flex items-center justify-center p-4",
2137
+ responsive && responsiveOuter,
2138
+ forceMobile && forcedOuter
2139
+ ),
2126
2140
  style: { zIndex },
2127
2141
  onClick: onClose,
2128
2142
  children: [
@@ -2135,7 +2149,10 @@ function Modal({
2135
2149
  "bg-card border border-border rounded-lg shadow-lg",
2136
2150
  "flex flex-col",
2137
2151
  "max-h-[90vh]",
2138
- sizeClasses[size]
2152
+ sizeClasses[size],
2153
+ responsive && responsiveFrame,
2154
+ forceMobile && forcedFrame,
2155
+ contentClassName
2139
2156
  ),
2140
2157
  onClick: (e) => e.stopPropagation(),
2141
2158
  children: [
@@ -2180,6 +2197,27 @@ function ModalButton({
2180
2197
  }
2181
2198
  );
2182
2199
  }
2200
+ var mobileSheetContentClasses = [
2201
+ "[@media(pointer:coarse)and(hover:none)]:!top-auto",
2202
+ "[@media(pointer:coarse)and(hover:none)]:!bottom-0",
2203
+ "[@media(pointer:coarse)and(hover:none)]:!left-0",
2204
+ "[@media(pointer:coarse)and(hover:none)]:!translate-x-0",
2205
+ "[@media(pointer:coarse)and(hover:none)]:!translate-y-0",
2206
+ "[@media(pointer:coarse)and(hover:none)]:!w-screen",
2207
+ "[@media(pointer:coarse)and(hover:none)]:!max-w-none",
2208
+ "[@media(pointer:coarse)and(hover:none)]:!rounded-t-xl",
2209
+ "[@media(pointer:coarse)and(hover:none)]:!rounded-b-none",
2210
+ "[@media(pointer:coarse)and(hover:none)]:!pb-[calc(1.5rem+env(safe-area-inset-bottom))]"
2211
+ ].join(" ");
2212
+ var mobileSheetFooterClasses = [
2213
+ // Stack buttons full-width with comfortable tap targets on touch devices.
2214
+ "[@media(pointer:coarse)and(hover:none)]:!flex-col",
2215
+ "[@media(pointer:coarse)and(hover:none)]:!gap-2"
2216
+ ].join(" ");
2217
+ var mobileSheetActionClasses = [
2218
+ "[@media(pointer:coarse)and(hover:none)]:!w-full",
2219
+ "[@media(pointer:coarse)and(hover:none)]:!min-h-11"
2220
+ ].join(" ");
2183
2221
  function ConfirmationModal({
2184
2222
  open,
2185
2223
  onOpenChange,
@@ -2199,18 +2237,28 @@ function ConfirmationModal({
2199
2237
  onConfirm();
2200
2238
  onOpenChange(false);
2201
2239
  };
2202
- return /* @__PURE__ */ jsx(AlertDialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(AlertDialogContent, { children: [
2240
+ return /* @__PURE__ */ jsx(AlertDialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(AlertDialogContent, { className: mobileSheetContentClasses, children: [
2203
2241
  /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
2204
2242
  /* @__PURE__ */ jsx(AlertDialogTitle, { children: title }),
2205
2243
  /* @__PURE__ */ jsx(AlertDialogDescription, { children: description })
2206
2244
  ] }),
2207
- /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [
2208
- /* @__PURE__ */ jsx(AlertDialogCancel, { onClick: handleCancel, children: cancelLabel }),
2245
+ /* @__PURE__ */ jsxs(AlertDialogFooter, { className: mobileSheetFooterClasses, children: [
2246
+ /* @__PURE__ */ jsx(
2247
+ AlertDialogCancel,
2248
+ {
2249
+ onClick: handleCancel,
2250
+ className: mobileSheetActionClasses,
2251
+ children: cancelLabel
2252
+ }
2253
+ ),
2209
2254
  /* @__PURE__ */ jsx(
2210
2255
  AlertDialogAction,
2211
2256
  {
2212
2257
  onClick: handleConfirm,
2213
- className: destructive ? "bg-destructive text-destructive-foreground hover:bg-destructive/90" : void 0,
2258
+ className: [
2259
+ destructive ? "bg-destructive text-destructive-foreground hover:bg-destructive/90" : "",
2260
+ mobileSheetActionClasses
2261
+ ].filter(Boolean).join(" "),
2214
2262
  children: confirmLabel
2215
2263
  }
2216
2264
  )