dishui 0.0.81 → 0.0.83

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.
Files changed (87) hide show
  1. package/dist/components/Accordion/index.js +6 -6
  2. package/dist/components/Accordion/index.js.map +1 -1
  3. package/dist/components/Button/index.d.ts +1 -0
  4. package/dist/components/Button/index.js.map +1 -1
  5. package/dist/components/ButtonMenu/index.d.ts +28 -0
  6. package/dist/components/ButtonMenu/index.js +197 -0
  7. package/dist/components/ButtonMenu/index.js.map +1 -0
  8. package/dist/components/Card/index.d.ts +7 -4
  9. package/dist/components/Card/index.js +98 -65
  10. package/dist/components/Card/index.js.map +1 -1
  11. package/dist/components/Demo/index.d.ts +5 -0
  12. package/dist/components/Demo/index.js +131 -56
  13. package/dist/components/Demo/index.js.map +1 -1
  14. package/dist/components/Demo/source.d.ts +9 -0
  15. package/dist/components/Demo/source.js +32 -0
  16. package/dist/components/Demo/source.js.map +1 -0
  17. package/dist/components/EmptyState/index.js +4 -4
  18. package/dist/components/EmptyState/index.js.map +1 -1
  19. package/dist/components/Field/index.d.ts +8 -0
  20. package/dist/components/Field/index.js +43 -0
  21. package/dist/components/Field/index.js.map +1 -0
  22. package/dist/components/LayoutSuite/DashboardSuite.d.ts +35 -0
  23. package/dist/components/LayoutSuite/DashboardSuite.js +61 -0
  24. package/dist/components/LayoutSuite/DashboardSuite.js.map +1 -0
  25. package/dist/components/LayoutSuite/index.d.ts +56 -0
  26. package/dist/components/LayoutSuite/index.js +101 -0
  27. package/dist/components/LayoutSuite/index.js.map +1 -0
  28. package/dist/components/List/index.js +5 -5
  29. package/dist/components/List/index.js.map +1 -1
  30. package/dist/components/Modal/focusScope.d.ts +1 -0
  31. package/dist/components/Modal/focusScope.js +81 -0
  32. package/dist/components/Modal/focusScope.js.map +1 -0
  33. package/dist/components/Modal/index.d.ts +4 -1
  34. package/dist/components/Modal/index.js +53 -48
  35. package/dist/components/Modal/index.js.map +1 -1
  36. package/dist/components/Page/Float.d.ts +14 -0
  37. package/dist/components/Page/Float2.js +154 -0
  38. package/dist/components/Page/Float2.js.map +1 -0
  39. package/dist/components/Page/index.d.ts +18 -5
  40. package/dist/components/Page/index.js +103 -34
  41. package/dist/components/Page/index.js.map +1 -1
  42. package/dist/components/Page/resize.d.ts +28 -0
  43. package/dist/components/Page/resize2.js +95 -0
  44. package/dist/components/Page/resize2.js.map +1 -0
  45. package/dist/components/PageHeader/index.d.ts +10 -3
  46. package/dist/components/PageHeader/index.js +46 -25
  47. package/dist/components/PageHeader/index.js.map +1 -1
  48. package/dist/components/PagePanel/index.d.ts +29 -0
  49. package/dist/components/PagePanel/index.js +99 -0
  50. package/dist/components/PagePanel/index.js.map +1 -0
  51. package/dist/components/Resizable/ResizeGrip.d.ts +5 -0
  52. package/dist/components/Resizable/ResizeGrip.js +14 -0
  53. package/dist/components/Resizable/ResizeGrip.js.map +1 -0
  54. package/dist/components/Resizable/index.js +57 -59
  55. package/dist/components/Resizable/index.js.map +1 -1
  56. package/dist/components/Resizable/useResize.d.ts +6 -0
  57. package/dist/components/Resizable/useResize.js +29 -28
  58. package/dist/components/Resizable/useResize.js.map +1 -1
  59. package/dist/components/SidebarToggleButton/index.d.ts +5 -3
  60. package/dist/components/SidebarToggleButton/index.js +24 -21
  61. package/dist/components/SidebarToggleButton/index.js.map +1 -1
  62. package/dist/components/Table/index.d.ts +10 -12
  63. package/dist/components/Table/index.js +25 -20
  64. package/dist/components/Table/index.js.map +1 -1
  65. package/dist/components/Text/index.d.ts +1 -1
  66. package/dist/components/Text/index.js +11 -10
  67. package/dist/components/Text/index.js.map +1 -1
  68. package/dist/components/Textarea/index.d.ts +14 -0
  69. package/dist/components/Textarea/index.js +44 -0
  70. package/dist/components/Textarea/index.js.map +1 -0
  71. package/dist/components/Tree/Tree.d.ts +2 -0
  72. package/dist/components/Tree/Tree.js +49 -40
  73. package/dist/components/Tree/Tree.js.map +1 -1
  74. package/dist/dishui.css +1 -1
  75. package/dist/index-nostyled.d.ts +9 -0
  76. package/dist/index.d.ts +11 -2
  77. package/dist/index.js +119 -113
  78. package/dist/layouts/Sidebar.d.ts +12 -1
  79. package/dist/layouts/Sidebar.js +121 -97
  80. package/dist/layouts/Sidebar.js.map +1 -1
  81. package/dist/lite.d.ts +10 -2
  82. package/dist/lite.js +17 -12
  83. package/dist/styles/themeStyles.js +6 -6
  84. package/dist/styles/themeStyles.js.map +1 -1
  85. package/dist/utils/menu.js +14 -16
  86. package/dist/utils/menu.js.map +1 -1
  87. package/package.json +3 -1
