@carto/ps-react-ui 4.3.3 → 4.3.5
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.js +3 -3
- package/dist/components.js.map +1 -1
- package/dist/{lasso-tool-BwRzEW7k.js → lasso-tool-wFqOD6wk.js} +13 -13
- package/dist/lasso-tool-wFqOD6wk.js.map +1 -0
- package/dist/types/components/common-types.d.ts +41 -0
- package/dist/types/components/types.d.ts +1 -1
- package/dist/types/widgets/echart/echart-ui.d.ts +1 -1
- package/dist/types/widgets/echart/types.d.ts +4 -0
- package/dist/widgets/actions.js +1 -1
- package/dist/widgets/bar.js +1 -1
- package/dist/widgets/category.js +1 -1
- package/dist/widgets/echart.js +96 -85
- package/dist/widgets/echart.js.map +1 -1
- package/dist/widgets/formula.js +1 -1
- package/dist/widgets/histogram.js +1 -1
- package/dist/widgets/markdown.js +1 -1
- package/dist/widgets/pie.js +1 -1
- package/dist/widgets/scatterplot.js +1 -1
- package/dist/widgets/spread.js +1 -1
- package/dist/widgets/table.js +1 -1
- package/dist/widgets/timeseries.js +1 -1
- package/dist/widgets/toolbar-actions.js.map +1 -1
- package/dist/widgets/wrapper.js +1 -1
- package/package.json +8 -3
- package/src/components/basemaps/basemaps.test.tsx +196 -0
- package/src/components/basemaps/basemaps.tsx +128 -0
- package/src/components/basemaps/const.ts +13 -0
- package/src/components/basemaps/group-wrapper.test.tsx +38 -0
- package/src/components/basemaps/group-wrapper.tsx +28 -0
- package/src/components/basemaps/group.test.tsx +52 -0
- package/src/components/basemaps/group.tsx +42 -0
- package/src/components/basemaps/header.test.tsx +54 -0
- package/src/components/basemaps/header.tsx +36 -0
- package/src/components/basemaps/styles.ts +76 -0
- package/src/components/basemaps/types.ts +30 -0
- package/src/components/common-types.ts +1 -0
- package/src/components/geolocation-controls/const.ts +6 -0
- package/src/components/geolocation-controls/geolocation-controls.test.tsx +133 -0
- package/src/components/geolocation-controls/geolocation-controls.tsx +95 -0
- package/src/components/geolocation-controls/types.ts +17 -0
- package/src/components/index.ts +64 -0
- package/src/components/lasso-tool/chip.tsx +37 -0
- package/src/components/lasso-tool/const.tsx +70 -0
- package/src/components/lasso-tool/icons.tsx +91 -0
- package/src/components/lasso-tool/lasso-tool-inline.test.tsx +168 -0
- package/src/components/lasso-tool/lasso-tool-inline.tsx +245 -0
- package/src/components/lasso-tool/lasso-tool.test.tsx +212 -0
- package/src/components/lasso-tool/lasso-tool.tsx +479 -0
- package/src/components/lasso-tool/styles.ts +143 -0
- package/src/components/lasso-tool/types.ts +114 -0
- package/src/components/list-data/list-data-skeleton.test.tsx +10 -0
- package/src/components/list-data/list-data-skeleton.tsx +40 -0
- package/src/components/list-data/list-data.test.tsx +94 -0
- package/src/components/list-data/list-data.tsx +106 -0
- package/src/components/list-data/styles.ts +37 -0
- package/src/components/list-data/types.ts +25 -0
- package/src/components/measurement-tools/const.tsx +108 -0
- package/src/components/measurement-tools/icons.tsx +54 -0
- package/src/components/measurement-tools/measurement-tools.test.tsx +165 -0
- package/src/components/measurement-tools/measurement-tools.tsx +443 -0
- package/src/components/measurement-tools/styles.ts +91 -0
- package/src/components/measurement-tools/types.ts +77 -0
- package/src/components/no-data-alert/no-data-alert.test.tsx +31 -0
- package/src/components/no-data-alert/no-data-alert.tsx +59 -0
- package/src/components/responsive-drawer/responsive-drawer.test.tsx +91 -0
- package/src/components/responsive-drawer/responsive-drawer.tsx +53 -0
- package/src/components/smart-tooltip/smart-tooltip.test.tsx +168 -0
- package/src/components/smart-tooltip/smart-tooltip.tsx +40 -0
- package/src/components/tooltip/tooltip.test.tsx +86 -0
- package/src/components/tooltip/tooltip.tsx +30 -0
- package/src/components/types.ts +1 -0
- package/src/components/zoom-controls/styles.ts +27 -0
- package/src/components/zoom-controls/types.ts +19 -0
- package/src/components/zoom-controls/zoom-controls.test.tsx +101 -0
- package/src/components/zoom-controls/zoom-controls.tsx +114 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-debounce.ts +55 -0
- package/src/hooks/use-skeleton.test.tsx +32 -0
- package/src/hooks/use-skeleton.ts +19 -0
- package/src/hooks/use-widget-ref.ts +33 -0
- package/src/widgets/README.md +277 -0
- package/src/widgets/_shared/chart-config/config-factory.ts +67 -0
- package/src/widgets/_shared/chart-config/csv-modifiers.ts +56 -0
- package/src/widgets/_shared/chart-config/index.ts +21 -0
- package/src/widgets/_shared/chart-config/option-builders.ts +203 -0
- package/src/widgets/_shared/skeleton/index.ts +5 -0
- package/src/widgets/_shared/skeleton/styles.ts +20 -0
- package/src/widgets/actions/change-column/change-column-icon.tsx +10 -0
- package/src/widgets/actions/change-column/change-column.test.tsx +163 -0
- package/src/widgets/actions/change-column/change-column.tsx +141 -0
- package/src/widgets/actions/change-column/sortable-column-item.tsx +49 -0
- package/src/widgets/actions/change-column/types.ts +20 -0
- package/src/widgets/actions/download/download.test.tsx +322 -0
- package/src/widgets/actions/download/download.tsx +118 -0
- package/src/widgets/actions/download/exports.test.tsx +275 -0
- package/src/widgets/actions/download/exports.tsx +103 -0
- package/src/widgets/actions/download/types.ts +21 -0
- package/src/widgets/actions/fullscreen/fullscreen.test.tsx +269 -0
- package/src/widgets/actions/fullscreen/fullscreen.tsx +82 -0
- package/src/widgets/actions/fullscreen/styles.ts +17 -0
- package/src/widgets/actions/fullscreen/types.ts +27 -0
- package/src/widgets/actions/index.ts +51 -0
- package/src/widgets/actions/lock-selection/lock-selection.test.tsx +186 -0
- package/src/widgets/actions/lock-selection/lock-selection.tsx +133 -0
- package/src/widgets/actions/lock-selection/types.ts +41 -0
- package/src/widgets/actions/relative-data/relative-data.test.tsx +267 -0
- package/src/widgets/actions/relative-data/relative-data.tsx +133 -0
- package/src/widgets/actions/relative-data/style.ts +9 -0
- package/src/widgets/actions/relative-data/types.ts +31 -0
- package/src/widgets/actions/relative-data/utils.test.ts +223 -0
- package/src/widgets/actions/relative-data/utils.ts +58 -0
- package/src/widgets/actions/searcher/searcher-toggle.test.tsx +354 -0
- package/src/widgets/actions/searcher/searcher-toggle.tsx +73 -0
- package/src/widgets/actions/searcher/searcher.tsx +205 -0
- package/src/widgets/actions/searcher/types.ts +72 -0
- package/src/widgets/actions/shared/styles.ts +12 -0
- package/src/widgets/actions/stack-toggle/grouped-bar-chart-icon.tsx +14 -0
- package/src/widgets/actions/stack-toggle/stack-toggle.test.tsx +270 -0
- package/src/widgets/actions/stack-toggle/stack-toggle.tsx +146 -0
- package/src/widgets/actions/stack-toggle/types.ts +29 -0
- package/src/widgets/actions/zoom-toggle/index.ts +2 -0
- package/src/widgets/actions/zoom-toggle/style.ts +14 -0
- package/src/widgets/actions/zoom-toggle/types.ts +44 -0
- package/src/widgets/actions/zoom-toggle/zoom-toggle.tsx +186 -0
- package/src/widgets/bar/config.ts +122 -0
- package/src/widgets/bar/index.ts +9 -0
- package/src/widgets/bar/skeleton.tsx +60 -0
- package/src/widgets/bar/style.ts +33 -0
- package/src/widgets/bar/types.ts +16 -0
- package/src/widgets/category/category-ui.test.tsx +399 -0
- package/src/widgets/category/category-ui.tsx +156 -0
- package/src/widgets/category/components/category-bar.tsx +28 -0
- package/src/widgets/category/components/category-legend.tsx +30 -0
- package/src/widgets/category/components/category-row-multi.tsx +50 -0
- package/src/widgets/category/components/category-row-other.tsx +23 -0
- package/src/widgets/category/components/category-row-single.tsx +47 -0
- package/src/widgets/category/components/index.ts +14 -0
- package/src/widgets/category/config.ts +85 -0
- package/src/widgets/category/index.ts +30 -0
- package/src/widgets/category/skeleton.tsx +24 -0
- package/src/widgets/category/style.ts +133 -0
- package/src/widgets/category/types.ts +40 -0
- package/src/widgets/echart/const.ts +1 -0
- package/src/widgets/echart/echart-ui.test.tsx +537 -0
- package/src/widgets/echart/echart-ui.tsx +92 -0
- package/src/widgets/echart/echart.test.tsx +562 -0
- package/src/widgets/echart/echart.tsx +68 -0
- package/src/widgets/echart/index.ts +16 -0
- package/src/widgets/echart/options.ts +53 -0
- package/src/widgets/echart/types.ts +45 -0
- package/src/widgets/echart/utils.ts +169 -0
- package/src/widgets/error/error.test.tsx +331 -0
- package/src/widgets/error/error.tsx +40 -0
- package/src/widgets/error/index.ts +2 -0
- package/src/widgets/error/types.ts +14 -0
- package/src/widgets/formula/components/item.test.tsx +249 -0
- package/src/widgets/formula/components/item.tsx +18 -0
- package/src/widgets/formula/components/prefix.test.tsx +341 -0
- package/src/widgets/formula/components/prefix.tsx +18 -0
- package/src/widgets/formula/components/row.test.tsx +364 -0
- package/src/widgets/formula/components/row.tsx +21 -0
- package/src/widgets/formula/components/series.tsx +34 -0
- package/src/widgets/formula/components/suffix.test.tsx +383 -0
- package/src/widgets/formula/components/suffix.tsx +28 -0
- package/src/widgets/formula/components/value.test.tsx +329 -0
- package/src/widgets/formula/components/value.tsx +29 -0
- package/src/widgets/formula/config.ts +27 -0
- package/src/widgets/formula/formula-ui.test.tsx +399 -0
- package/src/widgets/formula/formula-ui.tsx +27 -0
- package/src/widgets/formula/index.ts +24 -0
- package/src/widgets/formula/serializer.test.tsx +144 -0
- package/src/widgets/formula/serializer.ts +28 -0
- package/src/widgets/formula/skeleton.tsx +10 -0
- package/src/widgets/formula/style.ts +23 -0
- package/src/widgets/formula/types.ts +50 -0
- package/src/widgets/histogram/config.ts +143 -0
- package/src/widgets/histogram/index.ts +8 -0
- package/src/widgets/histogram/skeleton.tsx +52 -0
- package/src/widgets/histogram/style.ts +8 -0
- package/src/widgets/histogram/types.ts +17 -0
- package/src/widgets/index.ts +25 -0
- package/src/widgets/loader/index.ts +4 -0
- package/src/widgets/loader/loader.tsx +70 -0
- package/src/widgets/loader/types.ts +11 -0
- package/src/widgets/loader/utils.test.ts +112 -0
- package/src/widgets/loader/utils.ts +35 -0
- package/src/widgets/markdown/config.ts +18 -0
- package/src/widgets/markdown/index.ts +14 -0
- package/src/widgets/markdown/markdown-ui.test.tsx +341 -0
- package/src/widgets/markdown/markdown-ui.tsx +52 -0
- package/src/widgets/markdown/markdown.tsx +20 -0
- package/src/widgets/markdown/skeleton.tsx +12 -0
- package/src/widgets/markdown/style.ts +28 -0
- package/src/widgets/markdown/types.ts +28 -0
- package/src/widgets/no-data/index.ts +2 -0
- package/src/widgets/no-data/no-data.test.tsx +447 -0
- package/src/widgets/no-data/no-data.tsx +116 -0
- package/src/widgets/no-data/style.ts +18 -0
- package/src/widgets/no-data/types.ts +72 -0
- package/src/widgets/note/index.ts +2 -0
- package/src/widgets/note/note.test.tsx +391 -0
- package/src/widgets/note/note.tsx +114 -0
- package/src/widgets/note/style.ts +29 -0
- package/src/widgets/note/types.ts +9 -0
- package/src/widgets/pie/config.ts +177 -0
- package/src/widgets/pie/index.ts +8 -0
- package/src/widgets/pie/skeleton.tsx +70 -0
- package/src/widgets/pie/style.ts +8 -0
- package/src/widgets/pie/types.ts +16 -0
- package/src/widgets/range/components/range-item.tsx +213 -0
- package/src/widgets/range/config.ts +10 -0
- package/src/widgets/range/index.ts +16 -0
- package/src/widgets/range/range-ui.test.tsx +203 -0
- package/src/widgets/range/range-ui.tsx +11 -0
- package/src/widgets/range/serializer.test.ts +70 -0
- package/src/widgets/range/serializer.ts +27 -0
- package/src/widgets/range/skeleton.tsx +14 -0
- package/src/widgets/range/style.ts +37 -0
- package/src/widgets/range/types.ts +39 -0
- package/src/widgets/scatterplot/config.ts +138 -0
- package/src/widgets/scatterplot/index.ts +8 -0
- package/src/widgets/scatterplot/skeleton.tsx +59 -0
- package/src/widgets/scatterplot/style.ts +21 -0
- package/src/widgets/scatterplot/types.ts +17 -0
- package/src/widgets/selection-summary/index.ts +6 -0
- package/src/widgets/selection-summary/selection-summary.tsx +46 -0
- package/src/widgets/selection-summary/style.ts +10 -0
- package/src/widgets/selection-summary/types.ts +14 -0
- package/src/widgets/skeleton-loader/index.ts +2 -0
- package/src/widgets/skeleton-loader/skeleton-loader.test.tsx +139 -0
- package/src/widgets/skeleton-loader/skeleton-loader.tsx +28 -0
- package/src/widgets/skeleton-loader/types.ts +8 -0
- package/src/widgets/spread/components/max-value.tsx +29 -0
- package/src/widgets/spread/components/min-value.tsx +29 -0
- package/src/widgets/spread/components/separator.tsx +6 -0
- package/src/widgets/spread/config.ts +34 -0
- package/src/widgets/spread/index.ts +23 -0
- package/src/widgets/spread/skeleton.tsx +10 -0
- package/src/widgets/spread/spread-ui.test.tsx +368 -0
- package/src/widgets/spread/spread-ui.tsx +29 -0
- package/src/widgets/spread/style.ts +22 -0
- package/src/widgets/spread/types.ts +47 -0
- package/src/widgets/stores/index.ts +9 -0
- package/src/widgets/stores/types.ts +192 -0
- package/src/widgets/stores/widget-store.test.ts +601 -0
- package/src/widgets/stores/widget-store.ts +239 -0
- package/src/widgets/subheader/index.ts +3 -0
- package/src/widgets/subheader/style.ts +20 -0
- package/src/widgets/subheader/subheader.test.tsx +45 -0
- package/src/widgets/subheader/subheader.tsx +16 -0
- package/src/widgets/subheader/types.ts +11 -0
- package/src/widgets/table/components/cell-header.tsx +58 -0
- package/src/widgets/table/components/cell.tsx +80 -0
- package/src/widgets/table/components/index.ts +4 -0
- package/src/widgets/table/components/pagination-actions.tsx +67 -0
- package/src/widgets/table/components/pagination.tsx +41 -0
- package/src/widgets/table/components/row.tsx +60 -0
- package/src/widgets/table/config.ts +71 -0
- package/src/widgets/table/helpers.test.ts +244 -0
- package/src/widgets/table/helpers.ts +107 -0
- package/src/widgets/table/hooks/index.ts +7 -0
- package/src/widgets/table/hooks/use-pagination.test.ts +294 -0
- package/src/widgets/table/hooks/use-pagination.ts +155 -0
- package/src/widgets/table/hooks/use-selection.test.ts +504 -0
- package/src/widgets/table/hooks/use-selection.ts +189 -0
- package/src/widgets/table/hooks/use-sort.test.ts +296 -0
- package/src/widgets/table/hooks/use-sort.ts +138 -0
- package/src/widgets/table/index.ts +53 -0
- package/src/widgets/table/serializer.ts +54 -0
- package/src/widgets/table/skeleton.tsx +48 -0
- package/src/widgets/table/style.ts +34 -0
- package/src/widgets/table/table-ui.tsx +64 -0
- package/src/widgets/table/types.ts +223 -0
- package/src/widgets/timeseries/config.ts +135 -0
- package/src/widgets/timeseries/index.ts +8 -0
- package/src/widgets/timeseries/skeleton.tsx +55 -0
- package/src/widgets/timeseries/style.ts +36 -0
- package/src/widgets/timeseries/types.ts +17 -0
- package/src/widgets/toolbar-actions/index.ts +6 -0
- package/src/widgets/toolbar-actions/styles.ts +38 -0
- package/src/widgets/toolbar-actions/toolbar-actions.test.tsx +691 -0
- package/src/widgets/toolbar-actions/toolbar-actions.tsx +145 -0
- package/src/widgets/toolbar-actions/types.ts +60 -0
- package/src/widgets/wrapper/components/actions.test.tsx +101 -0
- package/src/widgets/wrapper/components/actions.tsx +30 -0
- package/src/widgets/wrapper/components/options.test.tsx +323 -0
- package/src/widgets/wrapper/components/options.tsx +73 -0
- package/src/widgets/wrapper/components/title.test.tsx +126 -0
- package/src/widgets/wrapper/components/title.tsx +32 -0
- package/src/widgets/wrapper/index.ts +16 -0
- package/src/widgets/wrapper/styles.ts +98 -0
- package/src/widgets/wrapper/types.ts +55 -0
- package/src/widgets/wrapper/wrapper-ui.test.tsx +232 -0
- package/src/widgets/wrapper/wrapper-ui.tsx +57 -0
- package/src/widgets/wrapper/wrapper.test.tsx +365 -0
- package/src/widgets/wrapper/wrapper.tsx +50 -0
- package/dist/lasso-tool-BwRzEW7k.js.map +0 -1
- package/dist/types/common/common.d.ts +0 -3
- package/dist/types/common/index.d.ts +0 -26
- package/dist/types/common/lasso-tools.d.ts +0 -36
- package/dist/types/common/measurement-tools.d.ts +0 -65
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { CloseOutlined } from '@mui/icons-material'
|
|
2
|
+
import { Box, Divider, Grow, IconButton, Paper } from '@mui/material'
|
|
3
|
+
import { isValidElement, useState, type JSX, type MouseEvent } from 'react'
|
|
4
|
+
import { Tooltip } from '../../components/tooltip/tooltip'
|
|
5
|
+
import { styles } from './styles'
|
|
6
|
+
import type { ToolbarActionsProps } from './types'
|
|
7
|
+
import { WidgetOptions } from '@carto/meridian-ds/custom-icons'
|
|
8
|
+
|
|
9
|
+
const DEFAULT_LABELS = {
|
|
10
|
+
trigger: 'More actions',
|
|
11
|
+
close: 'Close',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A floating toolbar component that displays action buttons.
|
|
16
|
+
* Shows a trigger button when there are more visible actions than the visibleCount,
|
|
17
|
+
* which expands to reveal all actions with a close button.
|
|
18
|
+
* When visibleCount is undefined, all actions are shown without overflow.
|
|
19
|
+
*
|
|
20
|
+
* Children with the `data-toolbar-hidden` attribute are ignored when counting
|
|
21
|
+
* visible actions but are included in the preview. This is useful for elements
|
|
22
|
+
* like Dividers that should appear between actions but not count toward the limit.
|
|
23
|
+
*
|
|
24
|
+
* Example: With visibleCount=2 and children [Action1, Divider, Action2, Divider, Action3]:
|
|
25
|
+
* - Preview shows: Action1, Divider, Action2 (2 visible actions + divider)
|
|
26
|
+
* - Hidden: Divider, Action3
|
|
27
|
+
*/
|
|
28
|
+
export function ToolbarActions({
|
|
29
|
+
children: _children,
|
|
30
|
+
visibleCount,
|
|
31
|
+
direction,
|
|
32
|
+
labels,
|
|
33
|
+
sx,
|
|
34
|
+
IconButtonProps,
|
|
35
|
+
TooltipProps,
|
|
36
|
+
}: ToolbarActionsProps): JSX.Element | null {
|
|
37
|
+
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
|
|
38
|
+
|
|
39
|
+
const triggerLabel = labels?.trigger ?? DEFAULT_LABELS.trigger
|
|
40
|
+
const closeLabel = labels?.close ?? DEFAULT_LABELS.close
|
|
41
|
+
|
|
42
|
+
const children = (Array.isArray(_children) ? _children : [_children]).filter(
|
|
43
|
+
Boolean,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
// Don't render if no actions
|
|
47
|
+
if (children.length === 0) {
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Filter out children with data-toolbar-hidden attribute for counting
|
|
52
|
+
const visibleChildren = children.filter(
|
|
53
|
+
(child) =>
|
|
54
|
+
!(
|
|
55
|
+
isValidElement<{ 'data-toolbar-hidden'?: boolean }>(child) &&
|
|
56
|
+
child.props['data-toolbar-hidden']
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
// Show trigger only when visibleCount is defined and there are more visible actions
|
|
61
|
+
const showTrigger =
|
|
62
|
+
visibleCount !== undefined && visibleChildren.length > visibleCount
|
|
63
|
+
|
|
64
|
+
const handleToggle = (e: MouseEvent<HTMLElement>) => {
|
|
65
|
+
setAnchorEl((prev) => (prev ? null : e.currentTarget))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Build preview by including hidden elements but only counting visible ones
|
|
69
|
+
const childrenPreview =
|
|
70
|
+
visibleCount === undefined
|
|
71
|
+
? children
|
|
72
|
+
: children.reduce<{ result: React.ReactNode[]; count: number }>(
|
|
73
|
+
(acc, child) => {
|
|
74
|
+
// Stop if we've already collected enough visible actions
|
|
75
|
+
if (acc.count >= visibleCount) {
|
|
76
|
+
return acc
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const isHidden =
|
|
80
|
+
isValidElement<{ 'data-toolbar-hidden'?: boolean }>(child) &&
|
|
81
|
+
child.props['data-toolbar-hidden']
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
result: [...acc.result, child],
|
|
85
|
+
count: acc.count + (isHidden ? 0 : 1),
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{ result: [], count: 0 },
|
|
89
|
+
).result
|
|
90
|
+
|
|
91
|
+
const width = anchorEl?.getBoundingClientRect().width
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Box
|
|
95
|
+
sx={{
|
|
96
|
+
...styles.root,
|
|
97
|
+
...sx,
|
|
98
|
+
flexDirection: direction === 'left' ? 'row-reverse' : 'row',
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
{/* Trigger button - only shown when 3+ actions */}
|
|
102
|
+
{showTrigger && (
|
|
103
|
+
<>
|
|
104
|
+
<Tooltip
|
|
105
|
+
title={anchorEl ? closeLabel : triggerLabel}
|
|
106
|
+
placement='top'
|
|
107
|
+
{...TooltipProps}
|
|
108
|
+
>
|
|
109
|
+
<IconButton
|
|
110
|
+
size='small'
|
|
111
|
+
aria-label={anchorEl ? closeLabel : triggerLabel}
|
|
112
|
+
onClick={handleToggle}
|
|
113
|
+
sx={styles.triggerButton}
|
|
114
|
+
data-active={!!anchorEl}
|
|
115
|
+
{...IconButtonProps}
|
|
116
|
+
>
|
|
117
|
+
{anchorEl ? <CloseOutlined /> : <WidgetOptions />}
|
|
118
|
+
</IconButton>
|
|
119
|
+
</Tooltip>
|
|
120
|
+
<Divider orientation='vertical' flexItem />
|
|
121
|
+
</>
|
|
122
|
+
)}
|
|
123
|
+
|
|
124
|
+
<Box sx={styles.preview}>{childrenPreview}</Box>
|
|
125
|
+
|
|
126
|
+
{/* Actions container - shown only when expanded (trigger is active) */}
|
|
127
|
+
<Grow in={!!anchorEl}>
|
|
128
|
+
<Paper
|
|
129
|
+
sx={{
|
|
130
|
+
...styles.actionsContainer,
|
|
131
|
+
transform: `translate3d(${
|
|
132
|
+
direction === 'left' ? `-${width ?? 0}px` : `${width ?? 0}px`
|
|
133
|
+
}, 0, 0) !important`,
|
|
134
|
+
marginLeft: ({ spacing }) =>
|
|
135
|
+
direction === 'left' ? 0 : spacing(0.25),
|
|
136
|
+
marginRight: ({ spacing }) =>
|
|
137
|
+
direction === 'left' ? spacing(0.25) : 0,
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
{children}
|
|
141
|
+
</Paper>
|
|
142
|
+
</Grow>
|
|
143
|
+
</Box>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IconButtonProps,
|
|
3
|
+
SxProps,
|
|
4
|
+
Theme,
|
|
5
|
+
TooltipProps,
|
|
6
|
+
} from '@mui/material'
|
|
7
|
+
import type { ReactNode } from 'react'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Represents a single action in the toolbar
|
|
11
|
+
*/
|
|
12
|
+
export interface ToolbarAction {
|
|
13
|
+
/** Unique identifier for the action */
|
|
14
|
+
id: string
|
|
15
|
+
/** Icon to display for the action */
|
|
16
|
+
icon: ReactNode
|
|
17
|
+
/** Label displayed in tooltip */
|
|
18
|
+
label: string
|
|
19
|
+
/** Callback when action is clicked */
|
|
20
|
+
onClick: () => void
|
|
21
|
+
/** Whether the action is disabled */
|
|
22
|
+
disabled?: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Labels for toolbar UI elements
|
|
27
|
+
*/
|
|
28
|
+
export interface ToolbarActionsLabels {
|
|
29
|
+
/** Tooltip for the trigger button */
|
|
30
|
+
trigger?: string
|
|
31
|
+
/** Tooltip for the close button */
|
|
32
|
+
close?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Props for the ToolbarActions component
|
|
37
|
+
*/
|
|
38
|
+
export interface ToolbarActionsProps {
|
|
39
|
+
/**
|
|
40
|
+
* Array of React elements (typically IconButtons) to display in the toolbar.
|
|
41
|
+
* Elements with `data-toolbar-hidden` attribute are ignored when counting visible
|
|
42
|
+
* actions and excluded from the collapsed preview (e.g., Dividers).
|
|
43
|
+
*/
|
|
44
|
+
children: ReactNode[] | ReactNode
|
|
45
|
+
/** Direction in which the toolbar expands */
|
|
46
|
+
direction?: 'left' | 'right'
|
|
47
|
+
/**
|
|
48
|
+
* Number of visible actions to show in collapsed mode.
|
|
49
|
+
* When undefined, shows all actions without overflow.
|
|
50
|
+
*/
|
|
51
|
+
visibleCount?: number
|
|
52
|
+
/** Custom labels for UI elements */
|
|
53
|
+
labels?: ToolbarActionsLabels
|
|
54
|
+
/** Custom styles for the root container */
|
|
55
|
+
sx?: SxProps<Theme>
|
|
56
|
+
/** Props passed to action IconButtons */
|
|
57
|
+
IconButtonProps?: Omit<IconButtonProps, 'onClick' | 'disabled'>
|
|
58
|
+
/** Props passed to Tooltips */
|
|
59
|
+
TooltipProps?: Omit<TooltipProps, 'title' | 'children'>
|
|
60
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, test, expect, vi } from 'vitest'
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import { Actions } from './actions'
|
|
4
|
+
|
|
5
|
+
describe('Actions', () => {
|
|
6
|
+
test('renders with empty actions array', () => {
|
|
7
|
+
const { container } = render(<Actions actions={[]} />)
|
|
8
|
+
expect(container.firstChild).toBeTruthy()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test('renders with undefined actions', () => {
|
|
12
|
+
const { container } = render(<Actions />)
|
|
13
|
+
expect(container.firstChild).toBeTruthy()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('renders single action', () => {
|
|
17
|
+
const actions = [<button key='action1'>Action 1</button>]
|
|
18
|
+
render(<Actions actions={actions} />)
|
|
19
|
+
expect(screen.getByText('Action 1')).toBeTruthy()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('renders multiple actions', () => {
|
|
23
|
+
const actions = [
|
|
24
|
+
<button key='action1'>Action 1</button>,
|
|
25
|
+
<button key='action2'>Action 2</button>,
|
|
26
|
+
<button key='action3'>Action 3</button>,
|
|
27
|
+
]
|
|
28
|
+
render(<Actions actions={actions} />)
|
|
29
|
+
expect(screen.getByText('Action 1')).toBeTruthy()
|
|
30
|
+
expect(screen.getByText('Action 2')).toBeTruthy()
|
|
31
|
+
expect(screen.getByText('Action 3')).toBeTruthy()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('stops event propagation on click', () => {
|
|
35
|
+
const onClick = vi.fn()
|
|
36
|
+
const parentOnClick = vi.fn()
|
|
37
|
+
|
|
38
|
+
const actions = [
|
|
39
|
+
<button key='action1' onClick={onClick}>
|
|
40
|
+
Action 1
|
|
41
|
+
</button>,
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
render(
|
|
45
|
+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
|
46
|
+
<div onClick={parentOnClick}>
|
|
47
|
+
<Actions actions={actions} />
|
|
48
|
+
</div>,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const button = screen.getByText('Action 1')
|
|
52
|
+
fireEvent.click(button)
|
|
53
|
+
|
|
54
|
+
// The action's onClick should fire
|
|
55
|
+
expect(onClick).toHaveBeenCalledTimes(1)
|
|
56
|
+
// But parent onClick should not fire due to stopPropagation
|
|
57
|
+
expect(parentOnClick).not.toHaveBeenCalled()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('renders actions with different types of elements', () => {
|
|
61
|
+
const actions = [
|
|
62
|
+
<button key='button'>Button</button>,
|
|
63
|
+
<button key='link'>Link</button>,
|
|
64
|
+
<span key='span'>Span</span>,
|
|
65
|
+
]
|
|
66
|
+
render(<Actions actions={actions} />)
|
|
67
|
+
expect(screen.getByText('Button')).toBeTruthy()
|
|
68
|
+
expect(screen.getByText('Link')).toBeTruthy()
|
|
69
|
+
expect(screen.getByText('Span')).toBeTruthy()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('wraps each action in a Box with correct key', () => {
|
|
73
|
+
const actions = [
|
|
74
|
+
<button key='action1'>Action 1</button>,
|
|
75
|
+
<button key='action2'>Action 2</button>,
|
|
76
|
+
]
|
|
77
|
+
const { container } = render(<Actions actions={actions} />)
|
|
78
|
+
|
|
79
|
+
// Check that actions are rendered
|
|
80
|
+
expect(screen.getByText('Action 1')).toBeTruthy()
|
|
81
|
+
expect(screen.getByText('Action 2')).toBeTruthy()
|
|
82
|
+
|
|
83
|
+
// The container should have the actions box
|
|
84
|
+
expect(container.firstChild).toBeTruthy()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('handles complex action components', () => {
|
|
88
|
+
const ComplexAction = () => (
|
|
89
|
+
<div>
|
|
90
|
+
<span>Complex</span>
|
|
91
|
+
<button>Action</button>
|
|
92
|
+
</div>
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
const actions = [<ComplexAction key='complex' />]
|
|
96
|
+
render(<Actions actions={actions} />)
|
|
97
|
+
|
|
98
|
+
expect(screen.getByText('Complex')).toBeTruthy()
|
|
99
|
+
expect(screen.getByText('Action')).toBeTruthy()
|
|
100
|
+
})
|
|
101
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Box } from '@mui/material'
|
|
2
|
+
import type { WrapperActionsProps } from '../types'
|
|
3
|
+
import { styles } from '../styles'
|
|
4
|
+
|
|
5
|
+
export function Actions({ actions = [] }: WrapperActionsProps) {
|
|
6
|
+
return (
|
|
7
|
+
<Box sx={styles.actions} className='widget-wrapper-actions'>
|
|
8
|
+
{actions.map((action, index) => {
|
|
9
|
+
// Prefer action.key if present (React elements), else fallback to index
|
|
10
|
+
const key =
|
|
11
|
+
action &&
|
|
12
|
+
typeof action === 'object' &&
|
|
13
|
+
'key' in action &&
|
|
14
|
+
action.key != null
|
|
15
|
+
? action.key
|
|
16
|
+
: index
|
|
17
|
+
return (
|
|
18
|
+
<Box
|
|
19
|
+
key={key}
|
|
20
|
+
onClick={(e) => {
|
|
21
|
+
e.stopPropagation()
|
|
22
|
+
}}
|
|
23
|
+
>
|
|
24
|
+
{action}
|
|
25
|
+
</Box>
|
|
26
|
+
)
|
|
27
|
+
})}
|
|
28
|
+
</Box>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { describe, test, expect, vi } from 'vitest'
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import { Options } from './options'
|
|
4
|
+
|
|
5
|
+
describe('Options', () => {
|
|
6
|
+
test('renders options button with default label', () => {
|
|
7
|
+
const options = [
|
|
8
|
+
{
|
|
9
|
+
label: 'Option 1',
|
|
10
|
+
onClick: vi.fn(),
|
|
11
|
+
},
|
|
12
|
+
]
|
|
13
|
+
render(<Options options={options} />)
|
|
14
|
+
expect(screen.getByLabelText('Options')).toBeTruthy()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('renders options button with custom label', () => {
|
|
18
|
+
const options = [
|
|
19
|
+
{
|
|
20
|
+
label: 'Option 1',
|
|
21
|
+
onClick: vi.fn(),
|
|
22
|
+
},
|
|
23
|
+
]
|
|
24
|
+
const labels = {
|
|
25
|
+
title: 'Custom Options Label',
|
|
26
|
+
}
|
|
27
|
+
render(<Options options={options} labels={labels} />)
|
|
28
|
+
expect(screen.getByLabelText('Custom Options Label')).toBeTruthy()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('renders when options array is empty', () => {
|
|
32
|
+
render(<Options options={[]} />)
|
|
33
|
+
// Component still renders button even with empty options
|
|
34
|
+
// (In practice, WrapperUI doesn't render Options when options.length === 0)
|
|
35
|
+
expect(screen.getByLabelText('Options')).toBeTruthy()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('renders when options is undefined', () => {
|
|
39
|
+
render(<Options />)
|
|
40
|
+
// Component still renders button even with undefined options
|
|
41
|
+
// (In practice, WrapperUI doesn't render Options when options.length === 0)
|
|
42
|
+
expect(screen.getByLabelText('Options')).toBeTruthy()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('opens menu when button is clicked', () => {
|
|
46
|
+
const options = [
|
|
47
|
+
{
|
|
48
|
+
label: 'Option 1',
|
|
49
|
+
onClick: vi.fn(),
|
|
50
|
+
},
|
|
51
|
+
]
|
|
52
|
+
render(<Options options={options} />)
|
|
53
|
+
|
|
54
|
+
const button = screen.getByLabelText('Options')
|
|
55
|
+
fireEvent.click(button)
|
|
56
|
+
|
|
57
|
+
expect(screen.getByText('Option 1')).toBeTruthy()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('closes menu when option is clicked', () => {
|
|
61
|
+
const onClick = vi.fn()
|
|
62
|
+
const options = [
|
|
63
|
+
{
|
|
64
|
+
label: 'Option 1',
|
|
65
|
+
onClick,
|
|
66
|
+
},
|
|
67
|
+
]
|
|
68
|
+
render(<Options options={options} />)
|
|
69
|
+
|
|
70
|
+
const button = screen.getByLabelText('Options')
|
|
71
|
+
fireEvent.click(button)
|
|
72
|
+
|
|
73
|
+
const menuItem = screen.getByText('Option 1')
|
|
74
|
+
fireEvent.click(menuItem)
|
|
75
|
+
|
|
76
|
+
expect(onClick).toHaveBeenCalledTimes(1)
|
|
77
|
+
// Menu should be closed after clicking
|
|
78
|
+
expect(screen.queryByRole('menu')).toBeNull()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('stops event propagation when opening menu', () => {
|
|
82
|
+
const parentOnClick = vi.fn()
|
|
83
|
+
const options = [
|
|
84
|
+
{
|
|
85
|
+
label: 'Option 1',
|
|
86
|
+
onClick: vi.fn(),
|
|
87
|
+
},
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
render(
|
|
91
|
+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
|
92
|
+
<div onClick={parentOnClick}>
|
|
93
|
+
<Options options={options} />
|
|
94
|
+
</div>,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
const button = screen.getByLabelText('Options')
|
|
98
|
+
fireEvent.click(button)
|
|
99
|
+
|
|
100
|
+
// Parent onClick should not be called
|
|
101
|
+
expect(parentOnClick).not.toHaveBeenCalled()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('stops event propagation when clicking option', () => {
|
|
105
|
+
const parentOnClick = vi.fn()
|
|
106
|
+
const onClick = vi.fn()
|
|
107
|
+
const options = [
|
|
108
|
+
{
|
|
109
|
+
label: 'Option 1',
|
|
110
|
+
onClick,
|
|
111
|
+
},
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
render(
|
|
115
|
+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
|
116
|
+
<div onClick={parentOnClick}>
|
|
117
|
+
<Options options={options} />
|
|
118
|
+
</div>,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
const button = screen.getByLabelText('Options')
|
|
122
|
+
fireEvent.click(button)
|
|
123
|
+
|
|
124
|
+
const menuItem = screen.getByText('Option 1')
|
|
125
|
+
fireEvent.click(menuItem)
|
|
126
|
+
|
|
127
|
+
expect(onClick).toHaveBeenCalledTimes(1)
|
|
128
|
+
expect(parentOnClick).not.toHaveBeenCalled()
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('renders multiple options', () => {
|
|
132
|
+
const options = [
|
|
133
|
+
{
|
|
134
|
+
label: 'Option 1',
|
|
135
|
+
onClick: vi.fn(),
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
label: 'Option 2',
|
|
139
|
+
onClick: vi.fn(),
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
label: 'Option 3',
|
|
143
|
+
onClick: vi.fn(),
|
|
144
|
+
},
|
|
145
|
+
]
|
|
146
|
+
render(<Options options={options} />)
|
|
147
|
+
|
|
148
|
+
const button = screen.getByLabelText('Options')
|
|
149
|
+
fireEvent.click(button)
|
|
150
|
+
|
|
151
|
+
expect(screen.getByText('Option 1')).toBeTruthy()
|
|
152
|
+
expect(screen.getByText('Option 2')).toBeTruthy()
|
|
153
|
+
expect(screen.getByText('Option 3')).toBeTruthy()
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('renders disabled option', () => {
|
|
157
|
+
const options = [
|
|
158
|
+
{
|
|
159
|
+
label: 'Disabled Option',
|
|
160
|
+
disabled: true,
|
|
161
|
+
onClick: vi.fn(),
|
|
162
|
+
},
|
|
163
|
+
]
|
|
164
|
+
render(<Options options={options} />)
|
|
165
|
+
|
|
166
|
+
const button = screen.getByLabelText('Options')
|
|
167
|
+
fireEvent.click(button)
|
|
168
|
+
|
|
169
|
+
const menuItem = screen.getByText('Disabled Option').closest('li')
|
|
170
|
+
expect(menuItem?.classList.contains('Mui-disabled')).toBe(true)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test('does not call onClick for disabled option', () => {
|
|
174
|
+
const onClick = vi.fn()
|
|
175
|
+
const options = [
|
|
176
|
+
{
|
|
177
|
+
label: 'Enabled Option',
|
|
178
|
+
onClick,
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
label: 'Disabled Option',
|
|
182
|
+
disabled: true,
|
|
183
|
+
onClick,
|
|
184
|
+
},
|
|
185
|
+
]
|
|
186
|
+
render(<Options options={options} />)
|
|
187
|
+
|
|
188
|
+
const button = screen.getByLabelText('Options')
|
|
189
|
+
fireEvent.click(button)
|
|
190
|
+
|
|
191
|
+
// Click on enabled option first
|
|
192
|
+
const enabled = screen.getByText('Enabled Option')
|
|
193
|
+
fireEvent.click(enabled)
|
|
194
|
+
|
|
195
|
+
// onClick should be called once for enabled
|
|
196
|
+
expect(onClick).toHaveBeenCalledTimes(1)
|
|
197
|
+
|
|
198
|
+
// Open menu again and try clicking disabled
|
|
199
|
+
fireEvent.click(button)
|
|
200
|
+
const disabled = screen.getByText('Disabled Option').closest('li')
|
|
201
|
+
|
|
202
|
+
// Disabled should have the class
|
|
203
|
+
expect(disabled?.classList.contains('Mui-disabled')).toBe(true)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('renders option with icon', () => {
|
|
207
|
+
const icon = <span data-testid='test-icon'>Icon</span>
|
|
208
|
+
const options = [
|
|
209
|
+
{
|
|
210
|
+
label: 'Option with Icon',
|
|
211
|
+
icon,
|
|
212
|
+
onClick: vi.fn(),
|
|
213
|
+
},
|
|
214
|
+
]
|
|
215
|
+
render(<Options options={options} />)
|
|
216
|
+
|
|
217
|
+
const button = screen.getByLabelText('Options')
|
|
218
|
+
fireEvent.click(button)
|
|
219
|
+
|
|
220
|
+
expect(screen.getByTestId('test-icon')).toBeTruthy()
|
|
221
|
+
expect(screen.getByText('Option with Icon')).toBeTruthy()
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
test('renders option without icon', () => {
|
|
225
|
+
const options = [
|
|
226
|
+
{
|
|
227
|
+
label: 'Option without Icon',
|
|
228
|
+
onClick: vi.fn(),
|
|
229
|
+
},
|
|
230
|
+
]
|
|
231
|
+
render(<Options options={options} />)
|
|
232
|
+
|
|
233
|
+
const button = screen.getByLabelText('Options')
|
|
234
|
+
fireEvent.click(button)
|
|
235
|
+
|
|
236
|
+
expect(screen.getByText('Option without Icon')).toBeTruthy()
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
test('closes menu when clicking outside', () => {
|
|
240
|
+
const options = [
|
|
241
|
+
{
|
|
242
|
+
label: 'Option 1',
|
|
243
|
+
onClick: vi.fn(),
|
|
244
|
+
},
|
|
245
|
+
]
|
|
246
|
+
render(<Options options={options} />)
|
|
247
|
+
|
|
248
|
+
const button = screen.getByLabelText('Options')
|
|
249
|
+
fireEvent.click(button)
|
|
250
|
+
|
|
251
|
+
// Menu should be open
|
|
252
|
+
expect(screen.getByText('Option 1')).toBeTruthy()
|
|
253
|
+
|
|
254
|
+
// Click outside (on the backdrop)
|
|
255
|
+
const backdrop = document.querySelector('.MuiBackdrop-root')
|
|
256
|
+
if (backdrop) {
|
|
257
|
+
fireEvent.click(backdrop)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Menu should be closed
|
|
261
|
+
expect(screen.queryByRole('menu')).toBeNull()
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
test('handles multiple option clicks correctly', () => {
|
|
265
|
+
const onClick1 = vi.fn()
|
|
266
|
+
const onClick2 = vi.fn()
|
|
267
|
+
const options = [
|
|
268
|
+
{
|
|
269
|
+
label: 'Option 1',
|
|
270
|
+
onClick: onClick1,
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
label: 'Option 2',
|
|
274
|
+
onClick: onClick2,
|
|
275
|
+
},
|
|
276
|
+
]
|
|
277
|
+
render(<Options options={options} />)
|
|
278
|
+
|
|
279
|
+
// Open menu and click first option
|
|
280
|
+
const button = screen.getByLabelText('Options')
|
|
281
|
+
fireEvent.click(button)
|
|
282
|
+
fireEvent.click(screen.getByText('Option 1'))
|
|
283
|
+
|
|
284
|
+
expect(onClick1).toHaveBeenCalledTimes(1)
|
|
285
|
+
expect(onClick2).not.toHaveBeenCalled()
|
|
286
|
+
|
|
287
|
+
// Open menu again and click second option
|
|
288
|
+
fireEvent.click(button)
|
|
289
|
+
fireEvent.click(screen.getByText('Option 2'))
|
|
290
|
+
|
|
291
|
+
expect(onClick1).toHaveBeenCalledTimes(1)
|
|
292
|
+
expect(onClick2).toHaveBeenCalledTimes(1)
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
test('renders MoreVert icon', () => {
|
|
296
|
+
const options = [
|
|
297
|
+
{
|
|
298
|
+
label: 'Option 1',
|
|
299
|
+
onClick: vi.fn(),
|
|
300
|
+
},
|
|
301
|
+
]
|
|
302
|
+
render(<Options options={options} />)
|
|
303
|
+
|
|
304
|
+
// Check that the button is rendered
|
|
305
|
+
const button = screen.getByLabelText('Options')
|
|
306
|
+
expect(button).toBeTruthy()
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
test('menu has correct attributes', () => {
|
|
310
|
+
const options = [
|
|
311
|
+
{
|
|
312
|
+
label: 'Option 1',
|
|
313
|
+
onClick: vi.fn(),
|
|
314
|
+
},
|
|
315
|
+
]
|
|
316
|
+
render(<Options options={options} />)
|
|
317
|
+
|
|
318
|
+
const button = screen.getByLabelText('Options')
|
|
319
|
+
expect(button.getAttribute('aria-label')).toBe('Options')
|
|
320
|
+
expect(button.getAttribute('aria-controls')).toBe('options-menu')
|
|
321
|
+
expect(button.getAttribute('aria-haspopup')).toBe('true')
|
|
322
|
+
})
|
|
323
|
+
})
|