@bitrise/bitkit-v2 0.3.481-beta.3105 → 0.3.481-beta.3107

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/dist/components/BitkitCalendar/BitkitCalendar.d.ts +12 -0
  2. package/dist/components/BitkitCalendar/BitkitCalendar.js +7 -1
  3. package/dist/components/BitkitCalendar/BitkitCalendar.js.map +1 -1
  4. package/dist/components/BitkitCalendar/components/CalendarContent.d.ts +5 -1
  5. package/dist/components/BitkitCalendar/components/CalendarContent.js +60 -43
  6. package/dist/components/BitkitCalendar/components/CalendarContent.js.map +1 -1
  7. package/dist/components/BitkitCalendar/components/CalendarMonth.d.ts +4 -2
  8. package/dist/components/BitkitCalendar/components/CalendarMonth.js +15 -5
  9. package/dist/components/BitkitCalendar/components/CalendarMonth.js.map +1 -1
  10. package/dist/components/BitkitCalendar/components/CalendarMonthSelect.d.ts +2 -1
  11. package/dist/components/BitkitCalendar/components/CalendarMonthSelect.js +3 -2
  12. package/dist/components/BitkitCalendar/components/CalendarMonthSelect.js.map +1 -1
  13. package/dist/components/BitkitCalendar/components/CalendarNextTrigger.d.ts +3 -1
  14. package/dist/components/BitkitCalendar/components/CalendarNextTrigger.js +3 -3
  15. package/dist/components/BitkitCalendar/components/CalendarNextTrigger.js.map +1 -1
  16. package/dist/components/BitkitCalendar/components/CalendarPrevTrigger.d.ts +3 -1
  17. package/dist/components/BitkitCalendar/components/CalendarPrevTrigger.js +3 -3
  18. package/dist/components/BitkitCalendar/components/CalendarPrevTrigger.js.map +1 -1
  19. package/dist/components/BitkitCalendar/components/CalendarSelect.d.ts +1 -0
  20. package/dist/components/BitkitCalendar/components/CalendarSelect.js +5 -3
  21. package/dist/components/BitkitCalendar/components/CalendarSelect.js.map +1 -1
  22. package/dist/components/BitkitCalendar/components/CalendarTrigger.js +1 -0
  23. package/dist/components/BitkitCalendar/components/CalendarTrigger.js.map +1 -1
  24. package/dist/components/BitkitCalendar/components/CalendarYearSelect.d.ts +2 -1
  25. package/dist/components/BitkitCalendar/components/CalendarYearSelect.js +3 -2
  26. package/dist/components/BitkitCalendar/components/CalendarYearSelect.js.map +1 -1
  27. package/dist/components/BitkitDatePicker/BitkitDatePicker.d.ts +2 -0
  28. package/dist/components/BitkitDatePicker/BitkitDatePicker.js +2 -1
  29. package/dist/components/BitkitDatePicker/BitkitDatePicker.js.map +1 -1
  30. package/dist/components/BitkitFilter/BitkitFilter.context.d.ts +65 -0
  31. package/dist/components/BitkitFilter/BitkitFilter.context.js +82 -0
  32. package/dist/components/BitkitFilter/BitkitFilter.context.js.map +1 -0
  33. package/dist/components/BitkitFilter/BitkitFilter.d.ts +54 -0
  34. package/dist/components/BitkitFilter/BitkitFilter.js +247 -0
  35. package/dist/components/BitkitFilter/BitkitFilter.js.map +1 -0
  36. package/dist/components/BitkitFilter/BitkitFilter.utils.d.ts +18 -0
  37. package/dist/components/BitkitFilter/BitkitFilter.utils.js +42 -0
  38. package/dist/components/BitkitFilter/BitkitFilter.utils.js.map +1 -0
  39. package/dist/components/BitkitFilter/BitkitFilterAdd.d.ts +18 -0
  40. package/dist/components/BitkitFilter/BitkitFilterAdd.js +200 -0
  41. package/dist/components/BitkitFilter/BitkitFilterAdd.js.map +1 -0
  42. package/dist/components/BitkitFilter/BitkitFilterDateRange.d.ts +26 -0
  43. package/dist/components/BitkitFilter/BitkitFilterDateRange.js +118 -0
  44. package/dist/components/BitkitFilter/BitkitFilterDateRange.js.map +1 -0
  45. package/dist/components/BitkitFilter/BitkitFilterSearch.d.ts +23 -0
  46. package/dist/components/BitkitFilter/BitkitFilterSearch.js +72 -0
  47. package/dist/components/BitkitFilter/BitkitFilterSearch.js.map +1 -0
  48. package/dist/components/BitkitFilter/BitkitFilterSelect.d.ts +61 -0
  49. package/dist/components/BitkitFilter/BitkitFilterSelect.js +149 -0
  50. package/dist/components/BitkitFilter/BitkitFilterSelect.js.map +1 -0
  51. package/dist/components/BitkitFilter/BitkitFilterSwitch.d.ts +28 -0
  52. package/dist/components/BitkitFilter/BitkitFilterSwitch.js +87 -0
  53. package/dist/components/BitkitFilter/BitkitFilterSwitch.js.map +1 -0
  54. package/dist/components/BitkitFilter/components/FilterChip.d.ts +23 -0
  55. package/dist/components/BitkitFilter/components/FilterChip.js +66 -0
  56. package/dist/components/BitkitFilter/components/FilterChip.js.map +1 -0
  57. package/dist/components/BitkitFilter/components/FilterField.d.ts +15 -0
  58. package/dist/components/BitkitFilter/components/FilterField.js +60 -0
  59. package/dist/components/BitkitFilter/components/FilterField.js.map +1 -0
  60. package/dist/components/BitkitFilter/components/FilterPanel.d.ts +42 -0
  61. package/dist/components/BitkitFilter/components/FilterPanel.js +126 -0
  62. package/dist/components/BitkitFilter/components/FilterPanel.js.map +1 -0
  63. package/dist/components/BitkitFilter/components/FilterSearchInput.d.ts +15 -0
  64. package/dist/components/BitkitFilter/components/FilterSearchInput.js +41 -0
  65. package/dist/components/BitkitFilter/components/FilterSearchInput.js.map +1 -0
  66. package/dist/components/BitkitFilter/components/FilterSelectForm.d.ts +45 -0
  67. package/dist/components/BitkitFilter/components/FilterSelectForm.js +342 -0
  68. package/dist/components/BitkitFilter/components/FilterSelectForm.js.map +1 -0
  69. package/dist/components/BitkitLabelTooltip/BitkitLabelTooltip.d.ts +4 -3
  70. package/dist/components/BitkitLabelTooltip/BitkitLabelTooltip.js +3 -2
  71. package/dist/components/BitkitLabelTooltip/BitkitLabelTooltip.js.map +1 -1
  72. package/dist/components/BitkitMarkdown/BitkitMarkdown.js +1 -1
  73. package/dist/components/BitkitNoteCard/BitkitNoteCard.js +1 -1
  74. package/dist/components/BitkitPageSidebar/BitkitPageSidebar.js +1 -1
  75. package/dist/components/BitkitTextInput/BitkitTextInput.js +16 -13
  76. package/dist/components/BitkitTextInput/BitkitTextInput.js.map +1 -1
  77. package/dist/components/index.d.ts +6 -1
  78. package/dist/main.js +8 -9
  79. package/dist/theme/recipes/index.d.ts +525 -0
  80. package/dist/theme/recipes/index.js +1 -3
  81. package/dist/theme/recipes/index.js.map +1 -1
  82. package/dist/theme/semantic-tokens/index.d.ts +0 -5
  83. package/dist/theme/semantic-tokens/semanticColors.d.ts +0 -5
  84. package/dist/theme/semantic-tokens/semanticColors.js +0 -1
  85. package/dist/theme/semantic-tokens/semanticColors.js.map +1 -1
  86. package/dist/theme/slot-recipes/DatePicker.recipe.d.ts +15 -0
  87. package/dist/theme/slot-recipes/DatePicker.recipe.js +72 -22
  88. package/dist/theme/slot-recipes/DatePicker.recipe.js.map +1 -1
  89. package/dist/theme/slot-recipes/DatePickerSelect.recipe.d.ts +85 -0
  90. package/dist/theme/slot-recipes/DatePickerSelect.recipe.js +35 -10
  91. package/dist/theme/slot-recipes/DatePickerSelect.recipe.js.map +1 -1
  92. package/dist/theme/slot-recipes/Filter.recipe.d.ts +35 -0
  93. package/dist/theme/slot-recipes/Filter.recipe.js +652 -0
  94. package/dist/theme/slot-recipes/Filter.recipe.js.map +1 -0
  95. package/dist/theme/slot-recipes/Listbox.recipe.d.ts +22 -0
  96. package/dist/theme/slot-recipes/Listbox.recipe.js +108 -0
  97. package/dist/theme/slot-recipes/Listbox.recipe.js.map +1 -0
  98. package/dist/theme/slot-recipes/Menu.recipe.js +6 -1
  99. package/dist/theme/slot-recipes/Menu.recipe.js.map +1 -1
  100. package/dist/theme/slot-recipes/Tag.recipe.d.ts +1 -1
  101. package/dist/theme/slot-recipes/UISideNav.recipe.d.ts +2 -1
  102. package/dist/theme/slot-recipes/UISideNav.recipe.js +3 -2
  103. package/dist/theme/slot-recipes/UISideNav.recipe.js.map +1 -1
  104. package/dist/theme/slot-recipes/index.d.ts +158 -3
  105. package/dist/theme/slot-recipes/index.js +4 -2
  106. package/dist/theme/slot-recipes/index.js.map +1 -1
  107. package/dist/theme/themeUtils.d.ts +0 -6
  108. package/dist/theme/themeUtils.js +1 -7
  109. package/dist/theme/themeUtils.js.map +1 -1
  110. package/dist/utilities/BitkitPortalContext.js +3 -2
  111. package/dist/utilities/BitkitPortalContext.js.map +1 -1
  112. package/docs/v1-to-v2-migration-guide.md +48 -1
  113. package/package.json +1 -1
  114. package/dist/components/BitkitUIHeader/BitkitUIHeader.d.ts +0 -41
  115. package/dist/components/BitkitUIHeader/BitkitUIHeader.js +0 -114
  116. package/dist/components/BitkitUIHeader/BitkitUIHeader.js.map +0 -1
  117. package/dist/components/BitkitUIHeader/BitriseLogo.d.ts +0 -10
  118. package/dist/components/BitkitUIHeader/BitriseLogo.js +0 -86
  119. package/dist/components/BitkitUIHeader/BitriseLogo.js.map +0 -1
  120. package/dist/components/BitkitUIHeader/UIHeaderAccount.d.ts +0 -26
  121. package/dist/components/BitkitUIHeader/UIHeaderAccount.js +0 -40
  122. package/dist/components/BitkitUIHeader/UIHeaderAccount.js.map +0 -1
  123. package/dist/components/BitkitUIHeader/UIHeaderButton.d.ts +0 -34
  124. package/dist/components/BitkitUIHeader/UIHeaderButton.js +0 -45
  125. package/dist/components/BitkitUIHeader/UIHeaderButton.js.map +0 -1
  126. package/dist/components/BitkitUIHeader/UIHeaderContext.d.ts +0 -13
  127. package/dist/components/BitkitUIHeader/UIHeaderContext.js +0 -11
  128. package/dist/components/BitkitUIHeader/UIHeaderContext.js.map +0 -1
  129. package/dist/components/BitkitUIHeader/UIHeaderControl.d.ts +0 -65
  130. package/dist/components/BitkitUIHeader/UIHeaderControl.js +0 -72
  131. package/dist/components/BitkitUIHeader/UIHeaderControl.js.map +0 -1
  132. package/dist/components/BitkitUIHeader/UIHeaderNavButton.d.ts +0 -20
  133. package/dist/components/BitkitUIHeader/UIHeaderNavButton.js +0 -46
  134. package/dist/components/BitkitUIHeader/UIHeaderNavButton.js.map +0 -1
  135. package/dist/components/BitkitUIHeader/UIHeaderSearch.d.ts +0 -32
  136. package/dist/components/BitkitUIHeader/UIHeaderSearch.js +0 -121
  137. package/dist/components/BitkitUIHeader/UIHeaderSearch.js.map +0 -1
  138. package/dist/components/BitkitUIHeader/uiHeaderPart.d.ts +0 -14
  139. package/dist/components/BitkitUIHeader/uiHeaderPart.js +0 -18
  140. package/dist/components/BitkitUIHeader/uiHeaderPart.js.map +0 -1
  141. package/dist/theme/recipes/UIHeaderButton.recipe.js +0 -103
  142. package/dist/theme/recipes/UIHeaderButton.recipe.js.map +0 -1
  143. package/dist/theme/slot-recipes/UIHeader.recipe.d.ts +0 -14
  144. package/dist/theme/slot-recipes/UIHeader.recipe.js +0 -253
  145. package/dist/theme/slot-recipes/UIHeader.recipe.js.map +0 -1
