@memelabui/ui 0.9.0 → 0.10.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.cjs CHANGED
@@ -342,7 +342,7 @@ var sizeClass3 = {
342
342
  lg: "p-2.5 w-11 h-11"
343
343
  };
344
344
  var variantClass2 = {
345
- primary: "bg-primary text-white shadow-glow hover:brightness-[0.98]",
345
+ primary: "bg-primary text-white shadow-glow hover:shadow-glow-lg hover:scale-[1.02]",
346
346
  success: "bg-emerald-600 text-white shadow-lg shadow-emerald-600/20 hover:bg-emerald-700",
347
347
  warning: "bg-amber-600 text-white shadow-lg shadow-amber-600/20 hover:bg-amber-700",
348
348
  danger: "bg-rose-600 text-white shadow-lg shadow-rose-600/20 hover:bg-rose-700",
@@ -2045,6 +2045,7 @@ var Card = React.forwardRef(function Card2({ hoverable, variant = "surface", pad
2045
2045
  }
2046
2046
  );
2047
2047
  });
2048
+ var EXIT_DURATION = 200;
2048
2049
  function Modal({
2049
2050
  isOpen,
2050
2051
  onClose,
@@ -2060,6 +2061,23 @@ function Modal({
2060
2061
  }) {
2061
2062
  const dialogRef = React.useRef(null);
2062
2063
  const lastActiveElementRef = React.useRef(null);
2064
+ const [mounted, setMounted] = React.useState(false);
2065
+ const [closing, setClosing] = React.useState(false);
2066
+ const closingTimerRef = React.useRef();
2067
+ React.useEffect(() => {
2068
+ if (isOpen) {
2069
+ setClosing(false);
2070
+ setMounted(true);
2071
+ clearTimeout(closingTimerRef.current);
2072
+ } else if (mounted) {
2073
+ setClosing(true);
2074
+ closingTimerRef.current = setTimeout(() => {
2075
+ setMounted(false);
2076
+ setClosing(false);
2077
+ }, EXIT_DURATION);
2078
+ }
2079
+ return () => clearTimeout(closingTimerRef.current);
2080
+ }, [isOpen]);
2063
2081
  React.useEffect(() => {
2064
2082
  if (!isOpen) return;
2065
2083
  lastActiveElementRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
@@ -2076,20 +2094,28 @@ function Modal({
2076
2094
  if (lastActive?.isConnected) focusSafely(lastActive);
2077
2095
  };
2078
2096
  }, [isOpen]);
2079
- useScrollLock(isOpen);
2080
- if (!isOpen) return null;
2097
+ useScrollLock(mounted);
2098
+ const handleClose = React.useCallback(() => {
2099
+ if (closing) return;
2100
+ onClose();
2101
+ }, [closing, onClose]);
2102
+ if (!mounted) return null;
2081
2103
  return /* @__PURE__ */ jsxRuntime.jsx(
2082
2104
  "div",
2083
2105
  {
2106
+ "data-closing": closing || void 0,
2084
2107
  className: cn(
2085
- "fixed inset-0 flex items-end sm:items-center justify-center p-4 pb-safe bg-black/25 backdrop-blur-sm animate-modal-backdrop",
2108
+ "fixed inset-0 flex items-end sm:items-center justify-center p-4 pb-safe bg-black/25 backdrop-blur-sm",
2086
2109
  zIndexClassName,
2087
2110
  overlayClassName
2088
2111
  ),
2112
+ style: {
2113
+ animation: closing ? `ml-modal-backdrop-out ${EXIT_DURATION}ms ease forwards` : "ml-modal-backdrop 200ms ease forwards"
2114
+ },
2089
2115
  role: "presentation",
2090
2116
  onMouseDown: (e) => {
2091
2117
  if (!closeOnBackdrop) return;
2092
- if (e.target === e.currentTarget) onClose();
2118
+ if (e.target === e.currentTarget) handleClose();
2093
2119
  },
2094
2120
  children: /* @__PURE__ */ jsxRuntime.jsx(
2095
2121
  "div",
@@ -2101,16 +2127,19 @@ function Modal({
2101
2127
  tabIndex: -1,
2102
2128
  ref: dialogRef,
2103
2129
  className: cn(
2104
- "w-full rounded-t-3xl sm:rounded-2xl shadow-xl ring-1 ring-white/10 animate-modal-pop focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
2130
+ "w-full rounded-t-3xl sm:rounded-2xl shadow-xl ring-1 ring-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
2105
2131
  useGlass && "glass bg-surface-50/80",
2106
2132
  contentClassName
2107
2133
  ),
2134
+ style: {
2135
+ animation: closing ? `ml-modal-pop-out ${EXIT_DURATION}ms ease forwards` : "ml-modal-pop 200ms ease forwards"
2136
+ },
2108
2137
  onMouseDown: (e) => e.stopPropagation(),
2109
2138
  onKeyDownCapture: (e) => {
2110
2139
  if (closeOnEsc && e.key === "Escape") {
2111
2140
  e.preventDefault();
2112
2141
  e.stopPropagation();
2113
- onClose();
2142
+ handleClose();
2114
2143
  return;
2115
2144
  }
2116
2145
  if (e.key !== "Tab") return;
@@ -4891,7 +4920,10 @@ function ToastProvider({ children, position = "top-right", maxToasts = 5 }) {
4891
4920
  /* @__PURE__ */ jsxRuntime.jsx(
4892
4921
  "div",
4893
4922
  {
4923
+ role: "region",
4894
4924
  "aria-label": "Notifications",
4925
+ "aria-live": "polite",
4926
+ "aria-relevant": "additions text",
4895
4927
  className: cn("fixed z-[9999] flex flex-col gap-3 pointer-events-none", positionClass2[position]),
4896
4928
  children: toasts.map((t) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsxRuntime.jsx(ToastCard, { toast: t, onDismiss: dismiss }) }, t.id))
4897
4929
  }
package/dist/index.js CHANGED
@@ -336,7 +336,7 @@ var sizeClass3 = {
336
336
  lg: "p-2.5 w-11 h-11"
337
337
  };
338
338
  var variantClass2 = {
339
- primary: "bg-primary text-white shadow-glow hover:brightness-[0.98]",
339
+ primary: "bg-primary text-white shadow-glow hover:shadow-glow-lg hover:scale-[1.02]",
340
340
  success: "bg-emerald-600 text-white shadow-lg shadow-emerald-600/20 hover:bg-emerald-700",
341
341
  warning: "bg-amber-600 text-white shadow-lg shadow-amber-600/20 hover:bg-amber-700",
342
342
  danger: "bg-rose-600 text-white shadow-lg shadow-rose-600/20 hover:bg-rose-700",
@@ -2039,6 +2039,7 @@ var Card = forwardRef(function Card2({ hoverable, variant = "surface", padding =
2039
2039
  }
2040
2040
  );
2041
2041
  });
2042
+ var EXIT_DURATION = 200;
2042
2043
  function Modal({
2043
2044
  isOpen,
2044
2045
  onClose,
@@ -2054,6 +2055,23 @@ function Modal({
2054
2055
  }) {
2055
2056
  const dialogRef = useRef(null);
2056
2057
  const lastActiveElementRef = useRef(null);
2058
+ const [mounted, setMounted] = useState(false);
2059
+ const [closing, setClosing] = useState(false);
2060
+ const closingTimerRef = useRef();
2061
+ useEffect(() => {
2062
+ if (isOpen) {
2063
+ setClosing(false);
2064
+ setMounted(true);
2065
+ clearTimeout(closingTimerRef.current);
2066
+ } else if (mounted) {
2067
+ setClosing(true);
2068
+ closingTimerRef.current = setTimeout(() => {
2069
+ setMounted(false);
2070
+ setClosing(false);
2071
+ }, EXIT_DURATION);
2072
+ }
2073
+ return () => clearTimeout(closingTimerRef.current);
2074
+ }, [isOpen]);
2057
2075
  useEffect(() => {
2058
2076
  if (!isOpen) return;
2059
2077
  lastActiveElementRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
@@ -2070,20 +2088,28 @@ function Modal({
2070
2088
  if (lastActive?.isConnected) focusSafely(lastActive);
2071
2089
  };
2072
2090
  }, [isOpen]);
2073
- useScrollLock(isOpen);
2074
- if (!isOpen) return null;
2091
+ useScrollLock(mounted);
2092
+ const handleClose = useCallback(() => {
2093
+ if (closing) return;
2094
+ onClose();
2095
+ }, [closing, onClose]);
2096
+ if (!mounted) return null;
2075
2097
  return /* @__PURE__ */ jsx(
2076
2098
  "div",
2077
2099
  {
2100
+ "data-closing": closing || void 0,
2078
2101
  className: cn(
2079
- "fixed inset-0 flex items-end sm:items-center justify-center p-4 pb-safe bg-black/25 backdrop-blur-sm animate-modal-backdrop",
2102
+ "fixed inset-0 flex items-end sm:items-center justify-center p-4 pb-safe bg-black/25 backdrop-blur-sm",
2080
2103
  zIndexClassName,
2081
2104
  overlayClassName
2082
2105
  ),
2106
+ style: {
2107
+ animation: closing ? `ml-modal-backdrop-out ${EXIT_DURATION}ms ease forwards` : "ml-modal-backdrop 200ms ease forwards"
2108
+ },
2083
2109
  role: "presentation",
2084
2110
  onMouseDown: (e) => {
2085
2111
  if (!closeOnBackdrop) return;
2086
- if (e.target === e.currentTarget) onClose();
2112
+ if (e.target === e.currentTarget) handleClose();
2087
2113
  },
2088
2114
  children: /* @__PURE__ */ jsx(
2089
2115
  "div",
@@ -2095,16 +2121,19 @@ function Modal({
2095
2121
  tabIndex: -1,
2096
2122
  ref: dialogRef,
2097
2123
  className: cn(
2098
- "w-full rounded-t-3xl sm:rounded-2xl shadow-xl ring-1 ring-white/10 animate-modal-pop focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
2124
+ "w-full rounded-t-3xl sm:rounded-2xl shadow-xl ring-1 ring-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
2099
2125
  useGlass && "glass bg-surface-50/80",
2100
2126
  contentClassName
2101
2127
  ),
2128
+ style: {
2129
+ animation: closing ? `ml-modal-pop-out ${EXIT_DURATION}ms ease forwards` : "ml-modal-pop 200ms ease forwards"
2130
+ },
2102
2131
  onMouseDown: (e) => e.stopPropagation(),
2103
2132
  onKeyDownCapture: (e) => {
2104
2133
  if (closeOnEsc && e.key === "Escape") {
2105
2134
  e.preventDefault();
2106
2135
  e.stopPropagation();
2107
- onClose();
2136
+ handleClose();
2108
2137
  return;
2109
2138
  }
2110
2139
  if (e.key !== "Tab") return;
@@ -4885,7 +4914,10 @@ function ToastProvider({ children, position = "top-right", maxToasts = 5 }) {
4885
4914
  /* @__PURE__ */ jsx(
4886
4915
  "div",
4887
4916
  {
4917
+ role: "region",
4888
4918
  "aria-label": "Notifications",
4919
+ "aria-live": "polite",
4920
+ "aria-relevant": "additions text",
4889
4921
  className: cn("fixed z-[9999] flex flex-col gap-3 pointer-events-none", positionClass2[position]),
4890
4922
  children: toasts.map((t) => /* @__PURE__ */ jsx("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx(ToastCard, { toast: t, onDismiss: dismiss }) }, t.id))
4891
4923
  }
@@ -62,6 +62,14 @@
62
62
  --ml-transition-fast: 150ms ease;
63
63
  --ml-transition-normal: 200ms ease;
64
64
  --ml-transition-slow: 300ms ease;
65
+
66
+ /* ---- Animation durations ---- */
67
+ --ml-anim-fast: 200ms;
68
+ --ml-anim-normal: 300ms;
69
+ --ml-anim-slow: 500ms;
70
+
71
+ /* ---- Layout ---- */
72
+ --ml-header-height: 4rem;
65
73
  }
66
74
  @keyframes ml-float {
67
75
  0%,
@@ -120,6 +128,24 @@
120
128
  transform: translateY(0) scale(1);
121
129
  }
122
130
  }
131
+ @keyframes ml-modal-backdrop-out {
132
+ from {
133
+ opacity: 1;
134
+ }
135
+ to {
136
+ opacity: 0;
137
+ }
138
+ }
139
+ @keyframes ml-modal-pop-out {
140
+ from {
141
+ opacity: 1;
142
+ transform: translateY(0) scale(1);
143
+ }
144
+ to {
145
+ opacity: 0;
146
+ transform: translateY(6px) scale(0.98);
147
+ }
148
+ }
123
149
  @keyframes ml-shimmer {
124
150
  0% {
125
151
  transform: translateX(-200%);
@@ -149,7 +175,8 @@
149
175
  [class*='animate-modal'],
150
176
  [class*='animate-shimmer'],
151
177
  [class*='animate-spin'],
152
- [class*='animate-pulse'] {
178
+ [class*='animate-pulse'],
179
+ [data-closing] {
153
180
  animation-duration: 0.01ms;
154
181
  animation-iteration-count: 1;
155
182
  transition-duration: 0.01ms;
@@ -664,6 +691,12 @@ a {
664
691
  0 1px 3px rgba(0, 0, 0, 0.35),
665
692
  inset 0 0 0 1px var(--ml-glass-border, rgba(255, 255, 255, 0.1));
666
693
  }
694
+ .glass:hover {
695
+ background: var(--ml-glass-bg-hover, rgba(255, 255, 255, 0.07));
696
+ box-shadow:
697
+ 0 1px 3px rgba(0, 0, 0, 0.35),
698
+ inset 0 0 0 1px var(--ml-glass-border-hover, rgba(102, 126, 234, 0.25));
699
+ }
667
700
  /* Surface card */
668
701
  .surface {
669
702
  border-radius: var(--ml-radius-md, 0.75rem);
@@ -677,9 +710,12 @@ a {
677
710
  );
678
711
  }
679
712
  .surface-hover {
680
- transition: box-shadow var(--ml-transition-fast, 150ms ease);
713
+ transition:
714
+ box-shadow var(--ml-transition-fast, 150ms ease),
715
+ transform var(--ml-transition-fast, 150ms ease);
681
716
  }
682
717
  .surface-hover:hover {
718
+ transform: translateY(-1px);
683
719
  box-shadow: var(
684
720
  --ml-shadow-surface-hover,
685
721
  0 10px 25px rgba(0, 0, 0, 0.45),
@@ -1420,30 +1456,6 @@ a {
1420
1456
  .animate-\[ml-shimmer_2s_ease-in-out_infinite\] {
1421
1457
  animation: ml-shimmer 2s ease-in-out infinite;
1422
1458
  }
1423
- @keyframes ml-modal-backdrop {
1424
- from {
1425
- opacity: 0;
1426
- }
1427
- to {
1428
- opacity: 1;
1429
- }
1430
- }
1431
- .animate-modal-backdrop {
1432
- animation: ml-modal-backdrop 140ms ease-out both;
1433
- }
1434
- @keyframes ml-modal-pop {
1435
- from {
1436
- opacity: 0;
1437
- transform: translateY(6px) scale(0.98);
1438
- }
1439
- to {
1440
- opacity: 1;
1441
- transform: translateY(0) scale(1);
1442
- }
1443
- }
1444
- .animate-modal-pop {
1445
- animation: ml-modal-pop 160ms cubic-bezier(0.22,1,0.36,1) both;
1446
- }
1447
1459
  @keyframes ping {
1448
1460
  75%, 100% {
1449
1461
  transform: scale(2);
@@ -2734,10 +2746,6 @@ a {
2734
2746
  .hover\:ring-white\/20:hover {
2735
2747
  --tw-ring-color: rgb(255 255 255 / 0.2);
2736
2748
  }
2737
- .hover\:brightness-\[0\.98\]:hover {
2738
- --tw-brightness: brightness(0.98);
2739
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
2740
- }
2741
2749
  .focus\:outline-none:focus {
2742
2750
  outline: 2px solid transparent;
2743
2751
  outline-offset: 2px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memelabui/ui",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "MemeLab shared UI component library — React + Tailwind + Glassmorphism",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -46,6 +46,20 @@
46
46
  "dist",
47
47
  "README.md"
48
48
  ],
49
+ "scripts": {
50
+ "dev": "storybook dev -p 6006",
51
+ "build": "tsup && pnpm build:css",
52
+ "build:css": "postcss src/styles/index.css -o dist/styles/index.css",
53
+ "build:storybook": "storybook build -o storybook-static",
54
+ "test": "vitest",
55
+ "test:ci": "vitest --run",
56
+ "typecheck": "tsc --noEmit",
57
+ "lint": "eslint src --ext ts,tsx --report-unused-disable-directives",
58
+ "lint:fix": "eslint src --ext ts,tsx --fix",
59
+ "format": "prettier --check \"src/**/*.{ts,tsx,css}\"",
60
+ "format:fix": "prettier --write \"src/**/*.{ts,tsx,css}\"",
61
+ "prepublishOnly": "pnpm build"
62
+ },
49
63
  "peerDependencies": {
50
64
  "react": "^18.0.0 || ^19.0.0",
51
65
  "react-dom": "^18.0.0 || ^19.0.0",
@@ -67,7 +81,7 @@
67
81
  "@typescript-eslint/parser": "^8.22.0",
68
82
  "@vitejs/plugin-react": "^4.3.4",
69
83
  "autoprefixer": "^10.4.20",
70
- "eslint": "^9.19.0",
84
+ "eslint": "^10.0.2",
71
85
  "eslint-plugin-react": "^7.37.4",
72
86
  "eslint-plugin-react-hooks": "^5.1.0",
73
87
  "jsdom": "^25.0.1",
@@ -88,6 +102,10 @@
88
102
  "access": "public",
89
103
  "registry": "https://registry.npmjs.org/"
90
104
  },
105
+ "packageManager": "pnpm@9.15.0",
106
+ "engines": {
107
+ "node": ">=22"
108
+ },
91
109
  "license": "MIT",
92
110
  "repository": {
93
111
  "type": "git",
@@ -101,18 +119,5 @@
101
119
  "glassmorphism",
102
120
  "dark-theme",
103
121
  "component-library"
104
- ],
105
- "scripts": {
106
- "dev": "storybook dev -p 6006",
107
- "build": "tsup && pnpm build:css",
108
- "build:css": "postcss src/styles/index.css -o dist/styles/index.css",
109
- "build:storybook": "storybook build -o storybook-static",
110
- "test": "vitest",
111
- "test:ci": "vitest --run",
112
- "typecheck": "tsc --noEmit",
113
- "lint": "eslint src --ext ts,tsx --report-unused-disable-directives",
114
- "lint:fix": "eslint src --ext ts,tsx --fix",
115
- "format": "prettier --check \"src/**/*.{ts,tsx,css}\"",
116
- "format:fix": "prettier --write \"src/**/*.{ts,tsx,css}\""
117
- }
118
- }
122
+ ]
123
+ }