@@ -20,7 +20,7 @@ var i = ({ className: t }) => /* @__PURE__ */ n("svg", {
20
20
  h || m(n), l?.(n);
21
21
  };
22
22
  return /* @__PURE__ */ n("div", {
23
- className: e("w-full divide-y divide-base-300 overflow-hidden bg-base-100", d && "rounded-xl border border-base-300/70", f),
23
+ className: e("dishui-accordion w-full divide-y divide-base-300 overflow-hidden bg-base-100 text-body leading-5 text-base-content", d && "rounded-lg border border-base-300", f),
24
24
  children: a.map((t) => {
25
25
  let a = g.includes(t.key);
26
26
  return /* @__PURE__ */ r("div", {
@@ -30,10 +30,10 @@ var i = ({ className: t }) => /* @__PURE__ */ n("svg", {
30
30
  disabled: t.disabled,
31
31
  "aria-expanded": a,
32
32
  onClick: () => _(t.key, t.disabled),
33
- className: e("flex w-full items-center gap-2 px-4 py-3 text-left text-sm font-medium transition-colors", t.disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer hover:bg-base-200/60", a && !t.disabled && "text-primary"),
33
+ className: e("flex min-h-10 w-full items-center gap-2 px-4 py-2 text-left text-body font-medium transition-colors duration-150 motion-reduce:transition-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-current", t.disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer hover:bg-base-200/60", a && !t.disabled && "text-base-content"),
34
34
  children: [
35
35
  t.icon && /* @__PURE__ */ n("span", {
36
- className: "flex shrink-0 items-center text-base-content/60 [&>svg]:h-4 [&>svg]:w-4",
36
+ className: "flex shrink-0 items-center text-base-content/70 [&>svg]:h-4 [&>svg]:w-4",
37
37
  children: t.icon
38
38
  }),
39
39
  /* @__PURE__ */ n("span", {
@@ -45,17 +45,17 @@ var i = ({ className: t }) => /* @__PURE__ */ n("svg", {
45
45
  children: t.extra
46
46
  }),
47
47
  u && /* @__PURE__ */ n("span", {
48
- className: e("shrink-0 text-base-content/50 transition-transform duration-200", a && "rotate-180 text-primary"),
48
+ className: e("shrink-0 text-base-content/70 transition-transform duration-200 motion-reduce:transition-none", a && "rotate-180"),
49
49
  children: /* @__PURE__ */ n(i, {})
50
50
  })
51
51
  ]
52
52
  }), /* @__PURE__ */ n("div", {
53
- className: "grid transition-[grid-template-rows] duration-200 ease-out",
53
+ className: "grid transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none",
54
54
  style: { gridTemplateRows: a ? "1fr" : "0fr" },
55
55
  children: /* @__PURE__ */ n("div", {
56
56
  className: "overflow-hidden",
57
57
  children: /* @__PURE__ */ n("div", {
58
- className: "px-4 pb-4 pt-0 text-sm leading-relaxed text-base-content/75",
58
+ className: "px-4 pb-3 pt-1 text-body leading-5 text-base-content/70",
59
59
  children: t.content
60
60
  })
61
61
  })
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/Accordion/index.tsx"],"sourcesContent":["import React from 'react';\nimport { cn } from '../../utils/cn';\n\nexport type AccordionItemType = {\n /** 唯一标识(用于展开状态管理)。 */\n key: string;\n /** 标题。 */\n title: React.ReactNode;\n /** 展开区域内容。 */\n content: React.ReactNode;\n /** 标题左侧图标。 */\n icon?: React.ReactNode;\n /** 标题行右侧、展开指示器之前的附加内容。 */\n extra?: React.ReactNode;\n /** 禁用该项。 */\n disabled?: boolean;\n};\n\nexport type AccordionProps = {\n /** 手风琴项(数据驱动,可动态增删)。 */\n items: AccordionItemType[];\n /** 受控:当前展开项的 key 列表。 */\n openKeys?: string[];\n /** 非受控:默认展开项的 key 列表。 */\n defaultOpenKeys?: string[];\n /** 是否允许同时展开多项;默认 false(互斥展开,展开新项时收起其它项)。 */\n multiple?: boolean;\n /** 展开状态变化回调。 */\n onChange?: (openKeys: string[]) => void;\n /** 是否显示右侧展开指示箭头,默认 true。 */\n indicator?: boolean;\n /** 是否显示外层边框,默认 true。 */\n bordered?: boolean;\n className?: string;\n};\n\nconst ChevronGlyph: React.FC<{ className?: string }> = ({ className }) => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={cn('h-4 w-4', className)}\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n);\n\n/**\n * Accordion —— 手风琴组件:动态交互式的聚合展开列表。\n * 数据驱动(items 可动态增删),支持受控 / 非受控、互斥或多开、\n * 禁用项、图标与自定义内容,展开 / 收起带平滑过渡动画。\n */\nconst Accordion: React.FC<AccordionProps> = ({\n items,\n openKeys,\n defaultOpenKeys = [],\n multiple = false,\n onChange,\n indicator = true,\n bordered = true,\n className = '',\n}) => {\n const [innerKeys, setInnerKeys] = React.useState<string[]>(defaultOpenKeys);\n const isControlled = openKeys !== undefined;\n const currentKeys = isControlled ? (openKeys ?? []) : innerKeys;\n\n const toggle = (key: string, disabled?: boolean) => {\n if (disabled) return;\n const nextKeys = currentKeys.includes(key)\n ? currentKeys.filter((k) => k !== key)\n : multiple\n ? [...currentKeys, key]\n : [key];\n if (!isControlled) setInnerKeys(nextKeys);\n onChange?.(nextKeys);\n };\n\n return (\n <div\n className={cn(\n 'w-full divide-y divide-base-300 overflow-hidden bg-base-100',\n bordered && 'rounded-xl border border-base-300/70',\n className,\n )}\n >\n {items.map((item) => {\n const open = currentKeys.includes(item.key);\n return (\n <div key={item.key} className={cn(open && !item.disabled && 'bg-base-200/40')}>\n <button\n type=\"button\"\n disabled={item.disabled}\n aria-expanded={open}\n onClick={() => toggle(item.key, item.disabled)}\n className={cn(\n 'flex w-full items-center gap-2 px-4 py-3 text-left text-sm font-medium transition-colors',\n item.disabled\n ? 'cursor-not-allowed opacity-50'\n : 'cursor-pointer hover:bg-base-200/60',\n open && !item.disabled && 'text-primary',\n )}\n >\n {item.icon && <span className=\"flex shrink-0 items-center text-base-content/60 [&>svg]:h-4 [&>svg]:w-4\">{item.icon}</span>}\n <span className=\"min-w-0 flex-1 truncate\">{item.title}</span>\n {item.extra && <span className=\"flex shrink-0 items-center\">{item.extra}</span>}\n {indicator && (\n <span className={cn('shrink-0 text-base-content/50 transition-transform duration-200', open && 'rotate-180 text-primary')}>\n <ChevronGlyph />\n </span>\n )}\n </button>\n <div\n className=\"grid transition-[grid-template-rows] duration-200 ease-out\"\n style={{ gridTemplateRows: open ? '1fr' : '0fr' }}\n >\n <div className=\"overflow-hidden\">\n <div className=\"px-4 pb-4 pt-0 text-sm leading-relaxed text-base-content/75\">\n {item.content}\n </div>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n );\n};\n\nexport default Accordion;\n"],"mappings":";;;;AAoCA,IAAM,KAAkD,EAAE,mBACxD,kBAAC,OAAD;CACE,OAAM;CACN,SAAQ;CACR,MAAK;CACL,QAAO;CACP,aAAa;CACb,eAAc;CACd,gBAAe;CACf,WAAW,EAAG,WAAW,CAAS;CAClC,eAAY;CAEZ,UAAA,kBAAC,QAAD,EAAM,GAAE,eAAgB,CAAA;AACrB,CAAA,GAQD,KAAuC,EAC3C,UACA,aACA,qBAAkB,CAAC,GACnB,cAAW,IACX,aACA,eAAY,IACZ,cAAW,IACX,eAAY,SACR;CACJ,IAAM,CAAC,GAAW,KAAgB,EAAM,SAAmB,CAAe,GACpE,IAAe,MAAa,KAAA,GAC5B,IAAc,IAAgB,KAAY,CAAC,IAAK,GAEhD,KAAU,GAAa,MAAuB;EAClD,IAAI,GAAU;EACd,IAAM,IAAW,EAAY,SAAS,CAAG,IACrC,EAAY,QAAQ,MAAM,MAAM,CAAG,IACnC,IACE,CAAC,GAAG,GAAa,CAAG,IACpB,CAAC,CAAG;EAEV,AADK,KAAc,EAAa,CAAQ,GACxC,IAAW,CAAQ;CACrB;CAEA,OACE,kBAAC,OAAD;EACE,WAAW,EACT,+DACA,KAAY,wCACZ,CACF;EAEC,UAAA,EAAM,KAAK,MAAS;GACnB,IAAM,IAAO,EAAY,SAAS,EAAK,GAAG;GAC1C,OACE,kBAAC,OAAD;IAAoB,WAAW,EAAG,KAAQ,CAAC,EAAK,YAAY,gBAAgB;IAA5E,UAAA,CACE,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,EAAK;KACf,iBAAe;KACf,eAAe,EAAO,EAAK,KAAK,EAAK,QAAQ;KAC7C,WAAW,EACT,4FACA,EAAK,WACD,kCACA,uCACJ,KAAQ,CAAC,EAAK,YAAY,cAC5B;KAXF,UAAA;MAaG,EAAK,QAAQ,kBAAC,QAAD;OAAM,WAAU;OAA2E,UAAA,EAAK;MAAW,CAAA;MACzH,kBAAC,QAAD;OAAM,WAAU;OAA2B,UAAA,EAAK;MAAY,CAAA;MAC3D,EAAK,SAAS,kBAAC,QAAD;OAAM,WAAU;OAA8B,UAAA,EAAK;MAAY,CAAA;MAC7E,KACC,kBAAC,QAAD;OAAM,WAAW,EAAG,mEAAmE,KAAQ,yBAAyB;OACtH,UAAA,kBAAC,GAAD,CAAe,CAAA;MACX,CAAA;KAEF;IACR,CAAA,GAAA,kBAAC,OAAD;KACE,WAAU;KACV,OAAO,EAAE,kBAAkB,IAAO,QAAQ,MAAM;KAEhD,UAAA,kBAAC,OAAD;MAAK,WAAU;MACb,UAAA,kBAAC,OAAD;OAAK,WAAU;OACZ,UAAA,EAAK;MACH,CAAA;KACF,CAAA;IACF,CAAA,CACF;GAjCK,GAAA,EAAK,GAiCV;EAET,CAAC;CACE,CAAA;AAET"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/Accordion/index.tsx"],"sourcesContent":["import React from 'react';\nimport { cn } from '../../utils/cn';\n\nexport type AccordionItemType = {\n /** 唯一标识(用于展开状态管理)。 */\n key: string;\n /** 标题。 */\n title: React.ReactNode;\n /** 展开区域内容。 */\n content: React.ReactNode;\n /** 标题左侧图标。 */\n icon?: React.ReactNode;\n /** 标题行右侧、展开指示器之前的附加内容。 */\n extra?: React.ReactNode;\n /** 禁用该项。 */\n disabled?: boolean;\n};\n\nexport type AccordionProps = {\n /** 手风琴项(数据驱动,可动态增删)。 */\n items: AccordionItemType[];\n /** 受控:当前展开项的 key 列表。 */\n openKeys?: string[];\n /** 非受控:默认展开项的 key 列表。 */\n defaultOpenKeys?: string[];\n /** 是否允许同时展开多项;默认 false(互斥展开,展开新项时收起其它项)。 */\n multiple?: boolean;\n /** 展开状态变化回调。 */\n onChange?: (openKeys: string[]) => void;\n /** 是否显示右侧展开指示箭头,默认 true。 */\n indicator?: boolean;\n /** 是否显示外层边框,默认 true。 */\n bordered?: boolean;\n className?: string;\n};\n\nconst ChevronGlyph: React.FC<{ className?: string }> = ({ className }) => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={cn('h-4 w-4', className)}\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n);\n\n/**\n * Accordion —— 手风琴组件:动态交互式的聚合展开列表。\n * 数据驱动(items 可动态增删),支持受控 / 非受控、互斥或多开、\n * 禁用项、图标与自定义内容,展开 / 收起带平滑过渡动画。\n */\nconst Accordion: React.FC<AccordionProps> = ({\n items,\n openKeys,\n defaultOpenKeys = [],\n multiple = false,\n onChange,\n indicator = true,\n bordered = true,\n className = '',\n}) => {\n const [innerKeys, setInnerKeys] = React.useState<string[]>(defaultOpenKeys);\n const isControlled = openKeys !== undefined;\n const currentKeys = isControlled ? (openKeys ?? []) : innerKeys;\n\n const toggle = (key: string, disabled?: boolean) => {\n if (disabled) return;\n const nextKeys = currentKeys.includes(key)\n ? currentKeys.filter((k) => k !== key)\n : multiple\n ? [...currentKeys, key]\n : [key];\n if (!isControlled) setInnerKeys(nextKeys);\n onChange?.(nextKeys);\n };\n\n return (\n <div\n className={cn(\n 'dishui-accordion w-full divide-y divide-base-300 overflow-hidden bg-base-100 text-body leading-5 text-base-content',\n bordered && 'rounded-lg border border-base-300',\n className,\n )}\n >\n {items.map((item) => {\n const open = currentKeys.includes(item.key);\n return (\n <div key={item.key} className={cn(open && !item.disabled && 'bg-base-200/40')}>\n <button\n type=\"button\"\n disabled={item.disabled}\n aria-expanded={open}\n onClick={() => toggle(item.key, item.disabled)}\n className={cn(\n 'flex min-h-10 w-full items-center gap-2 px-4 py-2 text-left text-body font-medium transition-colors duration-150 motion-reduce:transition-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-current',\n item.disabled\n ? 'cursor-not-allowed opacity-50'\n : 'cursor-pointer hover:bg-base-200/60',\n open && !item.disabled && 'text-base-content',\n )}\n >\n {item.icon && <span className=\"flex shrink-0 items-center text-base-content/70 [&>svg]:h-4 [&>svg]:w-4\">{item.icon}</span>}\n <span className=\"min-w-0 flex-1 truncate\">{item.title}</span>\n {item.extra && <span className=\"flex shrink-0 items-center\">{item.extra}</span>}\n {indicator && (\n <span className={cn('shrink-0 text-base-content/70 transition-transform duration-200 motion-reduce:transition-none', open && 'rotate-180')}>\n <ChevronGlyph />\n </span>\n )}\n </button>\n <div\n className=\"grid transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none\"\n style={{ gridTemplateRows: open ? '1fr' : '0fr' }}\n >\n <div className=\"overflow-hidden\">\n <div className=\"px-4 pb-3 pt-1 text-body leading-5 text-base-content/70\">\n {item.content}\n </div>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n );\n};\n\nexport default Accordion;\n"],"mappings":";;;;AAoCA,IAAM,KAAkD,EAAE,mBACxD,kBAAC,OAAD;CACE,OAAM;CACN,SAAQ;CACR,MAAK;CACL,QAAO;CACP,aAAa;CACb,eAAc;CACd,gBAAe;CACf,WAAW,EAAG,WAAW,CAAS;CAClC,eAAY;CAEZ,UAAA,kBAAC,QAAD,EAAM,GAAE,eAAgB,CAAA;AACrB,CAAA,GAQD,KAAuC,EAC3C,UACA,aACA,qBAAkB,CAAC,GACnB,cAAW,IACX,aACA,eAAY,IACZ,cAAW,IACX,eAAY,SACR;CACJ,IAAM,CAAC,GAAW,KAAgB,EAAM,SAAmB,CAAe,GACpE,IAAe,MAAa,KAAA,GAC5B,IAAc,IAAgB,KAAY,CAAC,IAAK,GAEhD,KAAU,GAAa,MAAuB;EAClD,IAAI,GAAU;EACd,IAAM,IAAW,EAAY,SAAS,CAAG,IACrC,EAAY,QAAQ,MAAM,MAAM,CAAG,IACnC,IACE,CAAC,GAAG,GAAa,CAAG,IACpB,CAAC,CAAG;EAEV,AADK,KAAc,EAAa,CAAQ,GACxC,IAAW,CAAQ;CACrB;CAEA,OACE,kBAAC,OAAD;EACE,WAAW,EACT,sHACA,KAAY,qCACZ,CACF;EAEC,UAAA,EAAM,KAAK,MAAS;GACnB,IAAM,IAAO,EAAY,SAAS,EAAK,GAAG;GAC1C,OACE,kBAAC,OAAD;IAAoB,WAAW,EAAG,KAAQ,CAAC,EAAK,YAAY,gBAAgB;IAA5E,UAAA,CACE,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,EAAK;KACf,iBAAe;KACf,eAAe,EAAO,EAAK,KAAK,EAAK,QAAQ;KAC7C,WAAW,EACT,wOACA,EAAK,WACD,kCACA,uCACJ,KAAQ,CAAC,EAAK,YAAY,mBAC5B;KAXF,UAAA;MAaG,EAAK,QAAQ,kBAAC,QAAD;OAAM,WAAU;OAA2E,UAAA,EAAK;MAAW,CAAA;MACzH,kBAAC,QAAD;OAAM,WAAU;OAA2B,UAAA,EAAK;MAAY,CAAA;MAC3D,EAAK,SAAS,kBAAC,QAAD;OAAM,WAAU;OAA8B,UAAA,EAAK;MAAY,CAAA;MAC7E,KACC,kBAAC,QAAD;OAAM,WAAW,EAAG,iGAAiG,KAAQ,YAAY;OACvI,UAAA,kBAAC,GAAD,CAAe,CAAA;MACX,CAAA;KAEF;IACR,CAAA,GAAA,kBAAC,OAAD;KACE,WAAU;KACV,OAAO,EAAE,kBAAkB,IAAO,QAAQ,MAAM;KAEhD,UAAA,kBAAC,OAAD;MAAK,WAAU;MACb,UAAA,kBAAC,OAAD;OAAK,WAAU;OACZ,UAAA,EAAK;MACH,CAAA;KACF,CAAA;IACF,CAAA,CACF;GAjCK,GAAA,EAAK,GAiCV;EAET,CAAC;CACE,CAAA;AAET"}
@@ -6,6 +6,7 @@ export type ButtonState = {
6
6
  icon?: React.ReactNode;
7
7
  };
8
8
  export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
9
+ ref?: React.Ref<HTMLButtonElement>;
9
10
  children?: React.ReactNode;
10
11
  variant?: 'neutral' | 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error' | 'destructive' | 'ghost' | 'link';
11
12
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/Button/index.tsx"],"sourcesContent":["import React, { ReactElement, useState, useCallback } from 'react';\nimport { cn } from '../../utils/cn';\n\n// 状态项定义\nexport type ButtonState = {\n label: string; // 显示的字符/文本\n key: string; // 状态的唯一标识\n variant?: 'neutral' | 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error' | 'destructive' | 'ghost' | 'link';\n icon?: React.ReactNode; // 可选的图标\n};\n\nexport type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {\n children?: React.ReactNode;\n variant?: 'neutral' | 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error' | 'destructive' | 'ghost' | 'link';\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n outline?: boolean;\n dash?: boolean;\n soft?: boolean;\n active?: boolean;\n disabled?: boolean;\n wide?: boolean;\n block?: boolean;\n square?: boolean;\n circle?: boolean;\n leftSection?: React.ReactNode;\n rightSection?: React.ReactNode;\n onClick?: React.MouseEventHandler<HTMLButtonElement>;\n /** 原生 button type;不传时保持浏览器默认行为(表单内默认 submit) */\n type?: 'button' | 'submit' | 'reset';\n className?: string;\n\n // 状态切换相关\n states?: ButtonState[]; // 状态列表\n initialStateIndex?: number; // 初始状态索引\n onStateChange?: (state: ButtonState, clickCount: number, stateIndex: number) => void; // 状态改变回调\n};\n\nconst Button = ({\n children,\n variant = 'primary',\n size = 'sm',\n outline = false,\n dash = false,\n soft = false,\n active = false,\n disabled = false,\n wide = false,\n block = false,\n square = false,\n circle = false,\n leftSection,\n rightSection,\n onClick,\n type,\n className = '',\n states,\n initialStateIndex = 0,\n onStateChange,\n title,\n ...rest\n}: ButtonProps) => {\n // 状态管理\n const [currentStateIndex, setCurrentStateIndex] = useState(initialStateIndex);\n const [clickCount, setClickCount] = useState(0);\n\n // 是否是状态按钮\n const isStateful = states && states.length > 0;\n\n // 获取当前状态\n const currentState = isStateful ? states[currentStateIndex] : null;\n\n // 处理点击\n const handleClick = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {\n if (isStateful) {\n const newClickCount = clickCount + 1;\n const newStateIndex = (currentStateIndex + 1) % states.length;\n const newState = states[newStateIndex];\n\n setClickCount(newClickCount);\n setCurrentStateIndex(newStateIndex);\n\n // 调用状态改变回调\n if (onStateChange) {\n onStateChange(newState, newClickCount, newStateIndex);\n }\n }\n\n // 调用原始 onClick\n if (onClick) {\n onClick(event);\n }\n }, [isStateful, states, currentStateIndex, clickCount, onStateChange, onClick]);\n\n // 确定使用的 variant\n const effectiveVariant = currentState?.variant || variant;\n\n // 确定显示的内容\n const displayContent = isStateful ? (\n <>\n {currentState?.icon && <span className=\"mr-1\">{currentState.icon}</span>}\n {currentState?.label}\n </>\n ) : children;\n\n const baseClass = 'btn';\n const variantClass = effectiveVariant === 'destructive' ? 'btn-error btn-destructive' : effectiveVariant ? `btn-${effectiveVariant}` : '';\n const sizeClass = size ? `btn-${size}` : '';\n const outlineClass = outline ? 'btn-outline' : '';\n const dashClass = dash ? 'btn-dash' : '';\n const softClass = soft ? 'btn-soft' : '';\n const activeClass = active || isStateful ? 'btn-active' : '';\n const wideClass = wide ? 'btn-wide' : '';\n const blockClass = block ? 'btn-block' : '';\n const squareClass = square ? 'btn-square' : '';\n const circleClass = circle ? 'btn-circle' : '';\n\n const buttonClasses = cn(\n baseClass,\n variantClass,\n sizeClass,\n outlineClass,\n dashClass,\n softClass,\n activeClass,\n wideClass,\n blockClass,\n squareClass,\n circleClass,\n className\n );\n\n return (\n <button\n {...rest}\n type={type}\n className={buttonClasses}\n disabled={disabled}\n onClick={handleClick}\n title={title ?? (isStateful ? `当前状态: ${currentState?.key} (点击次数: ${clickCount})` : undefined)}\n >\n {!isStateful && leftSection && <span className=\"mr-2\">{leftSection}</span>}\n {displayContent}\n {!isStateful && rightSection && <span className=\"ml-2\">{rightSection}</span>}\n </button>\n );\n};\n\n// Button.Group 组件\nexport type ButtonGroupProps = {\n children: React.ReactNode;\n vertical?: boolean;\n className?: string;\n rounded?: 'left' | 'right' | 'both' | 'none' | 'top' | 'bottom';\n radius?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full';\n roundedStyle?: {\n left?: string;\n right?: string;\n top?: string;\n bottom?: string;\n };\n};\n\nconst ButtonGroup = ({\n children,\n vertical = false,\n className = '',\n rounded = 'none',\n radius = 'full',\n roundedStyle,\n}: ButtonGroupProps) => {\n const joinClasses = cn(\n 'join',\n vertical ? 'join-vertical' : 'join-horizontal',\n className\n );\n\n // 当rounded不为'none'时,强制使用'full'作为radius值\n const effectiveRadius = rounded !== 'none' ? 'full' : radius;\n\n return (\n <div className={joinClasses}>\n {React.Children.map(children, (child, index) => {\n // 只处理有效的React元素\n if (!React.isValidElement(child)) return child;\n\n const childElement = child as ReactElement<{ className?: string }>;\n const childClassName = childElement.props.className || '';\n const isFirstChild = index === 0;\n const isLastChild = index === React.Children.count(children) - 1;\n\n // 根据rounded属性添加额外的圆角类\n let roundedClasses = '';\n\n if (roundedStyle) {\n // 使用自定义圆角样式\n if (vertical) {\n if ((rounded === 'left' || rounded === 'top') && isFirstChild && roundedStyle.top) {\n roundedClasses = roundedStyle.top;\n } else if ((rounded === 'right' || rounded === 'bottom') && isLastChild && roundedStyle.bottom) {\n roundedClasses = roundedStyle.bottom;\n } else if (rounded === 'both') {\n if (isFirstChild && roundedStyle.top) roundedClasses = roundedStyle.top;\n if (isLastChild && roundedStyle.bottom) roundedClasses = roundedStyle.bottom;\n }\n } else {\n if ((rounded === 'left' || rounded === 'top') && isFirstChild && roundedStyle.left) {\n roundedClasses = roundedStyle.left;\n } else if ((rounded === 'right' || rounded === 'bottom') && isLastChild && roundedStyle.right) {\n roundedClasses = roundedStyle.right;\n } else if (rounded === 'both') {\n if (isFirstChild && roundedStyle.left) roundedClasses = roundedStyle.left;\n if (isLastChild && roundedStyle.right) roundedClasses = roundedStyle.right;\n }\n }\n } else {\n // 使用默认的圆角大小\n if (vertical) {\n // 垂直布局时,圆角应用在上下方\n if (rounded === 'left' || rounded === 'top') {\n if (isFirstChild) roundedClasses = `rounded-t-${effectiveRadius}`;\n } else if (rounded === 'right' || rounded === 'bottom') {\n if (isLastChild) roundedClasses = `rounded-b-${effectiveRadius}`;\n } else if (rounded === 'both') {\n if (isFirstChild) roundedClasses = `rounded-t-${effectiveRadius}`;\n if (isLastChild) roundedClasses = `rounded-b-${effectiveRadius}`;\n }\n } else {\n // 水平布局时,圆角应用在左右方\n if (rounded === 'left' || rounded === 'top') {\n if (isFirstChild) roundedClasses = `rounded-l-${effectiveRadius}`;\n } else if (rounded === 'right' || rounded === 'bottom') {\n if (isLastChild) roundedClasses = `rounded-r-${effectiveRadius}`;\n } else if (rounded === 'both') {\n if (isFirstChild) roundedClasses = `rounded-l-${effectiveRadius}`;\n if (isLastChild) roundedClasses = `rounded-r-${effectiveRadius}`;\n }\n }\n }\n\n const newClassName = `${childClassName} join-item ${roundedClasses}`.trim();\n\n return React.cloneElement(childElement, {\n className: newClassName,\n });\n })}\n </div>\n );\n};\n\n// Button.GroupSection 组件\nexport type ButtonGroupSectionProps = {\n children: React.ReactNode;\n className?: string;\n};\n\nconst ButtonGroupSection = ({\n children,\n className = '',\n}: ButtonGroupSectionProps) => {\n const sectionClasses = cn(\n 'join-item',\n 'flex',\n 'items-center',\n 'px-4',\n 'bg-base-200',\n 'border',\n 'border-base-300',\n className\n );\n\n return (\n <div className={sectionClasses}>\n {children}\n </div>\n );\n};\n\n// 扩展Button组件,添加Group和GroupSection子组件\ninterface ButtonCompound extends React.FC<ButtonProps> {\n Group: React.FC<ButtonGroupProps>;\n GroupSection: React.FC<ButtonGroupSectionProps>;\n}\n\nconst ButtonWithCompounds = Button as ButtonCompound;\nButtonWithCompounds.Group = ButtonGroup;\nButtonWithCompounds.GroupSection = ButtonGroupSection;\n\nexport default ButtonWithCompounds; "],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/Button/index.tsx"],"sourcesContent":["import React, { ReactElement, useState, useCallback } from 'react';\nimport { cn } from '../../utils/cn';\n\n// 状态项定义\nexport type ButtonState = {\n label: string; // 显示的字符/文本\n key: string; // 状态的唯一标识\n variant?: 'neutral' | 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error' | 'destructive' | 'ghost' | 'link';\n icon?: React.ReactNode; // 可选的图标\n};\n\nexport type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {\n ref?: React.Ref<HTMLButtonElement>;\n children?: React.ReactNode;\n variant?: 'neutral' | 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error' | 'destructive' | 'ghost' | 'link';\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n outline?: boolean;\n dash?: boolean;\n soft?: boolean;\n active?: boolean;\n disabled?: boolean;\n wide?: boolean;\n block?: boolean;\n square?: boolean;\n circle?: boolean;\n leftSection?: React.ReactNode;\n rightSection?: React.ReactNode;\n onClick?: React.MouseEventHandler<HTMLButtonElement>;\n /** 原生 button type;不传时保持浏览器默认行为(表单内默认 submit) */\n type?: 'button' | 'submit' | 'reset';\n className?: string;\n\n // 状态切换相关\n states?: ButtonState[]; // 状态列表\n initialStateIndex?: number; // 初始状态索引\n onStateChange?: (state: ButtonState, clickCount: number, stateIndex: number) => void; // 状态改变回调\n};\n\nconst Button = ({\n children,\n variant = 'primary',\n size = 'sm',\n outline = false,\n dash = false,\n soft = false,\n active = false,\n disabled = false,\n wide = false,\n block = false,\n square = false,\n circle = false,\n leftSection,\n rightSection,\n onClick,\n type,\n className = '',\n states,\n initialStateIndex = 0,\n onStateChange,\n title,\n ...rest\n}: ButtonProps) => {\n // 状态管理\n const [currentStateIndex, setCurrentStateIndex] = useState(initialStateIndex);\n const [clickCount, setClickCount] = useState(0);\n\n // 是否是状态按钮\n const isStateful = states && states.length > 0;\n\n // 获取当前状态\n const currentState = isStateful ? states[currentStateIndex] : null;\n\n // 处理点击\n const handleClick = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {\n if (isStateful) {\n const newClickCount = clickCount + 1;\n const newStateIndex = (currentStateIndex + 1) % states.length;\n const newState = states[newStateIndex];\n\n setClickCount(newClickCount);\n setCurrentStateIndex(newStateIndex);\n\n // 调用状态改变回调\n if (onStateChange) {\n onStateChange(newState, newClickCount, newStateIndex);\n }\n }\n\n // 调用原始 onClick\n if (onClick) {\n onClick(event);\n }\n }, [isStateful, states, currentStateIndex, clickCount, onStateChange, onClick]);\n\n // 确定使用的 variant\n const effectiveVariant = currentState?.variant || variant;\n\n // 确定显示的内容\n const displayContent = isStateful ? (\n <>\n {currentState?.icon && <span className=\"mr-1\">{currentState.icon}</span>}\n {currentState?.label}\n </>\n ) : children;\n\n const baseClass = 'btn';\n const variantClass = effectiveVariant === 'destructive' ? 'btn-error btn-destructive' : effectiveVariant ? `btn-${effectiveVariant}` : '';\n const sizeClass = size ? `btn-${size}` : '';\n const outlineClass = outline ? 'btn-outline' : '';\n const dashClass = dash ? 'btn-dash' : '';\n const softClass = soft ? 'btn-soft' : '';\n const activeClass = active || isStateful ? 'btn-active' : '';\n const wideClass = wide ? 'btn-wide' : '';\n const blockClass = block ? 'btn-block' : '';\n const squareClass = square ? 'btn-square' : '';\n const circleClass = circle ? 'btn-circle' : '';\n\n const buttonClasses = cn(\n baseClass,\n variantClass,\n sizeClass,\n outlineClass,\n dashClass,\n softClass,\n activeClass,\n wideClass,\n blockClass,\n squareClass,\n circleClass,\n className\n );\n\n return (\n <button\n {...rest}\n type={type}\n className={buttonClasses}\n disabled={disabled}\n onClick={handleClick}\n title={title ?? (isStateful ? `当前状态: ${currentState?.key} (点击次数: ${clickCount})` : undefined)}\n >\n {!isStateful && leftSection && <span className=\"mr-2\">{leftSection}</span>}\n {displayContent}\n {!isStateful && rightSection && <span className=\"ml-2\">{rightSection}</span>}\n </button>\n );\n};\n\n// Button.Group 组件\nexport type ButtonGroupProps = {\n children: React.ReactNode;\n vertical?: boolean;\n className?: string;\n rounded?: 'left' | 'right' | 'both' | 'none' | 'top' | 'bottom';\n radius?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full';\n roundedStyle?: {\n left?: string;\n right?: string;\n top?: string;\n bottom?: string;\n };\n};\n\nconst ButtonGroup = ({\n children,\n vertical = false,\n className = '',\n rounded = 'none',\n radius = 'full',\n roundedStyle,\n}: ButtonGroupProps) => {\n const joinClasses = cn(\n 'join',\n vertical ? 'join-vertical' : 'join-horizontal',\n className\n );\n\n // 当rounded不为'none'时,强制使用'full'作为radius值\n const effectiveRadius = rounded !== 'none' ? 'full' : radius;\n\n return (\n <div className={joinClasses}>\n {React.Children.map(children, (child, index) => {\n // 只处理有效的React元素\n if (!React.isValidElement(child)) return child;\n\n const childElement = child as ReactElement<{ className?: string }>;\n const childClassName = childElement.props.className || '';\n const isFirstChild = index === 0;\n const isLastChild = index === React.Children.count(children) - 1;\n\n // 根据rounded属性添加额外的圆角类\n let roundedClasses = '';\n\n if (roundedStyle) {\n // 使用自定义圆角样式\n if (vertical) {\n if ((rounded === 'left' || rounded === 'top') && isFirstChild && roundedStyle.top) {\n roundedClasses = roundedStyle.top;\n } else if ((rounded === 'right' || rounded === 'bottom') && isLastChild && roundedStyle.bottom) {\n roundedClasses = roundedStyle.bottom;\n } else if (rounded === 'both') {\n if (isFirstChild && roundedStyle.top) roundedClasses = roundedStyle.top;\n if (isLastChild && roundedStyle.bottom) roundedClasses = roundedStyle.bottom;\n }\n } else {\n if ((rounded === 'left' || rounded === 'top') && isFirstChild && roundedStyle.left) {\n roundedClasses = roundedStyle.left;\n } else if ((rounded === 'right' || rounded === 'bottom') && isLastChild && roundedStyle.right) {\n roundedClasses = roundedStyle.right;\n } else if (rounded === 'both') {\n if (isFirstChild && roundedStyle.left) roundedClasses = roundedStyle.left;\n if (isLastChild && roundedStyle.right) roundedClasses = roundedStyle.right;\n }\n }\n } else {\n // 使用默认的圆角大小\n if (vertical) {\n // 垂直布局时,圆角应用在上下方\n if (rounded === 'left' || rounded === 'top') {\n if (isFirstChild) roundedClasses = `rounded-t-${effectiveRadius}`;\n } else if (rounded === 'right' || rounded === 'bottom') {\n if (isLastChild) roundedClasses = `rounded-b-${effectiveRadius}`;\n } else if (rounded === 'both') {\n if (isFirstChild) roundedClasses = `rounded-t-${effectiveRadius}`;\n if (isLastChild) roundedClasses = `rounded-b-${effectiveRadius}`;\n }\n } else {\n // 水平布局时,圆角应用在左右方\n if (rounded === 'left' || rounded === 'top') {\n if (isFirstChild) roundedClasses = `rounded-l-${effectiveRadius}`;\n } else if (rounded === 'right' || rounded === 'bottom') {\n if (isLastChild) roundedClasses = `rounded-r-${effectiveRadius}`;\n } else if (rounded === 'both') {\n if (isFirstChild) roundedClasses = `rounded-l-${effectiveRadius}`;\n if (isLastChild) roundedClasses = `rounded-r-${effectiveRadius}`;\n }\n }\n }\n\n const newClassName = `${childClassName} join-item ${roundedClasses}`.trim();\n\n return React.cloneElement(childElement, {\n className: newClassName,\n });\n })}\n </div>\n );\n};\n\n// Button.GroupSection 组件\nexport type ButtonGroupSectionProps = {\n children: React.ReactNode;\n className?: string;\n};\n\nconst ButtonGroupSection = ({\n children,\n className = '',\n}: ButtonGroupSectionProps) => {\n const sectionClasses = cn(\n 'join-item',\n 'flex',\n 'items-center',\n 'px-4',\n 'bg-base-200',\n 'border',\n 'border-base-300',\n className\n );\n\n return (\n <div className={sectionClasses}>\n {children}\n </div>\n );\n};\n\n// 扩展Button组件,添加Group和GroupSection子组件\ninterface ButtonCompound extends React.FC<ButtonProps> {\n Group: React.FC<ButtonGroupProps>;\n GroupSection: React.FC<ButtonGroupSectionProps>;\n}\n\nconst ButtonWithCompounds = Button as ButtonCompound;\nButtonWithCompounds.Group = ButtonGroup;\nButtonWithCompounds.GroupSection = ButtonGroupSection;\n\nexport default ButtonWithCompounds; "],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import { type ButtonProps } from '../Button';
3
+ export type ButtonMenuItem = {
4
+ id: string;
5
+ label: string;
6
+ icon?: React.ReactNode;
7
+ disabled?: boolean;
8
+ onSelect?: () => void;
9
+ };
10
+ export type ButtonMenuProps = {
11
+ label: string;
12
+ icon?: React.ReactNode;
13
+ iconOnly?: boolean;
14
+ items: ButtonMenuItem[];
15
+ selectedId?: string;
16
+ onSelect?: (id: string) => void;
17
+ align?: 'start' | 'end';
18
+ disabled?: boolean;
19
+ variant?: ButtonProps['variant'];
20
+ size?: ButtonProps['size'];
21
+ className?: string;
22
+ 'aria-label'?: string;
23
+ open?: boolean;
24
+ defaultOpen?: boolean;
25
+ onOpenChange?: (open: boolean) => void;
26
+ };
27
+ /** A button-triggered action menu, or a single-choice menu when selectedId is supplied. */
28
+ export default function ButtonMenu({ label, icon, iconOnly, items, selectedId, onSelect, align, disabled, variant, size, className, 'aria-label': ariaLabel, open, defaultOpen, onOpenChange, }: ButtonMenuProps): React.JSX.Element;
@@ -0,0 +1,197 @@
1
+ import e from "../Button/index.js";
2
+ import t from "../Text/index.js";
3
+ /* empty css */
4
+ import { useCallback as n, useId as r, useLayoutEffect as i, useRef as a, useState as o } from "react";
5
+ import { Fragment as s, jsx as c, jsxs as l } from "react/jsx-runtime";
6
+ import { createPortal as u } from "react-dom";
7
+ //#region src/components/ButtonMenu/index.tsx
8
+ var d = () => /* @__PURE__ */ c("svg", {
9
+ viewBox: "0 0 16 16",
10
+ fill: "none",
11
+ "aria-hidden": "true",
12
+ children: /* @__PURE__ */ c("path", {
13
+ d: "m4 6 4 4 4-4",
14
+ stroke: "currentColor",
15
+ strokeWidth: "1.5",
16
+ strokeLinecap: "round",
17
+ strokeLinejoin: "round"
18
+ })
19
+ }), f = () => /* @__PURE__ */ c("svg", {
20
+ viewBox: "0 0 16 16",
21
+ fill: "none",
22
+ "aria-hidden": "true",
23
+ children: /* @__PURE__ */ c("path", {
24
+ d: "m3 8 3 3 7-7",
25
+ stroke: "currentColor",
26
+ strokeWidth: "1.5",
27
+ strokeLinecap: "round",
28
+ strokeLinejoin: "round"
29
+ })
30
+ });
31
+ function p({ label: p, icon: m, iconOnly: h = !1, items: g, selectedId: _, onSelect: v, align: y = "start", disabled: b = !1, variant: x = "ghost", size: S = "sm", className: C = "", "aria-label": w, open: T, defaultOpen: E = !1, onOpenChange: D }) {
32
+ let O = r(), k = a(null), A = a(null), j = a("selected"), [M, N] = o(E), P = b || g.length === 0, F = (T ?? M) && !P, [I, L] = o(), R = a(!1), z = n((e) => {
33
+ T === void 0 && N(e), e !== (T ?? M) && D?.(e);
34
+ }, [
35
+ T,
36
+ M,
37
+ D
38
+ ]), B = () => k.current?.focus({ preventScroll: !0 }), V = () => {
39
+ z(!1), B();
40
+ }, H = () => Array.from(A.current?.querySelectorAll("button:not(:disabled)") ?? []), U = (e) => {
41
+ if (!e) {
42
+ A.current?.focus({ preventScroll: !0 });
43
+ return;
44
+ }
45
+ e.focus({ preventScroll: !0 }), e.scrollIntoView({
46
+ block: "nearest",
47
+ inline: "nearest"
48
+ });
49
+ };
50
+ i(() => {
51
+ if (!F) return;
52
+ let e = k.current, t = A.current, n = () => {
53
+ let n = e.closest("[data-theme]")?.getAttribute("data-theme");
54
+ n == null ? t.removeAttribute("data-theme") : t.setAttribute("data-theme", n);
55
+ let r = getComputedStyle(e).direction === "rtl";
56
+ t.dir = r ? "rtl" : "ltr";
57
+ let i = window.visualViewport, a = (i?.offsetLeft ?? 0) + 8, o = (i?.offsetTop ?? 0) + 8, s = a + (i?.width ?? document.documentElement.clientWidth) - 16, c = o + (i?.height ?? window.innerHeight) - 16;
58
+ t.style.maxWidth = `${Math.max(0, Math.min(320, s - a))}px`;
59
+ let l = e.getBoundingClientRect(), u = Math.max(0, c - l.bottom - 4), d = Math.max(0, l.top - o - 4), f = u < Math.min(t.scrollHeight + 2, 352) && d > u;
60
+ t.style.maxHeight = `${Math.min(352, f ? d : u)}px`;
61
+ let p = t.getBoundingClientRect(), m = y === "end" === r ? l.left : l.right - p.width, h = f ? l.top - p.height - 4 : l.bottom + 4;
62
+ t.style.left = `${Math.max(a, Math.min(m, s - p.width))}px`, t.style.top = `${Math.max(o, Math.min(h, c - p.height))}px`;
63
+ };
64
+ n();
65
+ let r = new ResizeObserver(n);
66
+ r.observe(e), r.observe(t);
67
+ let i = new MutationObserver(n);
68
+ for (let t = e; t; t = t.parentElement) i.observe(t, {
69
+ attributes: !0,
70
+ attributeFilter: ["data-theme", "dir"]
71
+ });
72
+ return window.addEventListener("resize", n), window.addEventListener("scroll", n, !0), window.visualViewport?.addEventListener("resize", n), window.visualViewport?.addEventListener("scroll", n), () => {
73
+ r.disconnect(), i.disconnect(), window.removeEventListener("resize", n), window.removeEventListener("scroll", n, !0), window.visualViewport?.removeEventListener("resize", n), window.visualViewport?.removeEventListener("scroll", n);
74
+ };
75
+ }, [
76
+ F,
77
+ y,
78
+ g
79
+ ]), i(() => {
80
+ if (F && !R.current) {
81
+ let e = H(), t = j.current === "last" ? e.at(-1) : j.current === "first" ? e[0] : e.find((e) => e.dataset.itemId === _) ?? e[0];
82
+ U(t), j.current = "selected";
83
+ } else if (!F && R.current && document.activeElement === document.body) B();
84
+ else if (F && (document.activeElement === document.body || A.current?.contains(document.activeElement))) {
85
+ let e = document.activeElement;
86
+ (e === A.current || e?.matches(":disabled") || !A.current?.contains(e)) && U(H()[0]);
87
+ }
88
+ R.current = F;
89
+ }), i(() => {
90
+ if (!F) return;
91
+ let e = (e) => {
92
+ let t = e.target;
93
+ t instanceof Node && !k.current?.contains(t) && !A.current?.contains(t) && z(!1);
94
+ };
95
+ return document.addEventListener("pointerdown", e, !0), document.addEventListener("focusin", e), () => {
96
+ document.removeEventListener("pointerdown", e, !0), document.removeEventListener("focusin", e);
97
+ };
98
+ }, [F, z]);
99
+ let W = (e) => {
100
+ if (e.key === "Escape") e.preventDefault(), e.stopPropagation(), V();
101
+ else if (e.key === "Tab") B(), z(!1);
102
+ else if ([
103
+ "ArrowDown",
104
+ "ArrowUp",
105
+ "Home",
106
+ "End"
107
+ ].includes(e.key)) {
108
+ e.preventDefault();
109
+ let t = H(), n = t.indexOf(document.activeElement), r = e.key === "Home" ? 0 : e.key === "End" ? t.length - 1 : (n + (e.key === "ArrowDown" ? 1 : -1) + t.length) % t.length;
110
+ U(t[r]);
111
+ }
112
+ };
113
+ return /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ l(e, {
114
+ ref: k,
115
+ id: `${O}-trigger`,
116
+ type: "button",
117
+ variant: x,
118
+ size: S,
119
+ square: h,
120
+ disabled: P,
121
+ className: `dishui-button-menu-trigger ${C}`,
122
+ "aria-label": w ?? p,
123
+ title: h ? p : void 0,
124
+ "aria-haspopup": "menu",
125
+ "aria-expanded": F,
126
+ "aria-controls": F ? `${O}-menu` : void 0,
127
+ onClick: () => {
128
+ j.current = "selected", z(!F);
129
+ },
130
+ onKeyDown: (e) => {
131
+ P || (e.key === "ArrowDown" || e.key === "ArrowUp" ? (e.preventDefault(), j.current = e.key === "ArrowUp" ? "last" : "first", F ? U(e.key === "ArrowUp" ? H().at(-1) : H()[0]) : z(!0)) : F && e.key === "Escape" && W(e));
132
+ },
133
+ children: [
134
+ m != null && /* @__PURE__ */ c("span", {
135
+ className: "dishui-button-menu-icon",
136
+ "aria-hidden": "true",
137
+ children: m
138
+ }),
139
+ !h && /* @__PURE__ */ c(t, {
140
+ size: "sm",
141
+ weight: "medium",
142
+ color: "inherit",
143
+ className: "dishui-button-menu-label",
144
+ children: p
145
+ }),
146
+ (!h || m == null) && /* @__PURE__ */ c("span", {
147
+ className: "dishui-button-menu-icon",
148
+ "aria-hidden": "true",
149
+ children: /* @__PURE__ */ c(d, {})
150
+ })
151
+ ]
152
+ }), F && typeof document < "u" && u(/* @__PURE__ */ c("div", {
153
+ ref: A,
154
+ id: `${O}-menu`,
155
+ role: "menu",
156
+ "aria-labelledby": `${O}-trigger`,
157
+ tabIndex: -1,
158
+ className: "dishui-button-menu-surface",
159
+ onKeyDown: W,
160
+ children: g.map((e) => /* @__PURE__ */ l("button", {
161
+ type: "button",
162
+ "data-item-id": e.id,
163
+ role: _ === void 0 ? "menuitem" : "menuitemradio",
164
+ "aria-checked": _ === void 0 ? void 0 : _ === e.id,
165
+ disabled: e.disabled,
166
+ "aria-disabled": e.disabled || void 0,
167
+ tabIndex: !e.disabled && I === e.id ? 0 : -1,
168
+ className: "dishui-button-menu-item",
169
+ onFocus: () => L(e.id),
170
+ onClick: () => {
171
+ e.disabled || (V(), e.onSelect?.(), v?.(e.id));
172
+ },
173
+ children: [
174
+ e.icon != null && /* @__PURE__ */ c("span", {
175
+ className: "dishui-button-menu-icon",
176
+ "aria-hidden": "true",
177
+ children: e.icon
178
+ }),
179
+ /* @__PURE__ */ c(t, {
180
+ size: "sm",
181
+ color: "inherit",
182
+ className: "dishui-button-menu-label",
183
+ children: e.label
184
+ }),
185
+ _ !== void 0 && /* @__PURE__ */ c("span", {
186
+ className: "dishui-button-menu-check",
187
+ "aria-hidden": "true",
188
+ children: _ === e.id && /* @__PURE__ */ c(f, {})
189
+ })
190
+ ]
191
+ }, e.id))
192
+ }), document.body)] });
193
+ }
194
+ //#endregion
195
+ export { p as default };
196
+
197
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/ButtonMenu/index.tsx"],"sourcesContent":["import React, { useCallback, useId, useLayoutEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport Button, { type ButtonProps } from '../Button';\nimport Text from '../Text';\nimport './style.css';\n\nexport type ButtonMenuItem = {\n id: string;\n label: string;\n icon?: React.ReactNode;\n disabled?: boolean;\n onSelect?: () => void;\n};\n\nexport type ButtonMenuProps = {\n label: string;\n icon?: React.ReactNode;\n iconOnly?: boolean;\n items: ButtonMenuItem[];\n selectedId?: string;\n onSelect?: (id: string) => void;\n align?: 'start' | 'end';\n disabled?: boolean;\n variant?: ButtonProps['variant'];\n size?: ButtonProps['size'];\n className?: string;\n 'aria-label'?: string;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n};\n\nconst Chevron = () => <svg viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\"><path d=\"m4 6 4 4 4-4\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" /></svg>;\nconst Check = () => <svg viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\"><path d=\"m3 8 3 3 7-7\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" /></svg>;\n\n/** A button-triggered action menu, or a single-choice menu when selectedId is supplied. */\nexport default function ButtonMenu({\n label, icon, iconOnly = false, items, selectedId, onSelect, align = 'start',\n disabled = false, variant = 'ghost', size = 'sm', className = '',\n 'aria-label': ariaLabel, open, defaultOpen = false, onOpenChange,\n}: ButtonMenuProps) {\n const id = useId();\n const triggerRef = useRef<HTMLButtonElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const initialFocus = useRef<'first' | 'last' | 'selected'>('selected');\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const unavailable = disabled || items.length === 0;\n const isOpen = (open ?? internalOpen) && !unavailable;\n const [activeId, setActiveId] = useState<string>();\n const wasOpen = useRef(false);\n const requestOpen = useCallback((next: boolean) => {\n if (open === undefined) setInternalOpen(next);\n if (next !== (open ?? internalOpen)) onOpenChange?.(next);\n }, [open, internalOpen, onOpenChange]);\n const focusTrigger = () => triggerRef.current?.focus({ preventScroll: true });\n const close = () => { requestOpen(false); focusTrigger(); };\n const enabledElements = () => Array.from(menuRef.current?.querySelectorAll<HTMLButtonElement>('button:not(:disabled)') ?? []);\n const focusItem = (element?: HTMLButtonElement) => {\n if (!element) { menuRef.current?.focus({ preventScroll: true }); return; }\n element.focus({ preventScroll: true });\n element.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n };\n\n useLayoutEffect(() => {\n if (!isOpen) return;\n const trigger = triggerRef.current!;\n const menu = menuRef.current!;\n const position = () => {\n const theme = trigger.closest('[data-theme]')?.getAttribute('data-theme');\n if (theme !== null && theme !== undefined) menu.setAttribute('data-theme', theme);\n else menu.removeAttribute('data-theme');\n const rtl = getComputedStyle(trigger).direction === 'rtl';\n menu.dir = rtl ? 'rtl' : 'ltr';\n const viewport = window.visualViewport;\n const leftEdge = (viewport?.offsetLeft ?? 0) + 8;\n const topEdge = (viewport?.offsetTop ?? 0) + 8;\n const rightEdge = leftEdge + (viewport?.width ?? document.documentElement.clientWidth) - 16;\n const bottomEdge = topEdge + (viewport?.height ?? window.innerHeight) - 16;\n menu.style.maxWidth = `${Math.max(0, Math.min(320, rightEdge - leftEdge))}px`;\n const rect = trigger.getBoundingClientRect();\n const below = Math.max(0, bottomEdge - rect.bottom - 4);\n const above = Math.max(0, rect.top - topEdge - 4);\n const desiredHeight = Math.min(menu.scrollHeight + 2, 352);\n const upwards = below < desiredHeight && above > below;\n menu.style.maxHeight = `${Math.min(352, upwards ? above : below)}px`;\n const box = menu.getBoundingClientRect();\n const end = (align === 'end') !== rtl;\n const left = end ? rect.right - box.width : rect.left;\n const top = upwards ? rect.top - box.height - 4 : rect.bottom + 4;\n menu.style.left = `${Math.max(leftEdge, Math.min(left, rightEdge - box.width))}px`;\n menu.style.top = `${Math.max(topEdge, Math.min(top, bottomEdge - box.height))}px`;\n };\n position();\n const resize = new ResizeObserver(position);\n resize.observe(trigger);\n resize.observe(menu);\n const themeObserver = new MutationObserver(position);\n for (let ancestor: HTMLElement | null = trigger; ancestor; ancestor = ancestor.parentElement) {\n themeObserver.observe(ancestor, { attributes: true, attributeFilter: ['data-theme', 'dir'] });\n }\n window.addEventListener('resize', position);\n window.addEventListener('scroll', position, true);\n window.visualViewport?.addEventListener('resize', position);\n window.visualViewport?.addEventListener('scroll', position);\n return () => {\n resize.disconnect();\n themeObserver.disconnect();\n window.removeEventListener('resize', position);\n window.removeEventListener('scroll', position, true);\n window.visualViewport?.removeEventListener('resize', position);\n window.visualViewport?.removeEventListener('scroll', position);\n };\n }, [isOpen, align, items]);\n\n useLayoutEffect(() => {\n if (isOpen && !wasOpen.current) {\n const enabled = enabledElements();\n const target = initialFocus.current === 'last' ? enabled.at(-1)\n : initialFocus.current === 'first' ? enabled[0]\n : enabled.find(element => element.dataset.itemId === selectedId) ?? enabled[0];\n focusItem(target);\n initialFocus.current = 'selected';\n } else if (!isOpen && wasOpen.current && document.activeElement === document.body) {\n // External controlled closes should not strand focus on the removed portal.\n focusTrigger();\n } else if (isOpen && (document.activeElement === document.body || menuRef.current?.contains(document.activeElement))) {\n const focused = document.activeElement;\n if (focused === menuRef.current || focused?.matches(':disabled') || !menuRef.current?.contains(focused)) focusItem(enabledElements()[0]);\n }\n wasOpen.current = isOpen;\n });\n\n useLayoutEffect(() => {\n if (!isOpen) return;\n const dismiss = (event: Event) => {\n const target = event.target;\n if (target instanceof Node && !triggerRef.current?.contains(target) && !menuRef.current?.contains(target)) requestOpen(false);\n };\n document.addEventListener('pointerdown', dismiss, true);\n document.addEventListener('focusin', dismiss);\n return () => {\n document.removeEventListener('pointerdown', dismiss, true);\n document.removeEventListener('focusin', dismiss);\n };\n }, [isOpen, requestOpen]);\n\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.preventDefault();\n event.stopPropagation();\n close();\n } else if (event.key === 'Tab') {\n // The portal is last in DOM order. Resume native traversal from its trigger,\n // without preventing Tab, so both directions reach the adjacent page control.\n focusTrigger();\n requestOpen(false);\n } else if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) {\n event.preventDefault();\n const enabled = enabledElements();\n const index = enabled.indexOf(document.activeElement as HTMLButtonElement);\n const next = event.key === 'Home' ? 0 : event.key === 'End' ? enabled.length - 1\n : (index + (event.key === 'ArrowDown' ? 1 : -1) + enabled.length) % enabled.length;\n focusItem(enabled[next]);\n }\n };\n\n return <>\n <Button ref={triggerRef} id={`${id}-trigger`} type=\"button\" variant={variant} size={size}\n square={iconOnly} disabled={unavailable}\n className={`dishui-button-menu-trigger ${className}`}\n aria-label={ariaLabel ?? label} title={iconOnly ? label : undefined}\n aria-haspopup=\"menu\" aria-expanded={isOpen} aria-controls={isOpen ? `${id}-menu` : undefined}\n onClick={() => { initialFocus.current = 'selected'; requestOpen(!isOpen); }}\n onKeyDown={event => {\n if (unavailable) return;\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n initialFocus.current = event.key === 'ArrowUp' ? 'last' : 'first';\n if (isOpen) focusItem(event.key === 'ArrowUp' ? enabledElements().at(-1) : enabledElements()[0]);\n else requestOpen(true);\n } else if (isOpen && event.key === 'Escape') handleKeyDown(event);\n }}>\n {icon != null && <span className=\"dishui-button-menu-icon\" aria-hidden=\"true\">{icon}</span>}\n {!iconOnly && <Text size=\"sm\" weight=\"medium\" color=\"inherit\" className=\"dishui-button-menu-label\">{label}</Text>}\n {(!iconOnly || icon == null) && <span className=\"dishui-button-menu-icon\" aria-hidden=\"true\"><Chevron /></span>}\n </Button>\n {isOpen && typeof document !== 'undefined' && createPortal(\n <div ref={menuRef} id={`${id}-menu`} role=\"menu\" aria-labelledby={`${id}-trigger`}\n tabIndex={-1} className=\"dishui-button-menu-surface\" onKeyDown={handleKeyDown}>\n {items.map(item => <button key={item.id} type=\"button\" data-item-id={item.id}\n role={selectedId === undefined ? 'menuitem' : 'menuitemradio'}\n aria-checked={selectedId === undefined ? undefined : selectedId === item.id}\n disabled={item.disabled} aria-disabled={item.disabled || undefined}\n tabIndex={!item.disabled && activeId === item.id ? 0 : -1}\n className=\"dishui-button-menu-item\" onFocus={() => setActiveId(item.id)}\n onClick={() => {\n if (item.disabled) return;\n close();\n item.onSelect?.();\n onSelect?.(item.id);\n }}>\n {item.icon != null && <span className=\"dishui-button-menu-icon\" aria-hidden=\"true\">{item.icon}</span>}\n <Text size=\"sm\" color=\"inherit\" className=\"dishui-button-menu-label\">{item.label}</Text>\n {selectedId !== undefined && <span className=\"dishui-button-menu-check\" aria-hidden=\"true\">{selectedId === item.id && <Check />}</span>}\n </button>)}\n </div>, document.body)}\n </>;\n}\n"],"mappings":"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"}
@@ -11,6 +11,9 @@ export type CardProps = {
11
11
  compact?: boolean;
12
12
  hover?: boolean;
13
13
  glass?: boolean;
14
+ /** Background opacity only (0..1, default 0.15). Non-finite values use the default. */
15
+ glassOpacity?: number;
16
+ style?: React.CSSProperties;
14
17
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
15
18
  className?: string;
16
19
  };
@@ -42,7 +45,7 @@ export type CardFigureProps = {
42
45
  /** 指定 figure 高度(数字按 px,支持 "16rem"/"50%" 等);内部图片自动填满该高度 */
43
46
  height?: number | string;
44
47
  };
45
- export type CardFeatureProps = {
48
+ export type CardFeatureProps = Pick<CardProps, 'glassOpacity' | 'style'> & {
46
49
  /** Icon node (usually a lucide / inline svg), rendered inside a tinted container. */
47
50
  icon?: React.ReactNode;
48
51
  /** Feature title. */
@@ -66,7 +69,7 @@ export type CardFeatureProps = {
66
69
  /** Click handler. */
67
70
  onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
68
71
  };
69
- export type CardQuoteProps = {
72
+ export type CardQuoteProps = Pick<CardProps, 'glassOpacity' | 'style'> & {
70
73
  /** The quoted text. */
71
74
  children: React.ReactNode;
72
75
  /** Author name (rendered in primary color). */
@@ -90,7 +93,7 @@ export type CardQuoteProps = {
90
93
  /** Click handler. */
91
94
  onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
92
95
  };
93
- export type CardMetaProps = {
96
+ export type CardMetaProps = Pick<CardProps, 'glassOpacity' | 'style'> & {
94
97
  /** Card title (top-left). */
95
98
  title: React.ReactNode;
96
99
  /** Badge / tag node (top-right, e.g. a visibility badge). */
@@ -113,7 +116,7 @@ export type CardMetaProps = {
113
116
  onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
114
117
  };
115
118
  export type CardInfoTone = 'primary' | 'success' | 'warning' | 'info' | 'error' | 'neutral';
116
- export type CardInfoProps = {
119
+ export type CardInfoProps = Pick<CardProps, 'glassOpacity' | 'style'> & {
117
120
  /** Metric name. */
118
121
  label: React.ReactNode;
119
122
  /** Metric value (number or short text). */