dishui 0.0.82 → 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.
- package/dist/components/Button/index.d.ts +1 -0
- package/dist/components/Button/index.js.map +1 -1
- package/dist/components/ButtonMenu/index.d.ts +28 -0
- package/dist/components/ButtonMenu/index.js +197 -0
- package/dist/components/ButtonMenu/index.js.map +1 -0
- package/dist/components/Demo/index.d.ts +5 -0
- package/dist/components/Demo/index.js +131 -56
- package/dist/components/Demo/index.js.map +1 -1
- package/dist/components/Demo/source.d.ts +9 -0
- package/dist/components/Demo/source.js +32 -0
- package/dist/components/Demo/source.js.map +1 -0
- package/dist/components/Field/index.d.ts +8 -0
- package/dist/components/Field/index.js +43 -0
- package/dist/components/Field/index.js.map +1 -0
- package/dist/components/LayoutSuite/DashboardSuite.d.ts +35 -0
- package/dist/components/LayoutSuite/DashboardSuite.js +61 -0
- package/dist/components/LayoutSuite/DashboardSuite.js.map +1 -0
- package/dist/components/LayoutSuite/index.d.ts +56 -0
- package/dist/components/LayoutSuite/index.js +101 -0
- package/dist/components/LayoutSuite/index.js.map +1 -0
- package/dist/components/Modal/focusScope.d.ts +1 -0
- package/dist/components/Modal/focusScope.js +81 -0
- package/dist/components/Modal/focusScope.js.map +1 -0
- package/dist/components/Modal/index.d.ts +4 -1
- package/dist/components/Modal/index.js +48 -43
- package/dist/components/Modal/index.js.map +1 -1
- package/dist/components/Page/Float.d.ts +14 -0
- package/dist/components/Page/Float2.js +154 -0
- package/dist/components/Page/Float2.js.map +1 -0
- package/dist/components/Page/index.d.ts +18 -5
- package/dist/components/Page/index.js +103 -34
- package/dist/components/Page/index.js.map +1 -1
- package/dist/components/Page/resize.d.ts +28 -0
- package/dist/components/Page/resize2.js +95 -0
- package/dist/components/Page/resize2.js.map +1 -0
- package/dist/components/PageHeader/index.d.ts +10 -3
- package/dist/components/PageHeader/index.js +46 -25
- package/dist/components/PageHeader/index.js.map +1 -1
- package/dist/components/PagePanel/index.d.ts +29 -0
- package/dist/components/PagePanel/index.js +99 -0
- package/dist/components/PagePanel/index.js.map +1 -0
- package/dist/components/Resizable/ResizeGrip.d.ts +5 -0
- package/dist/components/Resizable/ResizeGrip.js +14 -0
- package/dist/components/Resizable/ResizeGrip.js.map +1 -0
- package/dist/components/Resizable/index.js +57 -59
- package/dist/components/Resizable/index.js.map +1 -1
- package/dist/components/Resizable/useResize.d.ts +6 -0
- package/dist/components/Resizable/useResize.js +29 -28
- package/dist/components/Resizable/useResize.js.map +1 -1
- package/dist/components/SidebarToggleButton/index.d.ts +5 -3
- package/dist/components/SidebarToggleButton/index.js +24 -21
- package/dist/components/SidebarToggleButton/index.js.map +1 -1
- package/dist/components/Table/index.d.ts +10 -12
- package/dist/components/Table/index.js +25 -20
- package/dist/components/Table/index.js.map +1 -1
- package/dist/components/Text/index.d.ts +1 -1
- package/dist/components/Text/index.js +11 -10
- package/dist/components/Text/index.js.map +1 -1
- package/dist/components/Textarea/index.d.ts +14 -0
- package/dist/components/Textarea/index.js +44 -0
- package/dist/components/Textarea/index.js.map +1 -0
- package/dist/components/Tree/Tree.d.ts +2 -0
- package/dist/components/Tree/Tree.js +49 -40
- package/dist/components/Tree/Tree.js.map +1 -1
- package/dist/dishui.css +1 -1
- package/dist/index-nostyled.d.ts +9 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.js +119 -113
- package/dist/layouts/Sidebar.d.ts +12 -1
- package/dist/layouts/Sidebar.js +121 -97
- package/dist/layouts/Sidebar.js.map +1 -1
- package/dist/lite.d.ts +10 -2
- package/dist/lite.js +17 -12
- package/dist/utils/menu.js +14 -16
- package/dist/utils/menu.js.map +1 -1
- package/package.json +3 -1
|
@@ -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":";;;;AAsCA,IAAM,KAAU,EACd,aACA,aAAU,WACV,UAAO,MACP,aAAU,IACV,UAAO,IACP,UAAO,IACP,YAAS,IACT,cAAW,IACX,UAAO,IACP,WAAQ,IACR,YAAS,IACT,YAAS,IACT,gBACA,iBACA,YACA,SACA,eAAY,IACZ,WACA,uBAAoB,GACpB,kBACA,UACA,GAAG,QACc;CAEjB,IAAM,CAAC,GAAmB,KAAwB,EAAS,CAAiB,GACtE,CAAC,GAAY,KAAiB,EAAS,CAAC,GAGxC,IAAa,KAAU,EAAO,SAAS,GAGvC,IAAe,IAAa,EAAO,KAAqB,MAGxD,IAAc,GAAa,MAA+C;EAC9E,IAAI,GAAY;GACd,IAAM,IAAgB,IAAa,GAC7B,KAAiB,IAAoB,KAAK,EAAO,QACjD,IAAW,EAAO;GAMxB,AAJA,EAAc,CAAa,GAC3B,EAAqB,CAAa,GAG9B,KACF,EAAc,GAAU,GAAe,CAAa;EAExD;EAGA,AAAI,KACF,EAAQ,CAAK;CAEjB,GAAG;EAAC;EAAY;EAAQ;EAAmB;EAAY;EAAe;CAAO,CAAC,GAGxE,IAAmB,GAAc,WAAW,GAG5C,IAAiB,IACrB,kBAAA,GAAA,EAAA,UAAA,CACG,GAAc,QAAQ,kBAAC,QAAD;EAAM,WAAU;EAAQ,UAAA,EAAa;CAAW,CAAA,GACtE,GAAc,KACf,EAAA,CAAA,IACA,GAGE,IAAe,MAAqB,gBAAgB,8BAA8B,IAAmB,OAAO,MAAqB,IACjI,IAAY,IAAO,OAAO,MAAS,IAUnC,IAAgB,EACpB,OACA,GACA,GAZmB,IAAU,gBAAgB,IAC7B,IAAO,aAAa,IACpB,IAAO,aAAa,IAClB,KAAU,IAAa,eAAe,IACxC,IAAO,aAAa,IACnB,IAAQ,cAAc,IACrB,IAAS,eAAe,IACxB,IAAS,eAAe,IAc1C,CACF;CAEA,OACE,kBAAC,UAAD;EACE,GAAI;EACE;EACN,WAAW;EACD;EACV,SAAS;EACT,OAAO,MAAU,IAAa,SAAS,GAAc,IAAI,UAAU,EAAW,KAAK,KAAA;EANrF,UAAA;GAQG,CAAC,KAAc,KAAe,kBAAC,QAAD;IAAM,WAAU;IAAQ,UAAA;GAAkB,CAAA;GACxE;GACA,CAAC,KAAc,KAAgB,kBAAC,QAAD;IAAM,WAAU;IAAQ,UAAA;GAAmB,CAAA;EACrE;;AAEZ,GAiBM,KAAe,EACnB,aACA,cAAW,IACX,eAAY,IACZ,aAAU,QACV,YAAS,QACT,sBACsB;CACtB,IAAM,IAAc,EAClB,QACA,IAAW,kBAAkB,mBAC7B,CACF,GAGM,IAAkB,MAAY,SAAkB,IAAT;CAE7C,OACE,kBAAC,OAAD;EAAK,WAAW;EACb,UAAA,EAAM,SAAS,IAAI,IAAW,GAAO,MAAU;GAE9C,IAAI,CAAC,EAAM,eAAe,CAAK,GAAG,OAAO;GAEzC,IAAM,IAAe,GACf,IAAiB,EAAa,MAAM,aAAa,IACjD,IAAe,MAAU,GACzB,IAAc,MAAU,EAAM,SAAS,MAAM,CAAQ,IAAI,GAG3D,IAAiB;GAErB,AAAI,IAEE,KACG,MAAY,UAAU,MAAY,UAAU,KAAgB,EAAa,MAC5E,IAAiB,EAAa,OACpB,MAAY,WAAW,MAAY,aAAa,KAAe,EAAa,SACtF,IAAiB,EAAa,SACrB,MAAY,WACjB,KAAgB,EAAa,QAAK,IAAiB,EAAa,MAChE,KAAe,EAAa,WAAQ,IAAiB,EAAa,YAGnE,MAAY,UAAU,MAAY,UAAU,KAAgB,EAAa,OAC5E,IAAiB,EAAa,QACpB,MAAY,WAAW,MAAY,aAAa,KAAe,EAAa,QACtF,IAAiB,EAAa,QACrB,MAAY,WACjB,KAAgB,EAAa,SAAM,IAAiB,EAAa,OACjE,KAAe,EAAa,UAAO,IAAiB,EAAa,UAKrE,IAEE,MAAY,UAAU,MAAY,QAChC,MAAc,IAAiB,aAAa,OACvC,MAAY,WAAW,MAAY,WACxC,MAAa,IAAiB,aAAa,OACtC,MAAY,WACjB,MAAc,IAAiB,aAAa,MAC5C,MAAa,IAAiB,aAAa,QAI7C,MAAY,UAAU,MAAY,QAChC,MAAc,IAAiB,aAAa,OACvC,MAAY,WAAW,MAAY,WACxC,MAAa,IAAiB,aAAa,OACtC,MAAY,WACjB,MAAc,IAAiB,aAAa,MAC5C,MAAa,IAAiB,aAAa;GAKrD,IAAM,IAAe,GAAG,EAAe,aAAa,IAAiB,KAAK;GAE1E,OAAO,EAAM,aAAa,GAAc,EACtC,WAAW,EACb,CAAC;EACH,CAAC;CACE,CAAA;AAET,GAQM,KAAsB,EAC1B,aACA,eAAY,SACiB;CAC7B,IAAM,IAAiB,EACrB,aACA,QACA,gBACA,QACA,eACA,UACA,mBACA,CACF;CAEA,OACE,kBAAC,OAAD;EAAK,WAAW;EACb;CACE,CAAA;AAET,GAQM,IAAsB;AAC5B,EAAoB,QAAQ,GAC5B,EAAoB,eAAe"}
|
|
@@ -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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"}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { type DemoSource } from './source';
|
|
3
|
+
export type { DemoSource } from './source';
|
|
2
4
|
export interface CodeBlockProps {
|
|
3
5
|
children: string;
|
|
4
6
|
language?: string;
|
|
5
7
|
}
|
|
8
|
+
type DemoSourceLoader = () => Promise<DemoSource>;
|
|
6
9
|
export interface DemoProps {
|
|
7
10
|
title: string;
|
|
8
11
|
description?: string;
|
|
9
12
|
children: React.ReactNode;
|
|
10
13
|
code: string;
|
|
11
14
|
language?: string;
|
|
15
|
+
source?: DemoSource | DemoSourceLoader;
|
|
16
|
+
sourceContext?: Record<string, unknown>;
|
|
12
17
|
}
|
|
13
18
|
declare const Demo: React.FC<DemoProps>;
|
|
14
19
|
export default Demo;
|
|
@@ -1,35 +1,41 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import e from "../Button/index.js";
|
|
2
|
+
import t from "../Text/index.js";
|
|
3
|
+
import { useCopyToClipboard as n } from "../../hooks/useCopyToClipboard.js";
|
|
4
|
+
import { formatDemoSource as r } from "./source.js";
|
|
5
|
+
import { useEffect as i, useId as a, useRef as o, useState as s } from "react";
|
|
6
|
+
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
7
|
+
import { Prism as u } from "react-syntax-highlighter";
|
|
8
|
+
import { coldarkDark as d } from "react-syntax-highlighter/dist/esm/styles/prism";
|
|
6
9
|
//#region src/components/Demo/index.tsx
|
|
7
|
-
var
|
|
8
|
-
let { copied:
|
|
9
|
-
return /* @__PURE__ */
|
|
10
|
+
var f = ({ children: r, language: i = "jsx" }) => {
|
|
11
|
+
let { copied: a, error: o, copy: s } = n();
|
|
12
|
+
return /* @__PURE__ */ l("div", {
|
|
10
13
|
className: "min-w-0 overflow-hidden rounded-lg border border-base-300",
|
|
11
|
-
children: [/* @__PURE__ */
|
|
14
|
+
children: [/* @__PURE__ */ l("div", {
|
|
12
15
|
className: "flex items-center justify-between gap-3 bg-base-200 px-4 py-2",
|
|
13
|
-
children: [/* @__PURE__ */
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
children: [/* @__PURE__ */ c(t, {
|
|
17
|
+
size: "xs",
|
|
18
|
+
mono: !0,
|
|
19
|
+
color: "secondary",
|
|
20
|
+
children: i
|
|
21
|
+
}), /* @__PURE__ */ l("div", {
|
|
17
22
|
className: "flex items-center gap-2",
|
|
18
|
-
children: [/* @__PURE__ */
|
|
23
|
+
children: [/* @__PURE__ */ c(t, {
|
|
19
24
|
role: "status",
|
|
20
|
-
|
|
21
|
-
children:
|
|
22
|
-
}), /* @__PURE__ */
|
|
25
|
+
size: "xs",
|
|
26
|
+
children: o ? "Copy failed / 复制失败,请手动复制" : a ? "Copied / 已复制" : ""
|
|
27
|
+
}), /* @__PURE__ */ c(e, {
|
|
23
28
|
type: "button",
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
variant: "ghost",
|
|
30
|
+
size: "xs",
|
|
31
|
+
onClick: () => void s(r),
|
|
26
32
|
"aria-label": "Copy source code / 复制源代码",
|
|
27
33
|
children: "Copy / 复制"
|
|
28
34
|
})]
|
|
29
35
|
})]
|
|
30
|
-
}), /* @__PURE__ */
|
|
31
|
-
language:
|
|
32
|
-
style:
|
|
36
|
+
}), /* @__PURE__ */ c(u, {
|
|
37
|
+
language: i,
|
|
38
|
+
style: d,
|
|
33
39
|
tabIndex: 0,
|
|
34
40
|
"aria-label": "Source code / 源代码",
|
|
35
41
|
customStyle: {
|
|
@@ -41,59 +47,128 @@ var s = ({ children: t, language: n = "jsx" }) => {
|
|
|
41
47
|
maxHeight: "32rem",
|
|
42
48
|
overflow: "auto"
|
|
43
49
|
},
|
|
44
|
-
children:
|
|
50
|
+
children: r
|
|
45
51
|
})]
|
|
46
52
|
});
|
|
47
|
-
},
|
|
48
|
-
let [
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
|
|
53
|
+
}, p = ({ title: n, description: u, children: d, code: p, language: m = "jsx", source: h, sourceContext: g }) => {
|
|
54
|
+
let [_, v] = s(!1), [y, b] = s(0), [x, S] = s(null), C = o(null);
|
|
55
|
+
i(() => {
|
|
56
|
+
if (!_ || typeof h != "function") return;
|
|
57
|
+
let e = !0;
|
|
58
|
+
return (C.current?.loader !== h || C.current.attempt !== y) && (C.current = {
|
|
59
|
+
loader: h,
|
|
60
|
+
attempt: y,
|
|
61
|
+
promise: Promise.resolve().then(() => h())
|
|
62
|
+
}), C.current.promise.then((t) => {
|
|
63
|
+
e && S({
|
|
64
|
+
loader: h,
|
|
65
|
+
attempt: y,
|
|
66
|
+
status: "success",
|
|
67
|
+
data: t
|
|
68
|
+
});
|
|
69
|
+
}, () => {
|
|
70
|
+
e && S({
|
|
71
|
+
loader: h,
|
|
72
|
+
attempt: y,
|
|
73
|
+
status: "error"
|
|
74
|
+
});
|
|
75
|
+
}), () => {
|
|
76
|
+
e = !1;
|
|
77
|
+
};
|
|
78
|
+
}, [
|
|
79
|
+
h,
|
|
80
|
+
_,
|
|
81
|
+
y
|
|
82
|
+
]);
|
|
83
|
+
let w = typeof h == "function" && x?.loader === h && x.attempt === y ? x : null, T = typeof h == "function" ? w?.status === "success" ? w.data : void 0 : h, E = typeof h == "function" && !w, D = a(), O = `${D}-title`, k = `${D}-code`;
|
|
84
|
+
return /* @__PURE__ */ l("section", {
|
|
85
|
+
"aria-labelledby": O,
|
|
86
|
+
className: "dishui-demo mb-4 min-w-0 rounded-lg border border-hairline bg-page",
|
|
52
87
|
children: [
|
|
53
|
-
/* @__PURE__ */
|
|
54
|
-
className: "border-b border-
|
|
55
|
-
children: [/* @__PURE__ */
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
88
|
+
/* @__PURE__ */ l("div", {
|
|
89
|
+
className: "border-b border-hairline bg-subtle px-4 py-3 rounded-t-lg",
|
|
90
|
+
children: [/* @__PURE__ */ c(t, {
|
|
91
|
+
as: "h3",
|
|
92
|
+
id: O,
|
|
93
|
+
size: "sm",
|
|
94
|
+
weight: "semibold",
|
|
95
|
+
children: n
|
|
96
|
+
}), u && /* @__PURE__ */ c(t, {
|
|
97
|
+
as: "p",
|
|
98
|
+
size: "sm",
|
|
99
|
+
color: "secondary",
|
|
100
|
+
className: "mt-1 leading-relaxed",
|
|
101
|
+
children: u
|
|
62
102
|
})]
|
|
63
103
|
}),
|
|
64
|
-
/* @__PURE__ */
|
|
65
|
-
className: "flex min-w-0 flex-wrap items-center gap-4
|
|
66
|
-
children:
|
|
104
|
+
/* @__PURE__ */ c("div", {
|
|
105
|
+
className: "flex min-w-0 flex-wrap items-center gap-4 p-4",
|
|
106
|
+
children: d
|
|
67
107
|
}),
|
|
68
|
-
/* @__PURE__ */
|
|
108
|
+
/* @__PURE__ */ l("div", {
|
|
69
109
|
className: "border-t border-base-300",
|
|
70
|
-
children: [/* @__PURE__ */
|
|
110
|
+
children: [/* @__PURE__ */ l(e, {
|
|
71
111
|
type: "button",
|
|
72
|
-
|
|
73
|
-
"
|
|
74
|
-
"aria-
|
|
75
|
-
|
|
76
|
-
|
|
112
|
+
variant: "ghost",
|
|
113
|
+
className: "flex w-full items-center justify-between rounded-none rounded-b-lg px-4 text-xs font-medium",
|
|
114
|
+
"aria-expanded": _,
|
|
115
|
+
"aria-controls": k,
|
|
116
|
+
onClick: () => v((e) => !e),
|
|
117
|
+
children: [/* @__PURE__ */ c(t, {
|
|
118
|
+
size: "xs",
|
|
119
|
+
weight: "medium",
|
|
120
|
+
color: "inherit",
|
|
121
|
+
children: "Source Code / 源代码"
|
|
122
|
+
}), /* @__PURE__ */ c("svg", {
|
|
77
123
|
"aria-hidden": "true",
|
|
78
124
|
xmlns: "http://www.w3.org/2000/svg",
|
|
79
|
-
className: `h-4 w-4 transition-transform motion-reduce:transition-none ${
|
|
125
|
+
className: `h-4 w-4 transition-transform motion-reduce:transition-none ${_ ? "rotate-180" : ""}`,
|
|
80
126
|
fill: "none",
|
|
81
127
|
viewBox: "0 0 24 24",
|
|
82
128
|
stroke: "currentColor",
|
|
83
|
-
children: /* @__PURE__ */
|
|
129
|
+
children: /* @__PURE__ */ c("path", {
|
|
84
130
|
strokeLinecap: "round",
|
|
85
131
|
strokeLinejoin: "round",
|
|
86
132
|
strokeWidth: "2",
|
|
87
133
|
d: "M19 9l-7 7-7-7"
|
|
88
134
|
})
|
|
89
135
|
})]
|
|
90
|
-
}), /* @__PURE__ */
|
|
91
|
-
id:
|
|
92
|
-
hidden: !
|
|
136
|
+
}), /* @__PURE__ */ c("div", {
|
|
137
|
+
id: k,
|
|
138
|
+
hidden: !_,
|
|
93
139
|
className: "min-w-0 border-t border-base-300 p-4",
|
|
94
|
-
children:
|
|
95
|
-
|
|
96
|
-
children: c
|
|
140
|
+
children: _ && /* @__PURE__ */ c("div", {
|
|
141
|
+
className: "min-w-0 space-y-3",
|
|
142
|
+
children: /* @__PURE__ */ c("div", {
|
|
143
|
+
className: "min-w-0",
|
|
144
|
+
"aria-busy": E,
|
|
145
|
+
children: E ? /* @__PURE__ */ c(t, {
|
|
146
|
+
as: "p",
|
|
147
|
+
role: "status",
|
|
148
|
+
size: "sm",
|
|
149
|
+
children: "Loading source code… / 正在加载源码…"
|
|
150
|
+
}) : w?.status === "error" ? /* @__PURE__ */ l("div", {
|
|
151
|
+
className: "space-y-2",
|
|
152
|
+
children: [/* @__PURE__ */ c(t, {
|
|
153
|
+
as: "p",
|
|
154
|
+
role: "alert",
|
|
155
|
+
size: "sm",
|
|
156
|
+
children: "Could not load source code. Please retry. / 源码加载失败,请重试。"
|
|
157
|
+
}), /* @__PURE__ */ c(e, {
|
|
158
|
+
type: "button",
|
|
159
|
+
variant: "ghost",
|
|
160
|
+
size: "sm",
|
|
161
|
+
onClick: () => b((e) => e + 1),
|
|
162
|
+
children: "Retry loading source / 重新加载源码"
|
|
163
|
+
})]
|
|
164
|
+
}) : T ? /* @__PURE__ */ c(f, {
|
|
165
|
+
language: "tsx",
|
|
166
|
+
children: r(T, g)
|
|
167
|
+
}) : h ? null : /* @__PURE__ */ c(f, {
|
|
168
|
+
language: m,
|
|
169
|
+
children: p
|
|
170
|
+
})
|
|
171
|
+
})
|
|
97
172
|
})
|
|
98
173
|
})]
|
|
99
174
|
})
|
|
@@ -101,6 +176,6 @@ var s = ({ children: t, language: n = "jsx" }) => {
|
|
|
101
176
|
});
|
|
102
177
|
};
|
|
103
178
|
//#endregion
|
|
104
|
-
export {
|
|
179
|
+
export { p as default };
|
|
105
180
|
|
|
106
181
|
//# sourceMappingURL=index.js.map
|