@evinvest/uikit 0.15.1 → 0.16.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
@@ -215,9 +215,19 @@ measuring needs host-only `web-sys`). Known gaps:
215
215
  onto an arbitrary child (no `Slot`), so it publishes them as a
216
216
  `FormControlContext` that `Input`/`Textarea` consume; wrap a bare element and
217
217
  you wire them yourself.
218
- - **resizable / carousel / drawer:** pointer-drag physics are TS-only (keyboard
219
- in Rust for resizable; prev/next + keyboard for carousel; click-to-dismiss for
220
- drawer). Embla momentum / vaul drag-to-dismiss are not reproduced.
218
+ - **resizable / carousel:** pointer-drag physics are TS-only (keyboard in Rust
219
+ for resizable; prev/next + keyboard for carousel). Embla momentum is not
220
+ reproduced.
221
+ - **drawer:** the Vaul-style enter/exit motion is shared by both ports via
222
+ `motion.css` (inlined into `tokens.css`), keyed on `data-slot` +
223
+ `data-vaul-drawer-direction` + `data-state`; the panel stays mounted on close
224
+ until its exit `transitionend`. Drag-to-dismiss (Vaul's pointer physics, from
225
+ any of the four edges; opt a region out with `data-vaul-no-drag`) is TS-only;
226
+ Rust dismisses on scrim click / Escape / `DrawerClose`. The panel is
227
+ `touch-action: none` so a touch never turns into a page pan, which also means
228
+ the panel itself must not scroll: the kit renders children inside a
229
+ `data-slot="drawer-body"` scroller (`overflow-y-auto`), so cap the sheet with
230
+ `max-h-*` on `DrawerContent` and let the body scroll by itself.
221
231
  - **sidebar:** the mobile-sheet integration, cookie persistence, and keyboard
222
232
  shortcut are omitted.
223
233
  - **brand chrome (header / footer / status pages):** TS routes links through an
package/dist/index.d.ts CHANGED
@@ -168,9 +168,9 @@ declare function mergeRefs<T>(...refs: Array<React.Ref<T> | undefined>): React.R
168
168
 
169
169
  declare const alertVariants: {
170
170
  readonly neutral: "bg-card text-ink";
171
- readonly destructive: "text-accent-error bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-accent-error/90";
172
- readonly success: "text-positive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-positive/90";
173
- readonly info: "text-accent-info bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-accent-info/90";
171
+ readonly destructive: "text-accent-error bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-ink-mid";
172
+ readonly success: "text-positive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-ink-mid";
173
+ readonly info: "text-accent-info bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-ink-mid";
174
174
  };
175
175
  type AlertVariant = keyof typeof alertVariants;
176
176
 
@@ -1242,10 +1242,10 @@ declare function SheetTitle({ className, ...props }: React.ComponentProps<"h2">)
1242
1242
  declare function SheetDescription({ className, ...props }: React.ComponentProps<"p">): React.JSX.Element;
1243
1243
 
1244
1244
  declare const drawerDirectionClasses: {
1245
- readonly bottom: "data-[state=open]:slide-in-from-bottom data-[state=closed]:slide-out-to-bottom inset-x-0 bottom-0 mt-24 max-h-[80vh] flex-col rounded-t-lg border-b-0";
1246
- readonly top: "data-[state=open]:slide-in-from-top data-[state=closed]:slide-out-to-top inset-x-0 top-0 mb-24 max-h-[80vh] flex-col rounded-b-lg border-t-0";
1247
- readonly left: "data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left inset-y-0 left-0 w-3/4 flex-row border-r sm:max-w-sm";
1248
- readonly right: "data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right inset-y-0 right-0 w-3/4 flex-row border-l sm:max-w-sm";
1245
+ readonly bottom: "inset-x-0 bottom-0 mt-24 max-h-[80vh] flex-col rounded-t-lg border-b-0";
1246
+ readonly top: "inset-x-0 top-0 mb-24 max-h-[80vh] flex-col rounded-b-lg border-t-0";
1247
+ readonly left: "inset-y-0 left-0 w-3/4 flex-row border-r sm:max-w-sm";
1248
+ readonly right: "inset-y-0 right-0 w-3/4 flex-row border-l sm:max-w-sm";
1249
1249
  };
