@clubmed/trident-ui 2.0.0-beta.10 → 2.0.0-beta.11

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.
@@ -30,7 +30,15 @@ function a() {
30
30
  height: "40px"
31
31
  }
32
32
  }),
33
- /* @__PURE__ */ r(e, { children: "Logout" })
33
+ /* @__PURE__ */ r("a", {
34
+ href: "/account",
35
+ className: "text-b3 font-semibold",
36
+ children: "Account"
37
+ }),
38
+ /* @__PURE__ */ r(e, {
39
+ "data-dropdown-item": "",
40
+ children: "Logout"
41
+ })
34
42
  ]
35
43
  });
36
44
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dropdown-demo.js","names":[],"sources":["../../lib/examples/dropdown-demo.tsx"],"sourcesContent":["'use client';\n\nimport { Avatar } from '@/ui/Avatar';\nimport { Dropdown } from '@/ui/Dropdown';\nimport { Button } from '@/ui/buttons/Button';\n\nexport default function DropdownDemo() {\n const user = { firstName: 'John', lastName: 'Doe' };\n\n return (\n <Dropdown aria-label=\"Open user actions\">\n <span data-slot=\"label\" className=\"hidden md:inline text-body\">\n {user.firstName} {user.lastName}\n </span>\n <Avatar\n data-slot=\"label\"\n alt={`${user.firstName} ${user.lastName}`}\n className=\"w-40 h-40 cursor-pointer hover:opacity-80 transition-opacity\"\n style={{ width: '40px', height: '40px' }}\n />\n\n <Button>Logout</Button>\n </Dropdown>\n );\n}\n"],"mappings":";;;;;;AAMA,SAAwB,IAAe;CACrC,IAAM,IAAO;EAAE,WAAW;EAAQ,UAAU;EAAO;AAEnD,QACE,kBAAC,GAAD;EAAU,cAAW;YAArB;GACE,kBAAC,QAAD;IAAM,aAAU;IAAQ,WAAU;cAAlC;KACG,EAAK;KAAU;KAAE,EAAK;;;GAEzB,kBAAC,GAAD;IACE,aAAU;IACV,KAAK,GAAG,EAAK,UAAU,GAAG,EAAK;IAC/B,WAAU;IACV,OAAO;KAAE,OAAO;KAAQ,QAAQ;;IAChC,CAAA;GAEF,kBAAC,GAAD,EAAA,UAAQ,UAAe,CAAA"}
1
+ {"version":3,"file":"dropdown-demo.js","names":[],"sources":["../../lib/examples/dropdown-demo.tsx"],"sourcesContent":["'use client';\n\nimport { Avatar } from '@/ui/Avatar';\nimport { Dropdown } from '@/ui/Dropdown';\nimport { Button } from '@/ui/buttons/Button';\n\nexport default function DropdownDemo() {\n const user = { firstName: 'John', lastName: 'Doe' };\n\n return (\n <Dropdown aria-label=\"Open user actions\">\n <span data-slot=\"label\" className=\"hidden md:inline text-body\">\n {user.firstName} {user.lastName}\n </span>\n <Avatar\n data-slot=\"label\"\n alt={`${user.firstName} ${user.lastName}`}\n className=\"w-40 h-40 cursor-pointer hover:opacity-80 transition-opacity\"\n style={{ width: '40px', height: '40px' }}\n />\n\n <a href=\"/account\" className=\"text-b3 font-semibold\">\n Account\n </a>\n <Button data-dropdown-item=\"\">Logout</Button>\n </Dropdown>\n );\n}\n"],"mappings":";;;;;;AAMA,SAAwB,IAAe;CACrC,IAAM,IAAO;EAAE,WAAW;EAAQ,UAAU;EAAO;AAEnD,QACE,kBAAC,GAAD;EAAU,cAAW;YAArB;GACE,kBAAC,QAAD;IAAM,aAAU;IAAQ,WAAU;cAAlC;KACG,EAAK;KAAU;KAAE,EAAK;;;GAEzB,kBAAC,GAAD;IACE,aAAU;IACV,KAAK,GAAG,EAAK,UAAU,GAAG,EAAK;IAC/B,WAAU;IACV,OAAO;KAAE,OAAO;KAAQ,QAAQ;;IAChC,CAAA;GAEF,kBAAC,KAAD;IAAG,MAAK;IAAW,WAAU;cAAwB;IAEjD,CAAA;GACJ,kBAAC,GAAD;IAAQ,sBAAmB;cAAG;IAAe,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubmed/trident-ui",
3
- "version": "2.0.0-beta.10",
3
+ "version": "2.0.0-beta.11",
4
4
  "type": "module",
5
5
  "description": "Shared ClubMed React UI components",
6
6
  "keywords": [
package/ui/Dropdown.d.ts CHANGED
@@ -1,6 +1,23 @@
1
- import { ComponentPropsWithoutRef, PropsWithChildren } from 'react';
1
+ import { ComponentPropsWithoutRef, ReactNode } from 'react';
2
2
  import { IconicNames } from '@clubmed/trident-icons';
3
- export interface DropdownProps extends ComponentPropsWithoutRef<'button'> {
3
+ interface DropdownRenderContext {
4
+ close: () => void;
5
+ open: boolean;
6
+ toggle: () => void;
7
+ }
8
+ type DropdownChildren = ReactNode | ((context: DropdownRenderContext) => ReactNode);
9
+ export interface DropdownProps extends Omit<ComponentPropsWithoutRef<'button'>, 'children'> {
10
+ children?: DropdownChildren;
4
11
  icon?: IconicNames;
5
12
  }
6
- export declare function Dropdown({ icon, className, children: initialChildren, ...attrs }: PropsWithChildren<DropdownProps>): import("react/jsx-runtime").JSX.Element;
13
+ export declare function useDropdown(initialChildren?: DropdownChildren): {
14
+ children: ReactNode[];
15
+ close: () => void;
16
+ containerRef: import('react').RefObject<HTMLDivElement | null>;
17
+ isLeaving: boolean;
18
+ label: ReactNode[];
19
+ open: boolean;
20
+ toggle: () => void;
21
+ };
22
+ export declare function Dropdown({ icon, className, children: initialChildren, ...attrs }: DropdownProps): import("react/jsx-runtime").JSX.Element;
23
+ export {};
package/ui/Dropdown.js CHANGED
@@ -5,43 +5,77 @@ import { useCallback as r, useEffect as i, useRef as a, useState as o } from "re
5
5
  import { Icon as s } from "@clubmed/trident-icons";
6
6
  import { jsx as c, jsxs as l } from "react/jsx-runtime";
7
7
  //#region lib/ui/Dropdown.tsx
8
- function u({ icon: u = "ArrowOutlinedDown", className: d, children: f, ...p }) {
9
- let { label: m, children: h } = n(f, ["label"]), [g, _] = o(!1), v = a(null), y = a(g), b = y.current, x = !g && b;
10
- y.current = g;
11
- let S = r(() => {
12
- _((e) => !e);
13
- }, []);
8
+ function u(e) {
9
+ let [t, s] = o(!1), c = a(null), l = a(t), u = l.current, d = !t && u;
10
+ l.current = t;
11
+ let f = r(() => {
12
+ s((e) => !e);
13
+ }, []), p = r(() => {
14
+ s(!1);
15
+ }, []), m = typeof e == "function" ? e({
16
+ close: p,
17
+ open: t,
18
+ toggle: f
19
+ }) : e, { label: h, children: g } = n(m, ["label"]);
14
20
  return i(() => {
15
- if (!g) return;
21
+ if (!c.current) return;
22
+ let e = c.current.querySelectorAll(".dropdown-panel a[href], .dropdown-panel [data-dropdown-item]"), t = Array.from(e, (e) => {
23
+ let t = () => {
24
+ p();
25
+ };
26
+ return e.addEventListener("click", t), {
27
+ handleItemClick: t,
28
+ item: e
29
+ };
30
+ });
31
+ return () => {
32
+ t.forEach(({ handleItemClick: e, item: t }) => {
33
+ t.removeEventListener("click", e);
34
+ });
35
+ };
36
+ }, [p, m]), i(() => {
37
+ if (!t) return;
16
38
  let e = (e) => {
17
- !v.current || e.composedPath().includes(v.current) || _(!1);
39
+ !c.current || e.composedPath().includes(c.current) || p();
18
40
  };
19
- return document.addEventListener("pointerdown", e), () => {
20
- document.removeEventListener("pointerdown", e);
41
+ return document.addEventListener("click", e), () => {
42
+ document.removeEventListener("click", e);
21
43
  };
22
- }, [g]), /* @__PURE__ */ l("div", {
23
- ref: v,
24
- className: t("flex items-center gap-8 relative", d),
44
+ }, [p, t]), {
45
+ children: g,
46
+ close: p,
47
+ containerRef: c,
48
+ isLeaving: d,
49
+ label: h,
50
+ open: t,
51
+ toggle: f
52
+ };
53
+ }
54
+ function d({ icon: n = "ArrowOutlinedDown", className: r, children: i, ...a }) {
55
+ let { children: o, containerRef: d, isLeaving: f, label: p, open: m, toggle: h } = u(i);
56
+ return /* @__PURE__ */ l("div", {
57
+ ref: d,
58
+ className: t("flex items-center gap-8 relative", r),
25
59
  children: [/* @__PURE__ */ l("button", {
26
60
  tabIndex: -1,
27
- ...p,
61
+ ...a,
28
62
  className: "flex gap-8 items-center relative",
29
- onClick: S,
30
- children: [m, /* @__PURE__ */ c(s, {
31
- name: u,
63
+ onClick: h,
64
+ children: [p, /* @__PURE__ */ c(s, {
65
+ name: n,
32
66
  className: "w-30"
33
67
  })]
34
68
  }), /* @__PURE__ */ c("div", {
35
- className: t(e("z-5 flex items-center gap-12 bg-white rounded-16 border-1 border-lightGrey absolute top-[calc(100%+8px)] right-0 text-black p-20 min-w-[120px] origin-center will-change[transform,opacity]", {
36
- "animate-zoomIn": g,
37
- "animate-zoomOut": !g && x,
38
- "opacity-0 scale-90 pointer-events-none": !g && !x
69
+ className: t(e("dropdown-panel z-5 flex items-center gap-12 bg-white rounded-16 border-1 border-lightGrey absolute top-[calc(100%+8px)] right-0 text-black p-20 min-w-[120px] origin-center will-change[transform,opacity]", {
70
+ "animate-zoomIn": m,
71
+ "animate-zoomOut": !m && f,
72
+ "opacity-0 scale-90 pointer-events-none": !m && !f
39
73
  })),
40
- children: h
74
+ children: o
41
75
  })]
42
76
  });
43
77
  }
44
78
  //#endregion
45
- export { u as Dropdown };
79
+ export { d as Dropdown, u as useDropdown };
46
80
 
47
81
  //# sourceMappingURL=Dropdown.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Dropdown.js","names":[],"sources":["../../lib/ui/Dropdown.tsx"],"sourcesContent":["import {\n type ComponentPropsWithoutRef,\n type PropsWithChildren,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { Icon, type IconicNames } from '@clubmed/trident-icons';\nimport clsx from 'clsx';\nimport { twMerge } from '@/ui/helpers/twMerge';\nimport { useSlots } from '@/ui/hooks/useSlots';\n\nexport interface DropdownProps extends ComponentPropsWithoutRef<'button'> {\n icon?: IconicNames;\n}\n\nexport function Dropdown({\n icon = 'ArrowOutlinedDown',\n className,\n children: initialChildren,\n ...attrs\n}: PropsWithChildren<DropdownProps>) {\n const { label, children } = useSlots(initialChildren, ['label']);\n const [open, setOpen] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n const wasVisibleRef = useRef(open);\n const wasVisible = wasVisibleRef.current;\n const isLeaving = !open && wasVisible;\n\n // update ref for next render\n wasVisibleRef.current = open;\n\n const onClick = useCallback(() => {\n setOpen((prev) => !prev);\n }, []);\n\n // Close overlay when clicking outside using composedPath\n useEffect(() => {\n if (!open) {\n return;\n }\n\n const handlePointerDown = (event: PointerEvent) => {\n if (!containerRef.current || event.composedPath().includes(containerRef.current)) {\n return;\n }\n\n setOpen(false);\n };\n\n document.addEventListener('pointerdown', handlePointerDown);\n return () => {\n document.removeEventListener('pointerdown', handlePointerDown);\n };\n }, [open]);\n\n return (\n <div ref={containerRef} className={twMerge('flex items-center gap-8 relative', className)}>\n <button\n tabIndex={-1}\n {...attrs}\n className=\"flex gap-8 items-center relative\"\n onClick={onClick}\n >\n {label}\n\n <Icon name={icon} className=\"w-30\" />\n </button>\n\n <div\n className={twMerge(\n clsx(\n 'z-5 flex items-center gap-12 bg-white rounded-16 border-1 border-lightGrey absolute top-[calc(100%+8px)] right-0 text-black p-20 min-w-[120px] origin-center will-change[transform,opacity]',\n {\n 'animate-zoomIn': open,\n 'animate-zoomOut': !open && isLeaving,\n 'opacity-0 scale-90 pointer-events-none': !open && !isLeaving,\n },\n ),\n )}\n >\n {children}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;AAiBA,SAAgB,EAAS,EACvB,UAAO,qBACP,cACA,UAAU,GACV,GAAG,KACgC;CACnC,IAAM,EAAE,UAAO,gBAAa,EAAS,GAAiB,CAAC,QAAQ,CAAC,EAC1D,CAAC,GAAM,KAAW,EAAS,GAAM,EACjC,IAAe,EAAuB,KAAK,EAC3C,IAAgB,EAAO,EAAK,EAC5B,IAAa,EAAc,SAC3B,IAAY,CAAC,KAAQ;AAG3B,GAAc,UAAU;CAExB,IAAM,IAAU,QAAkB;AAChC,KAAS,MAAS,CAAC,EAAK;IACvB,EAAE,CAAC;AAsBN,QAnBA,QAAgB;AACd,MAAI,CAAC,EACH;EAGF,IAAM,KAAqB,MAAwB;AAC7C,IAAC,EAAa,WAAW,EAAM,cAAc,CAAC,SAAS,EAAa,QAAQ,IAIhF,EAAQ,GAAM;;AAIhB,SADA,SAAS,iBAAiB,eAAe,EAAkB,QAC9C;AACX,YAAS,oBAAoB,eAAe,EAAkB;;IAE/D,CAAC,EAAK,CAAC,EAGR,kBAAC,OAAD;EAAK,KAAK;EAAc,WAAW,EAAQ,oCAAoC,EAAU;YAAzF,CACE,kBAAC,UAAD;GACE,UAAU;GACV,GAAI;GACJ,WAAU;GACD;aAJX,CAMG,GAED,kBAAC,GAAD;IAAM,MAAM;IAAM,WAAU;IAAS,CAAA,CAC9B;MAET,kBAAC,OAAD;GACE,WAAW,EACT,EACE,+LACA;IACE,kBAAkB;IAClB,mBAAmB,CAAC,KAAQ;IAC5B,0CAA0C,CAAC,KAAQ,CAAC;IACrD,CACF,CACF;GAEA;GACG,CAAA,CACF"}
1
+ {"version":3,"file":"Dropdown.js","names":[],"sources":["../../lib/ui/Dropdown.tsx"],"sourcesContent":["import {\n type ComponentPropsWithoutRef,\n type ReactNode,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { Icon, type IconicNames } from '@clubmed/trident-icons';\nimport clsx from 'clsx';\nimport { twMerge } from '@/ui/helpers/twMerge';\nimport { useSlots } from '@/ui/hooks/useSlots';\n\ninterface DropdownRenderContext {\n close: () => void;\n open: boolean;\n toggle: () => void;\n}\n\ntype DropdownChildren = ReactNode | ((context: DropdownRenderContext) => ReactNode);\n\nexport interface DropdownProps extends Omit<ComponentPropsWithoutRef<'button'>, 'children'> {\n children?: DropdownChildren;\n icon?: IconicNames;\n}\n\nexport function useDropdown(initialChildren?: DropdownChildren) {\n const [open, setOpen] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n const wasVisibleRef = useRef(open);\n const wasVisible = wasVisibleRef.current;\n const isLeaving = !open && wasVisible;\n\n // update ref for next render\n wasVisibleRef.current = open;\n\n const toggle = useCallback(() => {\n setOpen((prev) => !prev);\n }, []);\n\n const close = useCallback(() => {\n setOpen(false);\n }, []);\n\n const renderedChildren =\n typeof initialChildren === 'function'\n ? initialChildren({ close, open, toggle })\n : initialChildren;\n\n const { label, children } = useSlots(renderedChildren, ['label']);\n\n useEffect(() => {\n if (!containerRef.current) {\n return;\n }\n\n const items = containerRef.current.querySelectorAll(\n '.dropdown-panel a[href], .dropdown-panel [data-dropdown-item]',\n );\n const listeners = Array.from(items, (item) => {\n const handleItemClick = () => {\n close();\n };\n item.addEventListener('click', handleItemClick);\n return { handleItemClick, item };\n });\n\n return () => {\n listeners.forEach(({ handleItemClick, item }) => {\n item.removeEventListener('click', handleItemClick);\n });\n };\n }, [close, renderedChildren]);\n\n // Close overlay when clicking outside using composedPath\n useEffect(() => {\n if (!open) {\n return;\n }\n\n const handleOutsideClick = (event: PointerEvent) => {\n if (!containerRef.current || event.composedPath().includes(containerRef.current)) {\n return;\n }\n\n close();\n };\n\n document.addEventListener('click', handleOutsideClick);\n return () => {\n document.removeEventListener('click', handleOutsideClick);\n };\n }, [close, open]);\n\n return {\n children,\n close,\n containerRef,\n isLeaving,\n label,\n open,\n toggle,\n };\n}\n\nexport function Dropdown({\n icon = 'ArrowOutlinedDown',\n className,\n children: initialChildren,\n ...attrs\n}: DropdownProps) {\n const { children, containerRef, isLeaving, label, open, toggle } = useDropdown(initialChildren);\n\n return (\n <div ref={containerRef} className={twMerge('flex items-center gap-8 relative', className)}>\n <button\n tabIndex={-1}\n {...attrs}\n className=\"flex gap-8 items-center relative\"\n onClick={toggle}\n >\n {label}\n\n <Icon name={icon} className=\"w-30\" />\n </button>\n\n <div\n className={twMerge(\n clsx(\n 'dropdown-panel z-5 flex items-center gap-12 bg-white rounded-16 border-1 border-lightGrey absolute top-[calc(100%+8px)] right-0 text-black p-20 min-w-[120px] origin-center will-change[transform,opacity]',\n {\n 'animate-zoomIn': open,\n 'animate-zoomOut': !open && isLeaving,\n 'opacity-0 scale-90 pointer-events-none': !open && !isLeaving,\n },\n ),\n )}\n >\n {children}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;AA0BA,SAAgB,EAAY,GAAoC;CAC9D,IAAM,CAAC,GAAM,KAAW,EAAS,GAAM,EACjC,IAAe,EAAuB,KAAK,EAC3C,IAAgB,EAAO,EAAK,EAC5B,IAAa,EAAc,SAC3B,IAAY,CAAC,KAAQ;AAG3B,GAAc,UAAU;CAExB,IAAM,IAAS,QAAkB;AAC/B,KAAS,MAAS,CAAC,EAAK;IACvB,EAAE,CAAC,EAEA,IAAQ,QAAkB;AAC9B,IAAQ,GAAM;IACb,EAAE,CAAC,EAEA,IACJ,OAAO,KAAoB,aACvB,EAAgB;EAAE;EAAO;EAAM;EAAQ,CAAC,GACxC,GAEA,EAAE,UAAO,gBAAa,EAAS,GAAkB,CAAC,QAAQ,CAAC;AA6CjE,QA3CA,QAAgB;AACd,MAAI,CAAC,EAAa,QAChB;EAGF,IAAM,IAAQ,EAAa,QAAQ,iBACjC,gEACD,EACK,IAAY,MAAM,KAAK,IAAQ,MAAS;GAC5C,IAAM,UAAwB;AAC5B,OAAO;;AAGT,UADA,EAAK,iBAAiB,SAAS,EAAgB,EACxC;IAAE;IAAiB;IAAM;IAChC;AAEF,eAAa;AACX,KAAU,SAAS,EAAE,oBAAiB,cAAW;AAC/C,MAAK,oBAAoB,SAAS,EAAgB;KAClD;;IAEH,CAAC,GAAO,EAAiB,CAAC,EAG7B,QAAgB;AACd,MAAI,CAAC,EACH;EAGF,IAAM,KAAsB,MAAwB;AAC9C,IAAC,EAAa,WAAW,EAAM,cAAc,CAAC,SAAS,EAAa,QAAQ,IAIhF,GAAO;;AAIT,SADA,SAAS,iBAAiB,SAAS,EAAmB,QACzC;AACX,YAAS,oBAAoB,SAAS,EAAmB;;IAE1D,CAAC,GAAO,EAAK,CAAC,EAEV;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACD;;AAGH,SAAgB,EAAS,EACvB,UAAO,qBACP,cACA,UAAU,GACV,GAAG,KACa;CAChB,IAAM,EAAE,aAAU,iBAAc,cAAW,UAAO,SAAM,cAAW,EAAY,EAAgB;AAE/F,QACE,kBAAC,OAAD;EAAK,KAAK;EAAc,WAAW,EAAQ,oCAAoC,EAAU;YAAzF,CACE,kBAAC,UAAD;GACE,UAAU;GACV,GAAI;GACJ,WAAU;GACV,SAAS;aAJX,CAMG,GAED,kBAAC,GAAD;IAAM,MAAM;IAAM,WAAU;IAAS,CAAA,CAC9B;MAET,kBAAC,OAAD;GACE,WAAW,EACT,EACE,8MACA;IACE,kBAAkB;IAClB,mBAAmB,CAAC,KAAQ;IAC5B,0CAA0C,CAAC,KAAQ,CAAC;IACrD,CACF,CACF;GAEA;GACG,CAAA,CACF"}