@martinsura/ui 0.1.2 → 0.1.3

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.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { cva } from 'class-variance-authority';
2
2
  import { twMerge } from 'tailwind-merge';
3
- import { IconDownload, IconPaperclip, IconFileSpreadsheet, IconX, IconTrashX, IconPlus, IconPencil, IconDotsVertical, IconEyeOff, IconEye, IconCheck, IconDroplet, IconDatabaseOff, IconChevronUp, IconChevronDown, IconSelector, IconCalendar, IconUsers, IconInfoCircle, IconAlertTriangle, IconCircleX, IconBold, IconItalic, IconUnderline, IconList, IconListNumbers, IconLink, IconUnlink, IconClearFormatting, IconUpload, IconAlertCircle, IconTrash, IconLoader2, IconChevronLeft, IconChevronRight, IconSearch } from '@tabler/icons-react';
3
+ import { IconEyeOff, IconEye, IconDownload, IconPaperclip, IconFileSpreadsheet, IconX, IconTrashX, IconPlus, IconPencil, IconDotsVertical, IconCheck, IconDroplet, IconDatabaseOff, IconChevronUp, IconChevronDown, IconSelector, IconCalendar, IconUsers, IconInfoCircle, IconAlertTriangle, IconCircleX, IconBold, IconItalic, IconUnderline, IconList, IconListNumbers, IconLink, IconUnlink, IconClearFormatting, IconUpload, IconAlertCircle, IconTrash, IconLoader2, IconChevronLeft, IconChevronRight, IconSearch } from '@tabler/icons-react';
4
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
- import { createContext, useRef, useState, useLayoutEffect, useEffect, useMemo, useContext, useCallback, useId } from 'react';
5
+ import { createContext, forwardRef, useState, useContext, useRef, useLayoutEffect, useEffect, useMemo, useCallback, useId } from 'react';
6
6
  import { createPortal } from 'react-dom';
7
7
  import dayjs from 'dayjs';
8
8
 