1250
1250
  type DrawerDirection = keyof typeof drawerDirectionClasses;
1251
1251
 
@@ -1265,7 +1265,20 @@ interface DrawerCloseProps extends React.ComponentProps<"button"> {
1265
1265
  asChild?: boolean;
1266
1266
  }
1267
1267
  declare function DrawerClose({ asChild, onClick, ...props }: DrawerCloseProps): React.JSX.Element;
1268
+ /**
1269
+ * Standalone scrim, redundant with the one {@link DrawerContent} renders (as in
1270
+ * shadcn). Mounted only while open; the animated exit lives on the content's
1271
+ * own scrim.
1272
+ */
1268
1273
  declare function DrawerOverlay({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element | null;
1274
+ /**
1275
+ * The panel plus its scrim. Presence outlives `open`: once it turns false the
1276
+ * panel flips to `data-state="closed"` and is unmounted when its exit
1277
+ * `transform` transition ends (or the fallback timer fires). Re-opening
1278
+ * mid-exit just flips the state back and the same transition carries the
1279
+ * panel home. Drag-to-dismiss is TS-only pointer physics; opt a region out
1280
+ * with `data-vaul-no-drag`.
1281
+ */
1269
1282
  declare function DrawerContent({ className, children, ...props }: React.ComponentProps<"div">): React.JSX.Element | null;
1270
1283
  declare function DrawerHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
1271
1284
  declare function DrawerFooter({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
package/dist/index.js CHANGED
@@ -334,9 +334,9 @@ import "react";
334
334
  var ALERT_BASE = "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current";
335
335
  var alertVariants = {
336
336
  "neutral": "bg-card text-ink",
337
- "destructive": "text-accent-error bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-accent-error/90",
338
- "success": "text-positive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-positive/90",
339
- "info": "text-accent-info bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-accent-info/90"
337
+ "destructive": "text-accent-error bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-ink-mid",
338
+ "success": "text-positive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-ink-mid",
339
+ "info": "text-accent-info bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-ink-mid"
340
340
  };
341
341
  var ALERT_TITLE = "col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight";
342
342
  var ALERT_DESCRIPTION = "text-ink-soft col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed";
@@ -5891,15 +5891,16 @@ function SheetDescription({ className, ...props }) {
5891
5891
  import * as React62 from "react";
5892
5892
 
5893
5893
  // src/generated/drawer.ts
5894
- var DRAWER_CONTENT_BASE = "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex h-auto border transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500";
5894
+ var DRAWER_CONTENT_BASE = "bg-background fixed z-50 flex h-auto border";
5895
5895
  var drawerDirectionClasses = {
5896
- "bottom": "data-[state=open]:slide-in-from-bottom data-[state=closed]:slide-out-to-bottom inset-x-0 bottom-0 mt-24 max-h-[80vh] flex-col rounded-t-lg border-b-0",
5897
- "top": "data-[state=open]:slide-in-from-top data-[state=closed]:slide-out-to-top inset-x-0 top-0 mb-24 max-h-[80vh] flex-col rounded-b-lg border-t-0",
5898
- "left": "data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left inset-y-0 left-0 w-3/4 flex-row border-r sm:max-w-sm",
5899
- "right": "data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right inset-y-0 right-0 w-3/4 flex-row border-l sm:max-w-sm"
5896
+ "bottom": "inset-x-0 bottom-0 mt-24 max-h-[80vh] flex-col rounded-t-lg border-b-0",
5897
+ "top": "inset-x-0 top-0 mb-24 max-h-[80vh] flex-col rounded-b-lg border-t-0",
5898
+ "left": "inset-y-0 left-0 w-3/4 flex-row border-r sm:max-w-sm",
5899
+ "right": "inset-y-0 right-0 w-3/4 flex-row border-l sm:max-w-sm"
5900
5900
  };
5901
- var DRAWER_OVERLAY = "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50";
5902
- var DRAWER_HANDLE = "bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full";
5901
+ var DRAWER_OVERLAY = "fixed inset-0 z-50 bg-black/50";
5902
+ var DRAWER_HANDLE = "bg-muted mx-auto mt-4 h-2 w-[100px] shrink-0 rounded-full";
5903
+ var DRAWER_BODY = "flex min-h-0 flex-1 flex-col overflow-y-auto";
5903
5904
  var DRAWER_HEADER = "flex flex-col gap-0.5 p-4 text-center sm:gap-1.5 sm:text-left";
5904
5905
  var DRAWER_FOOTER = "mt-auto flex flex-col gap-2 p-4";
5905
5906
  var DRAWER_TITLE = "text-ink font-semibold";
@@ -5907,6 +5908,10 @@ var DRAWER_DESCRIPTION = "text-ink-soft text-sm";
5907
5908
 
5908
5909
  // src/components/drawer.tsx
5909
5910
  import { jsx as jsx52, jsxs as jsxs22 } from "react/jsx-runtime";
5911
+ var CLOSE_THRESHOLD = 0.25;
5912
+ var VELOCITY_THRESHOLD = 0.4;
5913
+ var DRAG_AFTER_OPEN_MS = 500;
5914
+ var EXIT_FALLBACK_MS = 600;
5910
5915
  var DrawerContext = React62.createContext(null);
5911
5916
  function useDrawer() {
5912
5917
  const ctx = React62.useContext(DrawerContext);
@@ -5971,16 +5976,169 @@ function DrawerOverlay({ className, ...props }) {
5971
5976
  }
5972
5977
  );
5973
5978
  }
5979
+ var EDGE_SIGN = {
5980
+ bottom: 1,
5981
+ top: -1,
5982
+ right: 1,
5983
+ left: -1
5984
+ };
5985
+ function isVertical(direction) {
5986
+ return direction === "bottom" || direction === "top";
5987
+ }
5988
+ function translateAlong(direction, px) {
5989
+ return isVertical(direction) ? `translate3d(0, ${px}px, 0)` : `translate3d(${px}px, 0, 0)`;
5990
+ }
5991
+ function dampen(px) {
5992
+ return Math.max(8 * (Math.log(px + 1) - 2), 0);
5993
+ }
5994
+ function shouldDrag(target, panel, direction, towardsEdge) {
5995
+ if (!(target instanceof Element)) return false;
5996
+ if (target.tagName === "SELECT") return false;
5997
+ if (target.closest("[data-vaul-no-drag]")) return false;
5998
+ if (!isVertical(direction)) return true;
5999
+ if (!towardsEdge) return false;
6000
+ const selection = window.getSelection?.()?.toString();
6001
+ if (selection && selection.length > 0) return false;
6002
+ let el = target;
6003
+ while (el && el !== panel) {
6004
+ if (el.scrollHeight > el.clientHeight && el.scrollTop > 0) return false;
6005
+ el = el.parentElement;
6006
+ }
6007
+ return true;
6008
+ }
6009
+ function useDrawerDrag(opts) {
6010
+ const { panelRef, scrimRef, direction, open, present, openedAtRef, onClose } = opts;
6011
+ const dragRef = React62.useRef(null);
6012
+ React62.useEffect(() => {
6013
+ if (!present) return;
6014
+ const panel = panelRef.current;
6015
+ if (!panel) return;
6016
+ const onTouchMove = (e) => {
6017
+ if (dragRef.current?.allowed) e.preventDefault();
6018
+ };
6019
+ panel.addEventListener("touchmove", onTouchMove, { passive: false });
6020
+ return () => panel.removeEventListener("touchmove", onTouchMove);
6021
+ }, [present, panelRef]);
6022
+ const axisPos = (e) => isVertical(direction) ? e.clientY : e.clientX;
6023
+ const onPointerDown = (e) => {
6024
+ const panel = panelRef.current;
6025
+ if (!panel || e.button > 0 || !open || dragRef.current) return;
6026
+ const target = e.target;
6027
+ if (target.closest('[data-slot="drawer-close"]')) return;
6028
+ if (!panel.contains(target)) return;
6029
+ const rect = panel.getBoundingClientRect();
6030
+ dragRef.current = {
6031
+ pointerId: e.pointerId,
6032
+ target,
6033
+ start: axisPos(e),
6034
+ time: Date.now(),
6035
+ size: isVertical(direction) ? rect.height : rect.width,
6036
+ allowed: null,
6037
+ delta: 0
6038
+ };
6039
+ try {
6040
+ target.setPointerCapture?.(e.pointerId);
6041
+ } catch {
6042
+ }
6043
+ };
6044
+ const onPointerMove = (e) => {
6045
+ const drag = dragRef.current;
6046
+ const panel = panelRef.current;
6047
+ if (!drag || !panel) return;
6048
+ const delta = (axisPos(e) - drag.start) * EDGE_SIGN[direction];
6049
+ drag.delta = delta;
6050
+ if (!drag.allowed) {
6051
+ if (Date.now() - openedAtRef.current < DRAG_AFTER_OPEN_MS) return;
6052
+ if (!shouldDrag(drag.target, panel, direction, delta > 0)) return;
6053
+ drag.allowed = true;
6054
+ panel.setAttribute("data-dragging", "true");
6055
+ scrimRef.current?.setAttribute("data-dragging", "true");
6056
+ }
6057
+ const scrim = scrimRef.current;
6058
+ if (delta < 0) {
6059
+ panel.style.transform = translateAlong(direction, -dampen(-delta) * EDGE_SIGN[direction]);
6060
+ if (scrim) scrim.style.opacity = "1";
6061
+ return;
6062
+ }
6063
+ panel.style.transform = translateAlong(direction, delta * EDGE_SIGN[direction]);
6064
+ if (scrim) {
6065
+ scrim.style.opacity = `${Math.min(1, Math.max(0, 1 - delta / drag.size))}`;
6066
+ }
6067
+ };
6068
+ const endGesture = (e, cancelled) => {
6069
+ const drag = dragRef.current;
6070
+ const panel = panelRef.current;
6071
+ if (!drag || !panel || e.pointerId !== drag.pointerId) return;
6072
+ dragRef.current = null;
6073
+ try {
6074
+ drag.target.releasePointerCapture?.(drag.pointerId);
6075
+ } catch {
6076
+ }
6077
+ if (!drag.allowed) return;
6078
+ panel.removeAttribute("data-dragging");
6079
+ panel.style.transform = "";
6080
+ const scrim = scrimRef.current;
6081
+ if (scrim) {
6082
+ scrim.removeAttribute("data-dragging");
6083
+ scrim.style.opacity = "";
6084
+ }
6085
+ if (cancelled || drag.delta <= 0) return;
6086
+ const dt = Date.now() - drag.time;
6087
+ const velocity = dt > 0 ? drag.delta / dt : 0;
6088
+ if (velocity > VELOCITY_THRESHOLD || drag.delta >= drag.size * CLOSE_THRESHOLD) {
6089
+ onClose();
6090
+ }
6091
+ };
6092
+ return {
6093
+ onPointerDown,
6094
+ onPointerMove,
6095
+ onPointerUp: (e) => endGesture(e, false),
6096
+ onPointerCancel: (e) => endGesture(e, true)
6097
+ };
6098
+ }
5974
6099
  function DrawerContent({ className, children, ...props }) {
5975
6100
  const { open, setOpen, direction } = useDrawer();
6101
+ const [present, setPresent] = React62.useState(open);
6102
+ if (open && !present) setPresent(true);
6103
+ const closing = present && !open;
5976
6104
  const scopeRef = useFocusScope(open);
5977
- if (!open) return null;
6105
+ const panelRef = React62.useRef(null);
6106
+ const scrimRef = React62.useRef(null);
6107
+ const openedAtRef = React62.useRef(0);
6108
+ React62.useEffect(() => {
6109
+ if (open) openedAtRef.current = Date.now();
6110
+ }, [open]);
6111
+ React62.useEffect(() => {
6112
+ if (!open) return;
6113
+ const prev = document.body.style.overflow;
6114
+ document.body.style.overflow = "hidden";
6115
+ return () => {
6116
+ document.body.style.overflow = prev;
6117
+ };
6118
+ }, [open]);
6119
+ React62.useEffect(() => {
6120
+ if (!closing) return;
6121
+ const id = setTimeout(() => setPresent(false), EXIT_FALLBACK_MS);
6122
+ return () => clearTimeout(id);
6123
+ }, [closing]);
6124
+ const drag = useDrawerDrag({
6125
+ panelRef,
6126
+ scrimRef,
6127
+ direction,
6128
+ open,
6129
+ present,
6130
+ openedAtRef,
6131
+ onClose: () => setOpen(false)
6132
+ });
6133
+ if (!present) return null;
6134
+ const state = closing ? "closed" : "open";
5978
6135
  return /* @__PURE__ */ jsxs22(Portal, { children: [
5979
6136
  /* @__PURE__ */ jsx52(
5980
6137
  "div",
5981
6138
  {
6139
+ ref: scrimRef,
5982
6140
  "data-slot": "drawer-overlay",
5983
- "data-state": "open",
6141
+ "data-state": state,
5984
6142
  className: DRAWER_OVERLAY,
5985
6143
  onClick: () => setOpen(false)
5986
6144
  }
@@ -5991,12 +6149,21 @@ function DrawerContent({ className, children, ...props }) {
5991
6149
  role: "dialog",
5992
6150
  "aria-modal": "true",
5993
6151
  "data-slot": "drawer-content",
5994
- "data-state": "open",
6152
+ "data-state": state,
5995
6153
  "data-vaul-drawer-direction": direction,
5996
- ref: mergeRefs(scopeRef),
6154
+ ref: mergeRefs(scopeRef, panelRef),
5997
6155
  onKeyDown: (e) => {
5998
6156
  if (e.key === "Escape") setOpen(false);
5999
6157
  },
6158
+ onTransitionEnd: (e) => {
6159
+ if (closing && e.propertyName === "transform" && e.target === e.currentTarget) {
6160
+ setPresent(false);
6161
+ }
6162
+ },
6163
+ onPointerDown: drag.onPointerDown,
6164
+ onPointerMove: drag.onPointerMove,
6165
+ onPointerUp: drag.onPointerUp,
6166
+ onPointerCancel: drag.onPointerCancel,
6000
6167
  className: cn(DRAWER_CONTENT_BASE, drawerDirectionClasses[direction], className),
6001
6168
  ...props,
6002
6169
  children: [
@@ -6007,7 +6174,14 @@ function DrawerContent({ className, children, ...props }) {
6007
6174
  className: DRAWER_HANDLE
6008
6175
  }
6009
6176
  ) : null,
6010
- children
6177
+ /* @__PURE__ */ jsx52(
6178
+ "div",
6179
+ {
6180
+ "data-slot": "drawer-body",
6181
+ className: DRAWER_BODY,
6182
+ children
6183
+ }
6184
+ )
6011
6185
  ]
6012
6186
  }
6013
6187
  )