@@ -0,0 +1,126 @@
1
+ import IconArrowLeft from "../../../icons/IconArrowLeft.js";
2
+ import { usePortalContext } from "../../../utilities/BitkitPortalContext.js";
3
+ import BitkitBadge from "../../BitkitBadge/BitkitBadge.js";
4
+ import BitkitControlButton from "../../BitkitControlButton/BitkitControlButton.js";
5
+ import BitkitButton from "../../BitkitButton/BitkitButton.js";
6
+ import { useFilterContext } from "../BitkitFilter.context.js";
7
+ import { Box } from "@chakra-ui/react/box";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ import { Portal } from "@chakra-ui/react/portal";
10
+ import { Text } from "@chakra-ui/react/text";
11
+ import { Drawer } from "@chakra-ui/react/drawer";
12
+ import { Popover } from "@chakra-ui/react/popover";
13
+ //#region lib/components/BitkitFilter/components/FilterPanel.tsx
14
+ /**
15
+ * Where a filter edits its value: an anchored popover on desktop, a full-screen view stacked over the
16
+ * filters dialog on mobile. The content unmounts on close, so the draft state of the form inside
17
+ * starts over from the applied value each time it opens.
18
+ */
19
+ var FilterPanel = (props) => {
20
+ const { children, finalFocusEl, getAnchorElement, onOpenChange: onOpenChangeProp, open, trigger } = props;
21
+ const { isDisabled, isMobile, styles } = useFilterContext();
22
+ const onOpenChange = (isOpen) => {
23
+ if (!isOpen || !isDisabled) onOpenChangeProp(isOpen);
24
+ };
25
+ const { disablePortal } = usePortalContext();
26
+ if (isMobile) return /* @__PURE__ */ jsxs(Fragment, { children: [trigger, /* @__PURE__ */ jsx(Drawer.Root, {
27
+ finalFocusEl,
28
+ lazyMount: true,
29
+ onOpenChange: ({ open: isOpen }) => onOpenChange(isOpen),
30
+ open,
31
+ unmountOnExit: true,
32
+ children: /* @__PURE__ */ jsx(Portal, {
33
+ disabled: disablePortal,
34
+ children: /* @__PURE__ */ jsx(Drawer.Positioner, { children: /* @__PURE__ */ jsx(Drawer.Content, { children }) })
35
+ })
36
+ })] });
37
+ return /* @__PURE__ */ jsxs(Popover.Root, {
38
+ finalFocusEl,
39
+ lazyMount: true,
40
+ onOpenChange: ({ open: isOpen }) => onOpenChange(isOpen),
41
+ open,
42
+ positioning: {
43
+ getAnchorElement,
44
+ gutter: 8,
45
+ placement: "bottom-start"
46
+ },
47
+ unmountOnExit: true,
48
+ children: [trigger, /* @__PURE__ */ jsx(Portal, {
49
+ disabled: disablePortal,
50
+ children: /* @__PURE__ */ jsx(Popover.Positioner, { children: /* @__PURE__ */ jsx(Popover.Content, {
51
+ css: styles.popoverContent,
52
+ children
53
+ }) })
54
+ })]
55
+ });
56
+ };
57
+ var FilterPanelHeader = ({ count, title }) => {
58
+ const { isMobile, styles } = useFilterContext();
59
+ if (isMobile) return /* @__PURE__ */ jsxs(Box, {
60
+ css: styles.mobilePanelHeader,
61
+ children: [/* @__PURE__ */ jsx(Drawer.CloseTrigger, {
62
+ asChild: true,
63
+ unstyled: true,
64
+ children: /* @__PURE__ */ jsx(BitkitControlButton, {
65
+ icon: IconArrowLeft,
66
+ label: "Back"
67
+ })
68
+ }), /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
69
+ css: styles.mobilePanelOverline,
70
+ children: "Filter"
71
+ }), /* @__PURE__ */ jsx(Drawer.Title, {
72
+ css: styles.mobilePanelTitle,
73
+ unstyled: true,
74
+ children: title
75
+ })] })]
76
+ });
77
+ return /* @__PURE__ */ jsxs(Popover.Header, { children: [/* @__PURE__ */ jsx(Popover.Title, { children: title }), count !== void 0 && /* @__PURE__ */ jsx(BitkitBadge, { children: count })] });
78
+ };
79
+ var FilterPanelFooter = ({ applyLabel, isApplyDisabled, onApply, onCancel, start }) => {
80
+ const { isMobile, styles } = useFilterContext();
81
+ const applyState = isApplyDisabled ? "disabled" : void 0;
82
+ if (isMobile) return /* @__PURE__ */ jsx(Drawer.Footer, { children: /* @__PURE__ */ jsxs(Box, {
83
+ css: styles.mobileFooter,
84
+ children: [!!start && /* @__PURE__ */ jsx(BitkitButton, {
85
+ onClick: start.onClick,
86
+ size: "md",
87
+ variant: "secondary",
88
+ children: start.label
89
+ }), /* @__PURE__ */ jsx(BitkitButton, {
90
+ onClick: onApply,
91
+ size: "md",
92
+ state: applyState,
93
+ children: applyLabel ?? "Apply"
94
+ })]
95
+ }) });
96
+ return /* @__PURE__ */ jsxs(Box, {
97
+ css: styles.popoverFooter,
98
+ children: [
99
+ /* @__PURE__ */ jsx(Box, {
100
+ css: styles.popoverFooterStart,
101
+ children: !!start && /* @__PURE__ */ jsx(BitkitButton, {
102
+ onClick: start.onClick,
103
+ size: "sm",
104
+ variant: "tertiary",
105
+ children: start.label
106
+ })
107
+ }),
108
+ !!onCancel && /* @__PURE__ */ jsx(BitkitButton, {
109
+ onClick: onCancel,
110
+ size: "sm",
111
+ variant: "secondary",
112
+ children: "Cancel"
113
+ }),
114
+ /* @__PURE__ */ jsx(BitkitButton, {
115
+ onClick: onApply,
116
+ size: "sm",
117
+ state: applyState,
118
+ children: "Apply"
119
+ })
120
+ ]
121
+ });
122
+ };
123
+ //#endregion
124
+ export { FilterPanelFooter, FilterPanelHeader, FilterPanel as default };
125
+
126
+ //# sourceMappingURL=FilterPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterPanel.js","names":["_Fragment"],"sources":["../../../../lib/components/BitkitFilter/components/FilterPanel.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { Drawer } from '@chakra-ui/react/drawer';\nimport { Popover } from '@chakra-ui/react/popover';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { Text } from '@chakra-ui/react/text';\nimport { type ReactNode } from 'react';\n\nimport { IconArrowLeft } from '../../../icons';\nimport { usePortalContext } from '../../../utilities/BitkitPortalContext';\nimport BitkitBadge from '../../BitkitBadge/BitkitBadge';\nimport BitkitButton from '../../BitkitButton/BitkitButton';\nimport BitkitControlButton from '../../BitkitControlButton/BitkitControlButton';\nimport { useFilterContext } from '../BitkitFilter.context';\n\nexport interface FilterPanelProps {\n children: ReactNode;\n /** Where focus goes on close, for a panel without a trigger to return it to. */\n finalFocusEl?: () => HTMLElement | null;\n /** Anchors the desktop popover to an element it has no trigger for — the \"Add filter\" button. */\n getAnchorElement?: () => HTMLElement | null;\n onOpenChange: (open: boolean) => void;\n open: boolean;\n /**\n * What opens the panel: on desktop a `FilterChip`, which renders the popover's trigger; on mobile a\n * `FilterField`, which opens it through its own `onClick`.\n */\n trigger?: ReactNode;\n}\n\n/**\n * Where a filter edits its value: an anchored popover on desktop, a full-screen view stacked over the\n * filters dialog on mobile. The content unmounts on close, so the draft state of the form inside\n * starts over from the applied value each time it opens.\n */\nconst FilterPanel = (props: FilterPanelProps) => {\n const { children, finalFocusEl, getAnchorElement, onOpenChange: onOpenChangeProp, open, trigger } = props;\n const { isDisabled, isMobile, styles } = useFilterContext();\n // While the page loads the triggers are only `aria-disabled`, so opening is refused here.\n const onOpenChange = (isOpen: boolean) => {\n if (!isOpen || !isDisabled) onOpenChangeProp(isOpen);\n };\n const { disablePortal } = usePortalContext();\n\n if (isMobile) {\n return (\n <>\n {trigger}\n <Drawer.Root\n finalFocusEl={finalFocusEl}\n lazyMount\n onOpenChange={({ open: isOpen }) => onOpenChange(isOpen)}\n open={open}\n unmountOnExit\n >\n <Portal disabled={disablePortal}>\n <Drawer.Positioner>\n <Drawer.Content>{children}</Drawer.Content>\n </Drawer.Positioner>\n </Portal>\n </Drawer.Root>\n </>\n );\n }\n\n return (\n <Popover.Root\n finalFocusEl={finalFocusEl}\n lazyMount\n onOpenChange={({ open: isOpen }) => onOpenChange(isOpen)}\n open={open}\n positioning={{\n getAnchorElement,\n gutter: 8,\n placement: 'bottom-start',\n }}\n unmountOnExit\n >\n {trigger}\n <Portal disabled={disablePortal}>\n <Popover.Positioner>\n <Popover.Content css={styles.popoverContent}>{children}</Popover.Content>\n </Popover.Positioner>\n </Portal>\n </Popover.Root>\n );\n};\n\nexport interface FilterPanelHeaderProps {\n /** Desktop only: a multi-select's selection count, shown as a badge next to the title. */\n count?: number;\n title: string;\n}\n\nexport const FilterPanelHeader = ({ count, title }: FilterPanelHeaderProps) => {\n const { isMobile, styles } = useFilterContext();\n\n if (isMobile) {\n return (\n <Box css={styles.mobilePanelHeader}>\n <Drawer.CloseTrigger asChild unstyled>\n <BitkitControlButton icon={IconArrowLeft} label=\"Back\" />\n </Drawer.CloseTrigger>\n <Box>\n <Text css={styles.mobilePanelOverline}>Filter</Text>\n <Drawer.Title css={styles.mobilePanelTitle} unstyled>\n {title}\n </Drawer.Title>\n </Box>\n </Box>\n );\n }\n\n return (\n <Popover.Header>\n <Popover.Title>{title}</Popover.Title>\n {count !== undefined && <BitkitBadge>{count}</BitkitBadge>}\n </Popover.Header>\n );\n};\n\nexport interface FilterPanelFooterProps {\n /** Mobile only: the Apply button's label, e.g. \"Apply (2)\". */\n applyLabel?: string;\n isApplyDisabled: boolean;\n onApply: () => void;\n /** Desktop only: adds a \"Cancel\" button. On mobile the header's back button cancels. */\n onCancel?: () => void;\n /** The action before Apply: \"Clear\", or the wildcard-mode switch. */\n start?: { label: string; onClick: () => void };\n}\n\nexport const FilterPanelFooter = ({\n applyLabel,\n isApplyDisabled,\n onApply,\n onCancel,\n start,\n}: FilterPanelFooterProps) => {\n const { isMobile, styles } = useFilterContext();\n const applyState = isApplyDisabled ? 'disabled' : undefined;\n\n if (isMobile) {\n return (\n <Drawer.Footer>\n <Box css={styles.mobileFooter}>\n {!!start && (\n <BitkitButton onClick={start.onClick} size=\"md\" variant=\"secondary\">\n {start.label}\n </BitkitButton>\n )}\n <BitkitButton onClick={onApply} size=\"md\" state={applyState}>\n {applyLabel ?? 'Apply'}\n </BitkitButton>\n </Box>\n </Drawer.Footer>\n );\n }\n\n return (\n <Box css={styles.popoverFooter}>\n <Box css={styles.popoverFooterStart}>\n {!!start && (\n <BitkitButton onClick={start.onClick} size=\"sm\" variant=\"tertiary\">\n {start.label}\n </BitkitButton>\n )}\n </Box>\n {!!onCancel && (\n <BitkitButton onClick={onCancel} size=\"sm\" variant=\"secondary\">\n Cancel\n </BitkitButton>\n )}\n <BitkitButton onClick={onApply} size=\"sm\" state={applyState}>\n Apply\n </BitkitButton>\n </Box>\n );\n};\n\nexport default FilterPanel;\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { BoxProps } from '@chakra-ui/react/box';
2
+ import { ReactElement } from 'react';
3
+ export interface FilterSearchInputProps extends Omit<BoxProps, 'children' | 'defaultValue' | 'onChange'> {
4
+ /** Extra hint read out with the field, e.g. what the search matches. */
5
+ description?: string;
6
+ label: string;
7
+ onValueChange: (value: string) => void;
8
+ placeholder: string;
9
+ /** Wraps the input, e.g. in a tooltip. */
10
+ renderInput?: (input: ReactElement) => ReactElement;
11
+ value: string;
12
+ }
13
+ /** The borderless search field of the bar — and, on mobile, of each filter's view too. */
14
+ declare const FilterSearchInput: import('react').ForwardRefExoticComponent<FilterSearchInputProps & import('react').RefAttributes<HTMLInputElement>>;
15
+ export default FilterSearchInput;
@@ -0,0 +1,41 @@
1
+ import IconMagnifier from "../../../icons/IconMagnifier.js";
2
+ import BitkitCloseButton from "../../BitkitCloseButton/BitkitCloseButton.js";
3
+ import { useFilterContext } from "../BitkitFilter.context.js";
4
+ import { Box } from "@chakra-ui/react/box";
5
+ import { chakra } from "@chakra-ui/react/styled-system";
6
+ import { forwardRef } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ //#region lib/components/BitkitFilter/components/FilterSearchInput.tsx
9
+ /** The borderless search field of the bar — and, on mobile, of each filter's view too. */
10
+ var FilterSearchInput = forwardRef((props, ref) => {
11
+ const { css, description, label, onValueChange, placeholder, renderInput = (input) => input, value, ...rest } = props;
12
+ const { isMobile, styles } = useFilterContext();
13
+ return /* @__PURE__ */ jsxs(Box, {
14
+ css: [isMobile ? styles.mobileSearch : styles.search, css],
15
+ ...rest,
16
+ children: [/* @__PURE__ */ jsxs(Box, {
17
+ as: "label",
18
+ display: "contents",
19
+ children: [/* @__PURE__ */ jsx(IconMagnifier, { size: isMobile ? "24" : "16" }), renderInput(/* @__PURE__ */ jsx(chakra.input, {
20
+ "aria-description": description,
21
+ "aria-label": label,
22
+ css: isMobile ? styles.mobileSearchInput : styles.searchInput,
23
+ onChange: (event) => onValueChange(event.target.value),
24
+ placeholder,
25
+ ref,
26
+ type: "search",
27
+ value
28
+ }))]
29
+ }), !!value && /* @__PURE__ */ jsx(BitkitCloseButton, {
30
+ "aria-label": "Clear search",
31
+ colorVariant: "neutral",
32
+ onClick: () => onValueChange(""),
33
+ size: "sm"
34
+ })]
35
+ });
36
+ });
37
+ FilterSearchInput.displayName = "FilterSearchInput";
38
+ //#endregion
39
+ export { FilterSearchInput as default };
40
+
41
+ //# sourceMappingURL=FilterSearchInput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterSearchInput.js","names":[],"sources":["../../../../lib/components/BitkitFilter/components/FilterSearchInput.tsx"],"sourcesContent":["import { Box, type BoxProps } from '@chakra-ui/react/box';\n// `chakra.input`: the filter's search fields are borderless rows from the filter recipe, unlike the\n// bordered BitkitSearchInput.\nimport { chakra } from '@chakra-ui/react/styled-system';\nimport { forwardRef, type ReactElement } from 'react';\n\nimport { IconMagnifier } from '../../../icons';\nimport BitkitCloseButton from '../../BitkitCloseButton/BitkitCloseButton';\nimport { useFilterContext } from '../BitkitFilter.context';\n\nexport interface FilterSearchInputProps extends Omit<BoxProps, 'children' | 'defaultValue' | 'onChange'> {\n /** Extra hint read out with the field, e.g. what the search matches. */\n description?: string;\n label: string;\n onValueChange: (value: string) => void;\n placeholder: string;\n /** Wraps the input, e.g. in a tooltip. */\n renderInput?: (input: ReactElement) => ReactElement;\n value: string;\n}\n\n/** The borderless search field of the bar — and, on mobile, of each filter's view too. */\nconst FilterSearchInput = forwardRef<HTMLInputElement, FilterSearchInputProps>((props, ref) => {\n const { css, description, label, onValueChange, placeholder, renderInput = (input) => input, value, ...rest } = props;\n const { isMobile, styles } = useFilterContext();\n\n return (\n <Box css={[isMobile ? styles.mobileSearch : styles.search, css]} {...rest}>\n {/* A label around the icon and the input only, so a click on the icon focuses the input too. The clear\n button stays outside: a label can't hold a second control. `contents` keeps the field's layout. */}\n <Box as=\"label\" display=\"contents\">\n <IconMagnifier size={isMobile ? '24' : '16'} />\n {renderInput(\n <chakra.input\n aria-description={description}\n aria-label={label}\n css={isMobile ? styles.mobileSearchInput : styles.searchInput}\n onChange={(event) => onValueChange(event.target.value)}\n placeholder={placeholder}\n ref={ref}\n type=\"search\"\n value={value}\n />,\n )}\n </Box>\n {!!value && (\n <BitkitCloseButton\n aria-label=\"Clear search\"\n colorVariant=\"neutral\"\n onClick={() => onValueChange('')}\n size=\"sm\"\n />\n )}\n </Box>\n );\n});\n\nFilterSearchInput.displayName = 'FilterSearchInput';\n\nexport default FilterSearchInput;\n"],"mappings":";;;;;;;;;AAsBA,IAAM,oBAAoB,YAAsD,OAAO,QAAQ;CAC7F,MAAM,EAAE,KAAK,aAAa,OAAO,eAAe,aAAa,eAAe,UAAU,OAAO,OAAO,GAAG,SAAS;CAChH,MAAM,EAAE,UAAU,WAAW,iBAAiB;CAE9C,OACE,qBAAC,KAAD;EAAK,KAAK,CAAC,WAAW,OAAO,eAAe,OAAO,QAAQ,GAAG;EAAG,GAAI;EAArE,UAAA,CAGE,qBAAC,KAAD;GAAK,IAAG;GAAQ,SAAQ;GAAxB,UAAA,CACE,oBAAC,eAAD,EAAe,MAAM,WAAW,OAAO,KAAO,CAAA,GAC7C,YACC,oBAAC,OAAO,OAAR;IACE,oBAAkB;IAClB,cAAY;IACZ,KAAK,WAAW,OAAO,oBAAoB,OAAO;IAClD,WAAW,UAAU,cAAc,MAAM,OAAO,KAAK;IACxC;IACR;IACL,MAAK;IACE;GACR,CAAA,CACH,CACG;EACJ,CAAA,GAAA,CAAC,CAAC,SACD,oBAAC,mBAAD;GACE,cAAW;GACX,cAAa;GACb,eAAe,cAAc,EAAE;GAC/B,MAAK;EACN,CAAA,CAEA;;AAET,CAAC;AAED,kBAAkB,cAAc"}
@@ -0,0 +1,45 @@
1
+ import { BitkitIconComponent } from '../../../icons';
2
+ import { BitkitColorToken } from '../../../theme/themeUtils';
3
+ export interface BitkitFilterSelectItem {
4
+ /**
5
+ * Lists the option under this heading, with the others of the same group. Only the open list shows
6
+ * it: the chip shows the label alone, so the label has to make sense without its group.
7
+ */
8
+ group?: string;
9
+ icon?: BitkitIconComponent;
10
+ /** Colors the icon, e.g. a status: `icon/positive` for "Passed". */
11
+ iconColor?: BitkitColorToken<'icon'>;
12
+ label: string;
13
+ /** Unique across all groups. */
14
+ value: string;
15
+ }
16
+ /** The list renders at most this many options; search narrows down the rest. */
17
+ export declare const MAX_VISIBLE_ITEMS = 100;
18
+ export interface FilterSelectFormProps {
19
+ /** The filter holds a value: the footer offers "Clear" instead of the wildcard-mode switch. */
20
+ isEditing: boolean;
21
+ /** Keeps `items` in their own order instead of listing the applied ones first — for a switch's fixed options. */
22
+ isOrderFixed?: boolean;
23
+ /** A value is always applied: no "Clear" in the footer. */
24
+ isRequired?: boolean;
25
+ items: BitkitFilterSelectItem[];
26
+ label: string;
27
+ loading?: boolean;
28
+ loadingLabel?: string;
29
+ multiple?: boolean;
30
+ onApply: (value: string[]) => void;
31
+ onCancel: () => void;
32
+ onSearchChange?: (searchText: string) => void;
33
+ pattern?: boolean;
34
+ pluralLabel: string;
35
+ searchPlaceholder?: string;
36
+ /**
37
+ * The applied selection with its labels. Without a search, any of it missing from `items` — e.g.
38
+ * left behind by a server search — is listed first, so it can still be unchecked.
39
+ */
40
+ selectedItems?: BitkitFilterSelectItem[];
41
+ value: string[];
42
+ }
43
+ /** The body of a select filter's panel: search, the option list or wildcard pattern, and the footer. */
44
+ declare const FilterSelectForm: (props: FilterSelectFormProps) => import('react').JSX.Element;
45
+ export default FilterSelectForm;
@@ -0,0 +1,342 @@
1
+ import IconCheck from "../../../icons/IconCheck.js";
2
+ import BitkitGroupHeading from "../../BitkitGroupHeading/BitkitGroupHeading.js";
3
+ import BitkitCheckbox from "../../BitkitCheckbox/BitkitCheckbox.js";
4
+ import BitkitCheckboxGroup from "../../BitkitCheckboxGroup/BitkitCheckboxGroup.js";
5
+ import { compilePattern, isEqualFilterValue, isPattern, toRunningText } from "../BitkitFilter.utils.js";
6
+ import { useFilterContext } from "../BitkitFilter.context.js";
7
+ import { FilterPanelFooter, FilterPanelHeader } from "./FilterPanel.js";
8
+ import FilterSearchInput from "./FilterSearchInput.js";
9
+ import BitkitInlineLoading from "../../BitkitInlineLoading/BitkitInlineLoading.js";
10
+ import BitkitList_default from "../../BitkitList/BitkitList.js";
11
+ import BitkitTextInput from "../../BitkitTextInput/BitkitTextInput.js";
12
+ import BitkitSearchInput from "../../BitkitSearchInput/BitkitSearchInput.js";
13
+ import { Box } from "@chakra-ui/react/box";
14
+ import { useSlotRecipe } from "@chakra-ui/react/styled-system";
15
+ import { Fragment, useEffect, useId, useMemo, useRef, useState } from "react";
16
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
17
+ import { Text } from "@chakra-ui/react/text";
18
+ import { createListCollection } from "@chakra-ui/react/collection";
19
+ import { HStack } from "@chakra-ui/react/stack";
20
+ import { Listbox } from "@chakra-ui/react/listbox";
21
+ //#region lib/components/BitkitFilter/components/FilterSelectForm.tsx
22
+ /**
23
+ * Gathers the options by `group`, so the list (and its keyboard order) runs group by group. The groups
24
+ * keep their order in `items`, with the ungrouped options (an empty-named group) first; within a
25
+ * group the options keep their order in `options`, e.g. the applied ones first.
26
+ */
27
+ var groupItems = (options, items) => {
28
+ const groups = /* @__PURE__ */ new Map([["", []]]);
29
+ [...items, ...options].forEach((item) => {
30
+ if (!groups.has(item.group ?? "")) groups.set(item.group ?? "", []);
31
+ });
32
+ options.forEach((option) => groups.get(option.group ?? "")?.push(option));
33
+ return [...groups].filter(([, groupOptions]) => groupOptions.length);
34
+ };
35
+ /** Below this many options the list is short enough to scan, so there is no search input. */
36
+ var SEARCH_THRESHOLD = 5;
37
+ /** The body of a select filter's panel: search, the option list or wildcard pattern, and the footer. */
38
+ var FilterSelectForm = (props) => {
39
+ const { isEditing, isOrderFixed, isRequired, items, label, loading, loadingLabel = "Loading...", multiple, onApply, onCancel, onSearchChange, pattern, pluralLabel, searchPlaceholder = "Search for options", selectedItems = [], value } = props;
40
+ const { isMobile, styles } = useFilterContext();
41
+ const [draft, setDraft] = useState(value);
42
+ const [mode, setMode] = useState(pattern && value[0] && isPattern(value[0]) ? "pattern" : "list");
43
+ const [searchText, setSearchText] = useState("");
44
+ const [hasLoaded, setHasLoaded] = useState(!loading);
45
+ if (!loading && !hasLoaded) setHasLoaded(true);
46
+ const onSearchTextChange = (text) => {
47
+ setSearchText(text);
48
+ onSearchChange?.(text);
49
+ };
50
+ const matchingItems = useMemo(() => {
51
+ if (!searchText) {
52
+ if (isOrderFixed) return items;
53
+ return [...value.flatMap((selected) => {
54
+ return items.find((option) => option.value === selected) ?? selectedItems.find((option) => option.value === selected) ?? [];
55
+ }), ...items.filter((item) => !value.includes(item.value))];
56
+ }
57
+ if (onSearchChange) return items;
58
+ const query = searchText.toLowerCase();
59
+ return items.filter((item) => item.label.toLowerCase().includes(query));
60
+ }, [
61
+ isOrderFixed,
62
+ items,
63
+ onSearchChange,
64
+ searchText,
65
+ selectedItems,
66
+ value
67
+ ]);
68
+ const visibleGroups = useMemo(() => groupItems(matchingItems.slice(0, 100), items), [items, matchingItems]);
69
+ const visibleItems = visibleGroups.flatMap(([, groupItems]) => groupItems);
70
+ const isInitialLoading = !!loading && !hasLoaded;
71
+ const showSearch = !isInitialLoading && (!!onSearchChange || items.length > SEARCH_THRESHOLD);
72
+ const searchLabel = `Search ${toRunningText(pluralLabel)}`;
73
+ const showLimitHelper = matchingItems.length > 100;
74
+ const patternValue = draft[0] ?? "";
75
+ const isApplyDisabled = isEqualFilterValue(draft, value) || mode === "pattern" && !(patternValue && isPattern(patternValue)) || !!isRequired && !draft.length;
76
+ const bodyRef = useRef(null);
77
+ const hasSwitchedMode = useRef(false);
78
+ useEffect(() => {
79
+ if (!hasSwitchedMode.current) return;
80
+ bodyRef.current?.querySelector("input:not([type=hidden]), [role=listbox], input[type=checkbox]")?.focus();
81
+ }, [mode]);
82
+ let start;
83
+ if (isEditing && !isRequired) start = {
84
+ label: multiple && isMobile ? "Clear all" : "Clear",
85
+ onClick: () => onApply([])
86
+ };
87
+ else if (pattern && !isEditing) start = {
88
+ label: mode === "list" ? "Use wildcard pattern" : "Select from list",
89
+ onClick: () => {
90
+ hasSwitchedMode.current = true;
91
+ setMode(mode === "list" ? "pattern" : "list");
92
+ setDraft([]);
93
+ }
94
+ };
95
+ let list;
96
+ if (loading) list = /* @__PURE__ */ jsx(BitkitInlineLoading, {
97
+ css: styles.loading,
98
+ label: loadingLabel,
99
+ size: isMobile ? "lg" : "md"
100
+ });
101
+ else if (!visibleItems.length) list = /* @__PURE__ */ jsx(Text, {
102
+ css: styles.loading,
103
+ color: "text/secondary",
104
+ textStyle: isMobile ? "body/lg/regular" : "body/md/regular",
105
+ children: searchText ? "No result. Refine your search term." : "No options available."
106
+ });
107
+ else if (multiple && !isMobile) list = /* @__PURE__ */ jsxs(Box, {
108
+ css: styles.checkboxList,
109
+ children: [/* @__PURE__ */ jsx(BitkitCheckboxGroup, {
110
+ "aria-label": label,
111
+ onValueChange: setDraft,
112
+ size: "md",
113
+ value: draft,
114
+ children: visibleGroups.map(([group, groupItems]) => {
115
+ const checkboxes = groupItems.map((item) => /* @__PURE__ */ jsx(BitkitCheckbox, {
116
+ labelText: item.icon ? /* @__PURE__ */ jsxs(HStack, {
117
+ gap: "4",
118
+ children: [/* @__PURE__ */ jsx(item.icon, {
119
+ color: item.iconColor,
120
+ "data-option-icon": item.iconColor ? void 0 : "",
121
+ size: "16"
122
+ }), item.label]
123
+ }) : item.label,
124
+ value: item.value
125
+ }, item.value));
126
+ return group ? /* @__PURE__ */ jsx(CheckboxOptionGroup, {
127
+ label: group,
128
+ children: checkboxes
129
+ }, group) : /* @__PURE__ */ jsx(Fragment, { children: checkboxes }, group);
130
+ })
131
+ }), showLimitHelper && /* @__PURE__ */ jsx(LimitHelper, { marginBlockStart: "16" })]
132
+ });
133
+ else list = /* @__PURE__ */ jsx(FilterListbox, {
134
+ groups: visibleGroups,
135
+ items: visibleItems,
136
+ label,
137
+ multiple,
138
+ onValueChange: setDraft,
139
+ showLimitHelper,
140
+ value: draft
141
+ });
142
+ let search = null;
143
+ if (showSearch && isMobile) search = /* @__PURE__ */ jsx(FilterSearchInput, {
144
+ label: searchLabel,
145
+ onValueChange: onSearchTextChange,
146
+ placeholder: searchPlaceholder,
147
+ value: searchText
148
+ });
149
+ else if (showSearch) search = /* @__PURE__ */ jsx(Box, {
150
+ css: styles.popoverSearch,
151
+ children: /* @__PURE__ */ jsx(BitkitSearchInput, {
152
+ label: searchLabel,
153
+ onChange: onSearchTextChange,
154
+ placeholder: searchPlaceholder,
155
+ size: "md",
156
+ value: searchText
157
+ })
158
+ });
159
+ const body = /* @__PURE__ */ jsx(Box, {
160
+ css: styles.popoverBody,
161
+ ref: bodyRef,
162
+ children: mode === "pattern" ? /* @__PURE__ */ jsx(FilterPatternBody, {
163
+ items,
164
+ onValueChange: setDraft,
165
+ pluralLabel,
166
+ value: patternValue
167
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [search, list] })
168
+ });
169
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
170
+ /* @__PURE__ */ jsx(FilterPanelHeader, {
171
+ count: multiple && mode === "list" ? draft.length : void 0,
172
+ title: multiple ? pluralLabel : label
173
+ }),
174
+ isMobile ? /* @__PURE__ */ jsx(Box, {
175
+ flex: "1",
176
+ overflowY: "auto",
177
+ children: body
178
+ }) : body,
179
+ !isInitialLoading && /* @__PURE__ */ jsx(FilterPanelFooter, {
180
+ applyLabel: multiple && mode === "list" && draft.length ? `Apply (${draft.length})` : void 0,
181
+ isApplyDisabled,
182
+ onApply: () => onApply(mode === "pattern" ? [patternValue] : draft),
183
+ onCancel,
184
+ start
185
+ })
186
+ ] });
187
+ };
188
+ /** A named group of the desktop checkboxes, so a screen reader tells which group an option is in. */
189
+ var CheckboxOptionGroup = ({ children, label }) => {
190
+ const headingId = useId();
191
+ return /* @__PURE__ */ jsxs(Box, {
192
+ "aria-labelledby": headingId,
193
+ display: "flex",
194
+ flexDirection: "column",
195
+ gap: "12",
196
+ role: "group",
197
+ children: [/* @__PURE__ */ jsx(BitkitGroupHeading, {
198
+ id: headingId,
199
+ label,
200
+ paddingBlockStart: "12",
201
+ paddingInline: "4"
202
+ }), children]
203
+ });
204
+ };
205
+ var LimitHelper = ({ marginBlockStart }) => {
206
+ const { styles } = useFilterContext();
207
+ return /* @__PURE__ */ jsxs(Text, {
208
+ css: styles.listHelperText,
209
+ marginBlockStart,
210
+ children: [
211
+ "Showing the first ",
212
+ 100,
213
+ " options. Search to find more."
214
+ ]
215
+ });
216
+ };
217
+ /**
218
+ * The option list for a single select, and on mobile for a multiple one too — there the rows are
219
+ * full-width and highlighted when selected, with a checkbox drawn in front.
220
+ */
221
+ var FilterListbox = (props) => {
222
+ const { groups, items, label, multiple, onValueChange, showLimitHelper, value } = props;
223
+ const { isMobile, styles } = useFilterContext();
224
+ const collection = useMemo(() => createListCollection({ items }), [items]);
225
+ const checkboxStyles = useSlotRecipe({ key: "checkbox" })({ size: "lg" });
226
+ const checkIndicator = /* @__PURE__ */ jsx(Listbox.ItemIndicator, { children: /* @__PURE__ */ jsx(IconCheck, { size: "16" }) });
227
+ const renderItem = (item) => {
228
+ const state = value.includes(item.value) ? "checked" : "unchecked";
229
+ const check = multiple ? /* @__PURE__ */ jsx(Box, {
230
+ "aria-hidden": true,
231
+ css: [checkboxStyles.control, styles.mobileListCheckbox],
232
+ "data-state": state,
233
+ children: /* @__PURE__ */ jsx(IconCheck, {})
234
+ }) : /* @__PURE__ */ jsx(Box, {
235
+ "aria-hidden": true,
236
+ css: styles.mobileListCheck,
237
+ "data-state": state,
238
+ children: /* @__PURE__ */ jsx(IconCheck, { size: "16" })
239
+ });
240
+ return /* @__PURE__ */ jsxs(Listbox.Item, {
241
+ item,
242
+ children: [
243
+ isMobile && check,
244
+ !!item.icon && /* @__PURE__ */ jsx(item.icon, {
245
+ color: item.iconColor,
246
+ "data-option-icon": item.iconColor ? void 0 : "",
247
+ size: isMobile ? "24" : "16"
248
+ }),
249
+ /* @__PURE__ */ jsx(Listbox.ItemText, { children: item.label }),
250
+ !isMobile && checkIndicator
251
+ ]
252
+ }, item.value);
253
+ };
254
+ return /* @__PURE__ */ jsxs(Listbox.Root, {
255
+ collection,
256
+ onValueChange: (details) => onValueChange(details.value),
257
+ selectionMode: multiple ? "multiple" : "single",
258
+ size: isMobile ? "lg" : "md",
259
+ value,
260
+ children: [
261
+ /* @__PURE__ */ jsx(Listbox.Label, {
262
+ srOnly: true,
263
+ children: label
264
+ }),
265
+ /* @__PURE__ */ jsx(Listbox.Content, { children: groups.map(([group, groupItems]) => group ? /* @__PURE__ */ jsxs(Listbox.ItemGroup, { children: [/* @__PURE__ */ jsx(Listbox.ItemGroupLabel, {
266
+ asChild: true,
267
+ children: /* @__PURE__ */ jsx(BitkitGroupHeading, {
268
+ label: group,
269
+ paddingBlockStart: "12",
270
+ paddingInline: "24"
271
+ })
272
+ }), groupItems.map(renderItem)] }, group) : groupItems.map(renderItem)) }),
273
+ showLimitHelper && /* @__PURE__ */ jsx(Box, {
274
+ paddingBlockStart: "8",
275
+ paddingInline: "24",
276
+ children: /* @__PURE__ */ jsx(LimitHelper, {})
277
+ })
278
+ ]
279
+ });
280
+ };
281
+ var FilterPatternBody = ({ items, onValueChange, pluralLabel, value }) => {
282
+ const { isMobile, styles } = useFilterContext();
283
+ const noun = toRunningText(pluralLabel);
284
+ const matches = useMemo(() => {
285
+ if (!isPattern(value)) return null;
286
+ const regExp = compilePattern(value);
287
+ return items.filter((item) => !isPattern(item.label) && regExp.test(item.label));
288
+ }, [items, value]);
289
+ let results;
290
+ if (!matches) results = /* @__PURE__ */ jsxs(Text, {
291
+ color: "text/secondary",
292
+ textStyle: "body/md/regular",
293
+ children: [
294
+ "Enter a pattern with the wildcard ‘*’ to view ",
295
+ noun,
296
+ " with matching names."
297
+ ]
298
+ });
299
+ else if (!matches.length) results = /* @__PURE__ */ jsxs(Text, {
300
+ color: "text/tertiary",
301
+ textStyle: "body/md/regular",
302
+ children: [
303
+ "(no matching ",
304
+ noun,
305
+ ")"
306
+ ]
307
+ });
308
+ else results = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(BitkitList_default, {
309
+ size: "md",
310
+ children: matches.slice(0, 100).map((item) => /* @__PURE__ */ jsx(BitkitList_default.Item, { children: item.label }, item.value))
311
+ }), matches.length > 100 && /* @__PURE__ */ jsxs(Text, {
312
+ css: styles.listHelperText,
313
+ marginBlockStart: "16",
314
+ children: [
315
+ "Showing the first ",
316
+ 100,
317
+ " matches."
318
+ ]
319
+ })] });
320
+ return /* @__PURE__ */ jsxs(Box, {
321
+ css: styles.pattern,
322
+ "data-filter-pattern": "",
323
+ children: [/* @__PURE__ */ jsx(BitkitTextInput, {
324
+ helperText: "Allowed wildcard character: *",
325
+ label: "Wildcard pattern",
326
+ onValueChange: (text) => onValueChange(text ? [text] : []),
327
+ placeholder: "(e.g. *main*, release-*)",
328
+ size: isMobile ? "lg" : "md",
329
+ value
330
+ }), /* @__PURE__ */ jsxs(Box, {
331
+ css: styles.patternResults,
332
+ children: [/* @__PURE__ */ jsxs(Text, {
333
+ css: styles.patternResultsLabel,
334
+ children: ["Matching results", matches?.length ? ` (${matches.length})` : ""]
335
+ }), results]
336
+ })]
337
+ });
338
+ };
339
+ //#endregion
340
+ export { FilterSelectForm as default };
341
+
342
+ //# sourceMappingURL=FilterSelectForm.js.map