@@ -411,12 +411,12 @@ var ErrorContext = createContext({
411
411
  var ErrorProvider = ({ resolveInputError, resolveServerError, children }) => /* @__PURE__ */ jsx(ErrorContext.Provider, { value: { resolveInputError, resolveServerError }, children });
412
412
  var useErrorResolver = () => useContext(ErrorContext).resolveInputError;
413
413
  var useServerError = () => useContext(ErrorContext).resolveServerError;
414
- var TextInput = ({
414
+ var TextInput = forwardRef(({
415
415
  size = "middle",
416
416
  password = false,
417
417
  newPassword = false,
418
418
  ...props
419
- }) => {
419
+ }, ref) => {
420
420
  const [showPassword, setShowPassword] = useState(false);
421
421
  const resolveError = useErrorResolver();
422
422
  const resolvedErrors = props.errorName ? resolveError(props.errorName) : [];
@@ -437,6 +437,7 @@ var TextInput = ({
437
437
  /* @__PURE__ */ jsx(
438
438
  "input",
439
439
  {
440
+ ref,
440
441
  type: inputType,
441
442
  autoComplete: newPassword ? "new-password" : void 0,
442
443
  placeholder: props.placeholder,
@@ -470,7 +471,8 @@ var TextInput = ({
470
471
  ] }),
471
472
  errorDisplay && /* @__PURE__ */ jsx(InputError, { error: String(errorDisplay), className: props.classNames?.error })
472
473
  ] });
473
- };
474
+ });
475
+ TextInput.displayName = "TextInput";
474
476
  var numberInputClass = inputBaseClass + " [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none";
475
477
  var NumberInput = ({
476
478
  size = "middle",
@@ -645,7 +647,7 @@ var SwitchInput = ({
645
647
 
646
648
  // src/floating/layerStack.ts
647
649
  var FLOATING_ROOT_SELECTOR = "[data-ui-floating-root]";
648
- var DRAWER_ROOT_SELECTOR = "[data-ui-drawer-root]";
650
+ var LAYER_ROOT_SELECTOR = "[data-ui-layer-root]";
649
651
  function getTopmostElement(selector) {
650
652
  const elements = document.querySelectorAll(selector);
651
653
  return elements.length > 0 ? elements[elements.length - 1] : null;
@@ -656,8 +658,8 @@ function hasFloatingRootOpen() {
656
658
  function isTopmostFloatingRoot(element) {
657
659
  return element !== null && getTopmostElement(FLOATING_ROOT_SELECTOR) === element;
658
660
  }
659
- function isTopmostDrawerRoot(element) {
660
- return element !== null && getTopmostElement(DRAWER_ROOT_SELECTOR) === element;
661
+ function isTopmostLayerRoot(element) {
662
+ return element !== null && getTopmostElement(LAYER_ROOT_SELECTOR) === element;
661
663
  }
662
664
  function isTargetInsideFloatingRoot(target) {
663
665
  return target instanceof Element && target.closest(FLOATING_ROOT_SELECTOR) !== null;
@@ -2888,7 +2890,7 @@ var Drawer = ({ placement = "top", ...props }) => {
2888
2890
  if (isTargetInsideFloatingRoot(e.target)) {
2889
2891
  return;
2890
2892
  }
2891
- if (!isTopmostDrawerRoot(panelRef.current)) {
2893
+ if (!isTopmostLayerRoot(panelRef.current)) {
2892
2894
  return;
2893
2895
  }
2894
2896
  props.onClose(false);
@@ -2929,7 +2931,7 @@ var Drawer = ({ placement = "top", ...props }) => {
2929
2931
  "div",
2930
2932
  {
2931
2933
  ref: panelRef,
2932
- "data-ui-drawer-root": "",
2934
+ "data-ui-layer-root": "",
2933
2935
  className: twMerge(
2934
2936
  panelBase[placement],
2935
2937
  "bg-white shadow-xl transition-transform duration-220 ease-out",
@@ -3041,6 +3043,256 @@ var DrawerContent = ({ loading = false, children }) => {
3041
3043
  }
3042
3044
  return /* @__PURE__ */ jsx(Fragment, { children });
3043
3045
  };
3046
+ var ModalContext = createContext(null);
3047
+ function useModal() {
3048
+ const ctx = useContext(ModalContext);
3049
+ if (!ctx) {
3050
+ throw new Error("useModal must be used inside Modal");
3051
+ }
3052
+ return ctx;
3053
+ }
3054
+ var TRANSITION_MS2 = 220;
3055
+ var sizeClass = {
3056
+ small: "w-full max-w-lg",
3057
+ middle: "w-full max-w-3xl",
3058
+ large: "w-full max-w-5xl",
3059
+ auto: "w-auto"
3060
+ };
3061
+ var Modal = ({
3062
+ placement = "center",
3063
+ size = "middle",
3064
+ width,
3065
+ maxWidth,
3066
+ closeOnEscape = true,
3067
+ closeOnOverlayClick = true,
3068
+ showCloseButton = true,
3069
+ hideHeader = false,
3070
+ destroyOnClose = true,
3071
+ preventClose = false,
3072
+ beforeClose,
3073
+ initialFocusRef,
3074
+ ...props
3075
+ }) => {
3076
+ const [rendered, setRendered] = useState(props.isOpen);
3077
+ const [visible, setVisible] = useState(false);
3078
+ const [title, setTitle] = useState("");
3079
+ const [footer, setFooter] = useState(null);
3080
+ const [loading, setLoading] = useState(false);
3081
+ const frameRef = useRef(0);
3082
+ const panelRef = useRef(null);
3083
+ useEffect(() => {
3084
+ if (props.isOpen) {
3085
+ setRendered(true);
3086
+ frameRef.current = requestAnimationFrame(() => {
3087
+ frameRef.current = requestAnimationFrame(() => setVisible(true));
3088
+ });
3089
+ } else {
3090
+ setVisible(false);
3091
+ if (!destroyOnClose) {
3092
+ return () => cancelAnimationFrame(frameRef.current);
3093
+ }
3094
+ const t = setTimeout(() => setRendered(false), TRANSITION_MS2);
3095
+ return () => clearTimeout(t);
3096
+ }
3097
+ return () => cancelAnimationFrame(frameRef.current);
3098
+ }, [destroyOnClose, props.isOpen]);
3099
+ useEffect(() => {
3100
+ if (!props.isOpen) {
3101
+ return;
3102
+ }
3103
+ const focusTarget = initialFocusRef?.current;
3104
+ if (!focusTarget) {
3105
+ return;
3106
+ }
3107
+ const timeout = window.setTimeout(() => focusTarget.focus(), TRANSITION_MS2 / 2);
3108
+ return () => window.clearTimeout(timeout);
3109
+ }, [initialFocusRef, props.isOpen]);
3110
+ useEffect(() => {
3111
+ if (!props.isOpen) {
3112
+ return;
3113
+ }
3114
+ const handleKeyDown = (e) => {
3115
+ if (!closeOnEscape || e.key !== "Escape" || e.defaultPrevented) {
3116
+ return;
3117
+ }
3118
+ if (hasFloatingRootOpen() || isTargetInsideFloatingRoot(e.target)) {
3119
+ return;
3120
+ }
3121
+ if (!isTopmostLayerRoot(panelRef.current)) {
3122
+ return;
3123
+ }
3124
+ void requestClose();
3125
+ };
3126
+ document.body.style.overflow = "hidden";
3127
+ document.addEventListener("keydown", handleKeyDown);
3128
+ return () => {
3129
+ document.body.style.overflow = "";
3130
+ document.removeEventListener("keydown", handleKeyDown);
3131
+ };
3132
+ }, [closeOnEscape, props.isOpen]);
3133
+ if (!rendered) {
3134
+ return null;
3135
+ }
3136
+ const requestClose = async () => {
3137
+ if (preventClose) {
3138
+ return;
3139
+ }
3140
+ const canClose = await beforeClose?.();
3141
+ if (canClose === false) {
3142
+ return;
3143
+ }
3144
+ props.onClose(false);
3145
+ };
3146
+ const close = () => {
3147
+ void requestClose();
3148
+ };
3149
+ const saveAndClose = (message) => {
3150
+ if (message) {
3151
+ notification.success(message);
3152
+ }
3153
+ props.onClose(true);
3154
+ };
3155
+ const hasFooter = footer !== null;
3156
+ const isFullscreen = placement === "fullscreen";
3157
+ return createPortal(
3158
+ /* @__PURE__ */ jsxs(ModalContext.Provider, { value: { close, saveAndClose, setTitle, setFooter, setLoading }, children: [
3159
+ /* @__PURE__ */ jsx(
3160
+ "div",
3161
+ {
3162
+ className: twMerge(
3163
+ "fixed inset-0 z-1000 bg-black/40 transition-opacity duration-220",
3164
+ visible ? "opacity-100" : "opacity-0",
3165
+ !props.isOpen && "pointer-events-none",
3166
+ props.classNames?.overlay
3167
+ ),
3168
+ onClick: () => closeOnOverlayClick && close()
3169
+ }
3170
+ ),
3171
+ /* @__PURE__ */ jsx(
3172
+ "div",
3173
+ {
3174
+ className: twMerge(
3175
+ "fixed inset-0 z-1001 flex p-4 transition-all duration-220",
3176
+ isFullscreen ? "items-stretch justify-stretch p-0" : "items-center justify-center",
3177
+ !props.isOpen && "pointer-events-none"
3178
+ ),
3179
+ children: /* @__PURE__ */ jsxs(
3180
+ "div",
3181
+ {
3182
+ ref: panelRef,
3183
+ "data-ui-layer-root": "",
3184
+ role: "dialog",
3185
+ "aria-modal": "true",
3186
+ "aria-hidden": !props.isOpen,
3187
+ style: !isFullscreen ? { width, maxWidth } : void 0,
3188
+ className: twMerge(
3189
+ "bg-white shadow-xl flex flex-col transition-all duration-220 ease-out",
3190
+ isFullscreen ? "h-full w-full rounded-none" : `${sizeClass[size]} max-h-[calc(100dvh-32px)] rounded-(--ui-radius-lg)`,
3191
+ visible ? "scale-100 opacity-100" : "scale-95 opacity-0",
3192
+ props.className,
3193
+ props.classNames?.panel
3194
+ ),
3195
+ onClick: (e) => e.stopPropagation(),
3196
+ children: [
3197
+ !hideHeader && /* @__PURE__ */ jsxs(
3198
+ "div",
3199
+ {
3200
+ className: twMerge(
3201
+ "flex items-center justify-between gap-2 bg-(--ui-primary) shrink-0 rounded-t-(--ui-radius-lg)",
3202
+ isFullscreen && "rounded-none",
3203
+ drawerLayoutClasses.header,
3204
+ props.classNames?.header
3205
+ ),
3206
+ children: [
3207
+ /* @__PURE__ */ jsx("span", { className: twMerge(componentTitleClasses.inverse, "truncate", props.classNames?.title), children: title }),
3208
+ showCloseButton && /* @__PURE__ */ jsx(
3209
+ "button",
3210
+ {
3211
+ type: "button",
3212
+ onClick: close,
3213
+ className: twMerge(
3214
+ "shrink-0 text-(--ui-primary-text)/70 hover:text-(--ui-primary-text) cursor-pointer transition-colors",
3215
+ props.classNames?.closeButton
3216
+ ),
3217
+ children: /* @__PURE__ */ jsx(IconX, { size: 18, strokeWidth: 1.5 })
3218
+ }
3219
+ )
3220
+ ]
3221
+ }
3222
+ ),
3223
+ /* @__PURE__ */ jsx(
3224
+ "div",
3225
+ {
3226
+ className: twMerge(
3227
+ "overflow-y-auto flex-1",
3228
+ isFullscreen ? "min-h-0" : "max-h-[calc(100dvh-120px)]",
3229
+ drawerLayoutClasses.body,
3230
+ props.classNames?.body
3231
+ ),
3232
+ children: loading ? /* @__PURE__ */ jsx("div", { className: twMerge("flex items-center justify-center py-16", props.classNames?.loading), children: /* @__PURE__ */ jsx(Spinner, { size: "large", color: "primary" }) }) : props.children
3233
+ }
3234
+ ),
3235
+ hasFooter && /* @__PURE__ */ jsx(
3236
+ "div",
3237
+ {
3238
+ className: twMerge(
3239
+ "border-t border-(--ui-border) shrink-0 bg-white",
3240
+ isFullscreen ? "rounded-none" : "rounded-b-(--ui-radius-lg)",
3241
+ drawerLayoutClasses.footer,
3242
+ props.classNames?.footer
3243
+ ),
3244
+ children: footer
3245
+ }
3246
+ )
3247
+ ]
3248
+ }
3249
+ )
3250
+ }
3251
+ )
3252
+ ] }),
3253
+ document.body
3254
+ );
3255
+ };
3256
+ var ModalTitle = ({ children }) => {
3257
+ const { setTitle } = useModal();
3258
+ useEffect(() => {
3259
+ setTitle(String(children));
3260
+ return () => setTitle("");
3261
+ }, [children]);
3262
+ return null;
3263
+ };
3264
+ var alignClass2 = {
3265
+ left: "justify-start",
3266
+ center: "justify-center",
3267
+ right: "justify-end"
3268
+ };
3269
+ var ModalFooter = ({ children, align = "right" }) => {
3270
+ const { setFooter } = useModal();
3271
+ useEffect(() => {
3272
+ setFooter(
3273
+ /* @__PURE__ */ jsx("div", { className: twMerge("flex items-center gap-2", alignClass2[align]), children })
3274
+ );
3275
+ return () => setFooter(null);
3276
+ }, [align, children]);
3277
+ return null;
3278
+ };
3279
+ var ModalSkeleton = () => /* @__PURE__ */ jsxs("div", { className: "animate-pulse space-y-3", children: [
3280
+ /* @__PURE__ */ jsx("div", { className: "h-4 bg-(--ui-surface-muted) rounded w-1/3" }),
3281
+ /* @__PURE__ */ jsx("div", { className: "h-8 bg-(--ui-surface-muted) rounded" }),
3282
+ /* @__PURE__ */ jsx("div", { className: "h-4 bg-(--ui-surface-muted) rounded w-1/3 mt-5" }),
3283
+ /* @__PURE__ */ jsx("div", { className: "h-8 bg-(--ui-surface-muted) rounded" }),
3284
+ /* @__PURE__ */ jsx("div", { className: "h-4 bg-(--ui-surface-muted) rounded w-1/3 mt-5" }),
3285
+ /* @__PURE__ */ jsx("div", { className: "h-8 bg-(--ui-surface-muted) rounded" })
3286
+ ] });
3287
+ var ModalContent = ({ loading = false, children }) => {
3288
+ if (loading) {
3289
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
3290
+ /* @__PURE__ */ jsx(ModalSkeleton, {}),
3291
+ /* @__PURE__ */ jsx(Skeleton, { active: true, title: false, paragraph: { rows: 2 } })
3292
+ ] });
3293
+ }
3294
+ return /* @__PURE__ */ jsx(Fragment, { children });
3295
+ };
3044
3296
  var Alert = ({ type = "info", message, description, closable = false, className, classNames }) => {
3045
3297
  const [closed, setClosed] = useState(false);
3046
3298
  if (closed) {
@@ -4250,6 +4502,6 @@ function initUI(config) {
4250
4502
  r.style.setProperty("--ui-empty-compact-description", layout.empty.compactDescription);
4251
4503
  }
4252
4504
 
4253
- export { ActiveTag, Alert, Avatar, Badge, Button, CheckboxInput, ColorInput, DateInput, Drawer, DrawerContent, DrawerFooter, DrawerTitle, Dropdown, Empty, ErrorProvider, FilterCheckboxInput, FilterDateInput, FilterDateRangePopover, FilterNumberInput, FilterSelectGroupPopover, FilterSelectInput, FilterTextInput, FlagTag, Grid, GridFilters, HtmlInput, InputError, InputField, InputLabel, MultiSelectInput, NotificationProvider, NumberInput, Panel, Popconfirm, Pretty, SelectInput, ServerError, Skeleton, SortDirection, Spinner, SwitchInput, Table, Tabs, Tag, TextInput, Tooltip, UploadInput, UploadProvider, getIcon, initUI, notification, registerIcons, uiTheme, useDrawer, useGrid, useNotification, useUploadConfig };
4505
+ export { ActiveTag, Alert, Avatar, Badge, Button, CheckboxInput, ColorInput, DateInput, Drawer, DrawerContent, DrawerFooter, DrawerTitle, Dropdown, Empty, ErrorProvider, FilterCheckboxInput, FilterDateInput, FilterDateRangePopover, FilterNumberInput, FilterSelectGroupPopover, FilterSelectInput, FilterTextInput, FlagTag, Grid, GridFilters, HtmlInput, InputError, InputField, InputLabel, Modal, ModalContent, ModalFooter, ModalTitle, MultiSelectInput, NotificationProvider, NumberInput, Panel, Popconfirm, Pretty, SelectInput, ServerError, Skeleton, SortDirection, Spinner, SwitchInput, Table, Tabs, Tag, TextInput, Tooltip, UploadInput, UploadProvider, getIcon, initUI, notification, registerIcons, uiTheme, useDrawer, useGrid, useModal, useNotification, useUploadConfig };
4254
4506
  //# sourceMappingURL=index.js.map
4255
4507
  //# sourceMappingURL=index.js.map