@bitrise/bitkit-v2 0.3.481-beta.3069 → 0.3.481-beta.3073
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/BitkitCalendar/BitkitCalendar.d.ts +12 -0
- package/dist/components/BitkitCalendar/BitkitCalendar.js +7 -1
- package/dist/components/BitkitCalendar/BitkitCalendar.js.map +1 -1
- package/dist/components/BitkitCalendar/components/CalendarContent.d.ts +5 -1
- package/dist/components/BitkitCalendar/components/CalendarContent.js +60 -43
- package/dist/components/BitkitCalendar/components/CalendarContent.js.map +1 -1
- package/dist/components/BitkitCalendar/components/CalendarMonth.d.ts +4 -2
- package/dist/components/BitkitCalendar/components/CalendarMonth.js +15 -5
- package/dist/components/BitkitCalendar/components/CalendarMonth.js.map +1 -1
- package/dist/components/BitkitCalendar/components/CalendarMonthSelect.d.ts +2 -1
- package/dist/components/BitkitCalendar/components/CalendarMonthSelect.js +3 -2
- package/dist/components/BitkitCalendar/components/CalendarMonthSelect.js.map +1 -1
- package/dist/components/BitkitCalendar/components/CalendarNextTrigger.d.ts +3 -1
- package/dist/components/BitkitCalendar/components/CalendarNextTrigger.js +3 -3
- package/dist/components/BitkitCalendar/components/CalendarNextTrigger.js.map +1 -1
- package/dist/components/BitkitCalendar/components/CalendarPrevTrigger.d.ts +3 -1
- package/dist/components/BitkitCalendar/components/CalendarPrevTrigger.js +3 -3
- package/dist/components/BitkitCalendar/components/CalendarPrevTrigger.js.map +1 -1
- package/dist/components/BitkitCalendar/components/CalendarSelect.d.ts +1 -0
- package/dist/components/BitkitCalendar/components/CalendarSelect.js +5 -3
- package/dist/components/BitkitCalendar/components/CalendarSelect.js.map +1 -1
- package/dist/components/BitkitCalendar/components/CalendarTrigger.js +1 -0
- package/dist/components/BitkitCalendar/components/CalendarTrigger.js.map +1 -1
- package/dist/components/BitkitCalendar/components/CalendarYearSelect.d.ts +2 -1
- package/dist/components/BitkitCalendar/components/CalendarYearSelect.js +3 -2
- package/dist/components/BitkitCalendar/components/CalendarYearSelect.js.map +1 -1
- package/dist/components/BitkitDatePicker/BitkitDatePicker.d.ts +2 -0
- package/dist/components/BitkitDatePicker/BitkitDatePicker.js +2 -1
- package/dist/components/BitkitDatePicker/BitkitDatePicker.js.map +1 -1
- package/dist/components/BitkitFilter/BitkitFilter.context.d.ts +65 -0
- package/dist/components/BitkitFilter/BitkitFilter.context.js +82 -0
- package/dist/components/BitkitFilter/BitkitFilter.context.js.map +1 -0
- package/dist/components/BitkitFilter/BitkitFilter.d.ts +55 -0
- package/dist/components/BitkitFilter/BitkitFilter.js +247 -0
- package/dist/components/BitkitFilter/BitkitFilter.js.map +1 -0
- package/dist/components/BitkitFilter/BitkitFilter.utils.d.ts +18 -0
- package/dist/components/BitkitFilter/BitkitFilter.utils.js +42 -0
- package/dist/components/BitkitFilter/BitkitFilter.utils.js.map +1 -0
- package/dist/components/BitkitFilter/BitkitFilterAdd.d.ts +18 -0
- package/dist/components/BitkitFilter/BitkitFilterAdd.js +200 -0
- package/dist/components/BitkitFilter/BitkitFilterAdd.js.map +1 -0
- package/dist/components/BitkitFilter/BitkitFilterDateRange.d.ts +26 -0
- package/dist/components/BitkitFilter/BitkitFilterDateRange.js +118 -0
- package/dist/components/BitkitFilter/BitkitFilterDateRange.js.map +1 -0
- package/dist/components/BitkitFilter/BitkitFilterSearch.d.ts +23 -0
- package/dist/components/BitkitFilter/BitkitFilterSearch.js +72 -0
- package/dist/components/BitkitFilter/BitkitFilterSearch.js.map +1 -0
- package/dist/components/BitkitFilter/BitkitFilterSelect.d.ts +61 -0
- package/dist/components/BitkitFilter/BitkitFilterSelect.js +149 -0
- package/dist/components/BitkitFilter/BitkitFilterSelect.js.map +1 -0
- package/dist/components/BitkitFilter/BitkitFilterSwitch.d.ts +28 -0
- package/dist/components/BitkitFilter/BitkitFilterSwitch.js +87 -0
- package/dist/components/BitkitFilter/BitkitFilterSwitch.js.map +1 -0
- package/dist/components/BitkitFilter/components/FilterChip.d.ts +23 -0
- package/dist/components/BitkitFilter/components/FilterChip.js +66 -0
- package/dist/components/BitkitFilter/components/FilterChip.js.map +1 -0
- package/dist/components/BitkitFilter/components/FilterField.d.ts +15 -0
- package/dist/components/BitkitFilter/components/FilterField.js +60 -0
- package/dist/components/BitkitFilter/components/FilterField.js.map +1 -0
- package/dist/components/BitkitFilter/components/FilterPanel.d.ts +42 -0
- package/dist/components/BitkitFilter/components/FilterPanel.js +126 -0
- package/dist/components/BitkitFilter/components/FilterPanel.js.map +1 -0
- package/dist/components/BitkitFilter/components/FilterSearchInput.d.ts +15 -0
- package/dist/components/BitkitFilter/components/FilterSearchInput.js +42 -0
- package/dist/components/BitkitFilter/components/FilterSearchInput.js.map +1 -0
- package/dist/components/BitkitFilter/components/FilterSelectForm.d.ts +45 -0
- package/dist/components/BitkitFilter/components/FilterSelectForm.js +341 -0
- package/dist/components/BitkitFilter/components/FilterSelectForm.js.map +1 -0
- package/dist/components/BitkitLabelTooltip/BitkitLabelTooltip.d.ts +4 -3
- package/dist/components/BitkitLabelTooltip/BitkitLabelTooltip.js +3 -2
- package/dist/components/BitkitLabelTooltip/BitkitLabelTooltip.js.map +1 -1
- package/dist/components/BitkitMarkdown/BitkitMarkdown.js +1 -1
- package/dist/components/BitkitNoteCard/BitkitNoteCard.js +1 -1
- package/dist/components/BitkitPageSidebar/BitkitPageSidebar.js +1 -1
- package/dist/components/index.d.ts +6 -1
- package/dist/main.js +8 -9
- package/dist/theme/recipes/index.d.ts +525 -0
- package/dist/theme/recipes/index.js +1 -3
- package/dist/theme/recipes/index.js.map +1 -1
- package/dist/theme/semantic-tokens/index.d.ts +0 -5
- package/dist/theme/semantic-tokens/semanticColors.d.ts +0 -5
- package/dist/theme/semantic-tokens/semanticColors.js +0 -1
- package/dist/theme/semantic-tokens/semanticColors.js.map +1 -1
- package/dist/theme/slot-recipes/DatePicker.recipe.d.ts +15 -0
- package/dist/theme/slot-recipes/DatePicker.recipe.js +72 -22
- package/dist/theme/slot-recipes/DatePicker.recipe.js.map +1 -1
- package/dist/theme/slot-recipes/DatePickerSelect.recipe.d.ts +85 -0
- package/dist/theme/slot-recipes/DatePickerSelect.recipe.js +35 -10
- package/dist/theme/slot-recipes/DatePickerSelect.recipe.js.map +1 -1
- package/dist/theme/slot-recipes/Filter.recipe.d.ts +37 -0
- package/dist/theme/slot-recipes/Filter.recipe.js +655 -0
- package/dist/theme/slot-recipes/Filter.recipe.js.map +1 -0
- package/dist/theme/slot-recipes/Listbox.recipe.d.ts +22 -0
- package/dist/theme/slot-recipes/Listbox.recipe.js +96 -0
- package/dist/theme/slot-recipes/Listbox.recipe.js.map +1 -0
- package/dist/theme/slot-recipes/Menu.recipe.js +6 -1
- package/dist/theme/slot-recipes/Menu.recipe.js.map +1 -1
- package/dist/theme/slot-recipes/Tag.recipe.d.ts +1 -1
- package/dist/theme/slot-recipes/UISideNav.recipe.d.ts +2 -1
- package/dist/theme/slot-recipes/UISideNav.recipe.js +3 -2
- package/dist/theme/slot-recipes/UISideNav.recipe.js.map +1 -1
- package/dist/theme/slot-recipes/index.d.ts +160 -3
- package/dist/theme/slot-recipes/index.js +4 -2
- package/dist/theme/slot-recipes/index.js.map +1 -1
- package/dist/theme/themeUtils.d.ts +0 -6
- package/dist/theme/themeUtils.js +1 -7
- package/dist/theme/themeUtils.js.map +1 -1
- package/dist/utilities/BitkitPortalContext.js +3 -2
- package/dist/utilities/BitkitPortalContext.js.map +1 -1
- package/docs/v1-to-v2-migration-guide.md +48 -1
- package/package.json +1 -1
- package/dist/components/BitkitUIHeader/BitkitUIHeader.d.ts +0 -37
- package/dist/components/BitkitUIHeader/BitkitUIHeader.js +0 -113
- package/dist/components/BitkitUIHeader/BitkitUIHeader.js.map +0 -1
- package/dist/components/BitkitUIHeader/BitriseLogo.d.ts +0 -10
- package/dist/components/BitkitUIHeader/BitriseLogo.js +0 -86
- package/dist/components/BitkitUIHeader/BitriseLogo.js.map +0 -1
- package/dist/components/BitkitUIHeader/UIHeaderAccount.d.ts +0 -23
- package/dist/components/BitkitUIHeader/UIHeaderAccount.js +0 -40
- package/dist/components/BitkitUIHeader/UIHeaderAccount.js.map +0 -1
- package/dist/components/BitkitUIHeader/UIHeaderButton.d.ts +0 -32
- package/dist/components/BitkitUIHeader/UIHeaderButton.js +0 -41
- package/dist/components/BitkitUIHeader/UIHeaderButton.js.map +0 -1
- package/dist/components/BitkitUIHeader/UIHeaderContext.d.ts +0 -13
- package/dist/components/BitkitUIHeader/UIHeaderContext.js +0 -11
- package/dist/components/BitkitUIHeader/UIHeaderContext.js.map +0 -1
- package/dist/components/BitkitUIHeader/UIHeaderControl.d.ts +0 -65
- package/dist/components/BitkitUIHeader/UIHeaderControl.js +0 -71
- package/dist/components/BitkitUIHeader/UIHeaderControl.js.map +0 -1
- package/dist/components/BitkitUIHeader/UIHeaderNavButton.d.ts +0 -20
- package/dist/components/BitkitUIHeader/UIHeaderNavButton.js +0 -46
- package/dist/components/BitkitUIHeader/UIHeaderNavButton.js.map +0 -1
- package/dist/components/BitkitUIHeader/UIHeaderSearch.d.ts +0 -32
- package/dist/components/BitkitUIHeader/UIHeaderSearch.js +0 -116
- package/dist/components/BitkitUIHeader/UIHeaderSearch.js.map +0 -1
- package/dist/components/BitkitUIHeader/uiHeaderPart.d.ts +0 -9
- package/dist/components/BitkitUIHeader/uiHeaderPart.js +0 -13
- package/dist/components/BitkitUIHeader/uiHeaderPart.js.map +0 -1
- package/dist/theme/recipes/UIHeaderButton.recipe.js +0 -103
- package/dist/theme/recipes/UIHeaderButton.recipe.js.map +0 -1
- package/dist/theme/slot-recipes/UIHeader.recipe.d.ts +0 -4
- package/dist/theme/slot-recipes/UIHeader.recipe.js +0 -243
- package/dist/theme/slot-recipes/UIHeader.recipe.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitDatePicker.js","names":[],"sources":["../../../lib/components/BitkitDatePicker/BitkitDatePicker.tsx"],"sourcesContent":["import { Box, type BoxProps } from '@chakra-ui/react/box';\nimport { useFieldContext } from '@chakra-ui/react/field';\nimport { useSafeLayoutEffect } from '@chakra-ui/react/hooks';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { type SystemStyleObject, useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { forwardRef, type ReactNode, useContext, useMemo, useRef } from 'react';\n\nimport { DatePicker, type DatePickerValueChangeDetails, useDatePickerContext } from '../../atoms/DatePicker/DatePicker';\nimport IconCalendar from '../../icons/IconCalendar';\nimport IconErrorCircleFilled from '../../icons/IconErrorCircleFilled';\nimport IconWarningYellow from '../../icons/IconWarningYellow';\nimport BitkitPortalContext from '../../utilities/BitkitPortalContext';\nimport { calendarDateToDate, dateToCalendarDate } from '../BitkitCalendar/Calendar.utils';\nimport CalendarContent from '../BitkitCalendar/components/CalendarContent';\nimport BitkitControlButton from '../BitkitControlButton/BitkitControlButton';\nimport BitkitField, { type BitkitFieldProps } from '../BitkitField/BitkitField';\n\nexport type BitkitDatePickerControlProps = BoxProps;\n\nexport interface BitkitDatePickerProps extends Omit<BitkitFieldProps, 'children' | 'defaultValue' | 'state' | 'value'> {\n controlProps?: BitkitDatePickerControlProps;\n defaultValue?: Date | null;\n disablePortal?: boolean;\n max?: Date;\n min?: Date;\n onValueChange?: (value: Date | null) => void;\n placeholder?: string;\n showOutsideDays?: boolean;\n size?: 'md' | 'lg';\n state?: 'disabled' | 'error' | 'readOnly' | 'warning';\n value?: Date | null;\n}\n\n/**\n * Ark offers `openOnClick` but nothing for focus, so the calendar is opened from the input's own\n * focus event — that covers tabbing in as well as clicking.\n */\nconst DatePickerInput = ({ css }: { css: SystemStyleObject }) => {\n const { disabled, open, readOnly, setOpen } = useDatePickerContext();\n // Ark's DatePicker.Input has no notion of Chakra's Field — without this, `errorText`/`helperText`\n // render visually but are never announced, since nothing points the input's aria-describedby at them.\n const ariaDescribedby = useFieldContext()?.ariaDescribedby;\n const reopenBlockedRef = useRef(false);\n const wasOpenRef = useRef(false);\n\n // Zag's `focusInputElement` puts focus back on the input a frame after the calendar closes on\n // selection, which would bounce it straight back open. Ignore focus that lands right after a\n // close — this has to be a layout effect so the flag is set before Zag's rAF runs. Isomorphic\n // layout effect: avoids the SSR \"useLayoutEffect does nothing on the server\" warning.\n useSafeLayoutEffect(() => {\n const justClosed = wasOpenRef.current && !open;\n wasOpenRef.current = open;\n\n // Only a real close can trigger Zag's refocus, so mount (never-opened) must not arm the block\n // — otherwise an autoFocus or a programmatic focus right after mount gets swallowed.\n if (!justClosed) {\n return undefined;\n }\n\n reopenBlockedRef.current = true;\n const timeout = setTimeout(() => {\n reopenBlockedRef.current = false;\n }, 100);\n\n return () => clearTimeout(timeout);\n }, [open]);\n\n // Ark gates its own trigger handlers on the machine's interactive state, but `setOpen` is not —\n // it would happily open the calendar on a read-only or disabled field. A disabled input still\n // needs the check: Chrome fires focus events for a programmatic `.focus()` on one, even though\n // focus never actually lands there.\n const handleFocus = () => {\n if (!reopenBlockedRef.current && !readOnly && !disabled) {\n setOpen(true);\n }\n };\n\n return <DatePicker.Input aria-describedby={ariaDescribedby} css={css} index={0} onFocus={handleFocus} unstyled />;\n};\n\nconst BitkitDatePicker = forwardRef<HTMLDivElement, BitkitDatePickerProps>((props, ref) => {\n const {\n controlProps,\n defaultValue,\n disablePortal,\n max,\n min,\n onValueChange,\n placeholder,\n showOutsideDays,\n size = 'lg',\n state,\n value,\n ...fieldProps\n } = props;\n\n const recipe = useSlotRecipe({ key: 'textInput' });\n const styles = recipe({ size });\n\n const { disablePortal: disablePortalFromContext } = useContext(BitkitPortalContext);\n\n const minCalendarDate = useMemo(() => (min ? dateToCalendarDate(min) : undefined), [min]);\n const maxCalendarDate = useMemo(() => (max ? dateToCalendarDate(max) : undefined), [max]);\n\n const iconSize = size === 'md' ? '16' : '24';\n const hasWarning = state === 'warning' || !!fieldProps.warningText;\n const isInvalid = state === 'error' || !!fieldProps.errorText;\n\n let statusIcon: ReactNode = null;\n if (isInvalid) {\n statusIcon = <IconErrorCircleFilled color=\"icon/negative\" size={iconSize} />;\n } else if (hasWarning) {\n statusIcon = <IconWarningYellow size={iconSize} />;\n }\n\n const stateAttrs = {\n 'data-disabled': state === 'disabled' ? '' : undefined,\n 'data-invalid': isInvalid ? '' : undefined,\n 'data-readonly': state === 'readOnly' ? '' : undefined,\n };\n\n const isInteractive = state !== 'disabled' && state !== 'readOnly';\n\n const calendarButton = (\n <BitkitControlButton\n icon={IconCalendar}\n label=\"Open calendar\"\n // Pulled out by its own padding so the glyph lands where a plain end icon would sit in\n // BitkitTextInput — 16px (lg) / 12px (md) from the field's outer edge.\n marginInlineEnd={size === 'md' ? '-8' : '-4'}\n size={size === 'md' ? 'xs' : 'sm'}\n state={isInteractive ? undefined : 'disabled'}\n />\n );\n\n return (\n <BitkitField ref={ref} state={state} {...fieldProps}>\n <DatePicker.Root\n defaultValue={defaultValue ? [dateToCalendarDate(defaultValue)] : undefined}\n device=\"desktop\"\n disabled={state === 'disabled'}\n invalid={isInvalid}\n layout=\"1-month\"\n max={maxCalendarDate}\n min={minCalendarDate}\n numOfMonths={1}\n onValueChange={\n onValueChange\n ? (details: DatePickerValueChangeDetails) =>\n onValueChange(details.value?.[0] ? calendarDateToDate(details.value[0]) : null)\n : undefined\n }\n placeholder={state === 'readOnly' ? '(not set)' : placeholder}\n positioning={{ placement: 'bottom-start' }}\n readOnly={state === 'readOnly'}\n selectionMode=\"single\"\n showOutsideDays={showOutsideDays ?? true}\n startOfWeek={1}\n // Stay controlled whenever `value` is provided — even for `null` (\"nothing selected\").\n // Passing `undefined` would flip Root to uncontrolled. See BitkitCombobox.\n value={value === undefined ? undefined : value ? [dateToCalendarDate(value)] : []}\n >\n <DatePicker.Control asChild>\n <Box {...controlProps} css={[styles.field, controlProps?.css]} {...stateAttrs}>\n <DatePickerInput css={styles.input} />\n {statusIcon != null && (\n <Box css={styles.element} {...stateAttrs}>\n {statusIcon}\n </Box>\n )}\n {/* Ark keeps the trigger interactive in read-only mode and forces `disabled={false}`\n onto whatever it wraps, so the button is left unwrapped there to stay inert. */}\n {state === 'readOnly' ? calendarButton : <DatePicker.Trigger asChild>{calendarButton}</DatePicker.Trigger>}\n </Box>\n </DatePicker.Control>\n <Portal disabled={disablePortal || disablePortalFromContext}>\n <DatePicker.Positioner>\n <CalendarContent layout=\"1-month\" max={maxCalendarDate} min={minCalendarDate} selectionMode=\"day\" />\n </DatePicker.Positioner>\n </Portal>\n </DatePicker.Root>\n </BitkitField>\n );\n});\n\nBitkitDatePicker.displayName = 'BitkitDatePicker';\n\nexport default BitkitDatePicker;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BitkitDatePicker.js","names":[],"sources":["../../../lib/components/BitkitDatePicker/BitkitDatePicker.tsx"],"sourcesContent":["import { Box, type BoxProps } from '@chakra-ui/react/box';\nimport { useFieldContext } from '@chakra-ui/react/field';\nimport { useSafeLayoutEffect } from '@chakra-ui/react/hooks';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { type SystemStyleObject, useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { forwardRef, type ReactNode, useContext, useMemo, useRef } from 'react';\n\nimport { DatePicker, type DatePickerValueChangeDetails, useDatePickerContext } from '../../atoms/DatePicker/DatePicker';\nimport IconCalendar from '../../icons/IconCalendar';\nimport IconErrorCircleFilled from '../../icons/IconErrorCircleFilled';\nimport IconWarningYellow from '../../icons/IconWarningYellow';\nimport BitkitPortalContext from '../../utilities/BitkitPortalContext';\nimport { calendarDateToDate, dateToCalendarDate } from '../BitkitCalendar/Calendar.utils';\nimport CalendarContent from '../BitkitCalendar/components/CalendarContent';\nimport BitkitControlButton from '../BitkitControlButton/BitkitControlButton';\nimport BitkitField, { type BitkitFieldProps } from '../BitkitField/BitkitField';\n\nexport type BitkitDatePickerControlProps = BoxProps;\n\nexport interface BitkitDatePickerProps extends Omit<BitkitFieldProps, 'children' | 'defaultValue' | 'state' | 'value'> {\n controlProps?: BitkitDatePickerControlProps;\n /** Text for a day's tooltip, e.g. why it can't be picked. Return `undefined` for no tooltip. */\n dayTooltip?: (date: Date) => string | undefined;\n defaultValue?: Date | null;\n disablePortal?: boolean;\n max?: Date;\n min?: Date;\n onValueChange?: (value: Date | null) => void;\n placeholder?: string;\n showOutsideDays?: boolean;\n size?: 'md' | 'lg';\n state?: 'disabled' | 'error' | 'readOnly' | 'warning';\n value?: Date | null;\n}\n\n/**\n * Ark offers `openOnClick` but nothing for focus, so the calendar is opened from the input's own\n * focus event — that covers tabbing in as well as clicking.\n */\nconst DatePickerInput = ({ css }: { css: SystemStyleObject }) => {\n const { disabled, open, readOnly, setOpen } = useDatePickerContext();\n // Ark's DatePicker.Input has no notion of Chakra's Field — without this, `errorText`/`helperText`\n // render visually but are never announced, since nothing points the input's aria-describedby at them.\n const ariaDescribedby = useFieldContext()?.ariaDescribedby;\n const reopenBlockedRef = useRef(false);\n const wasOpenRef = useRef(false);\n\n // Zag's `focusInputElement` puts focus back on the input a frame after the calendar closes on\n // selection, which would bounce it straight back open. Ignore focus that lands right after a\n // close — this has to be a layout effect so the flag is set before Zag's rAF runs. Isomorphic\n // layout effect: avoids the SSR \"useLayoutEffect does nothing on the server\" warning.\n useSafeLayoutEffect(() => {\n const justClosed = wasOpenRef.current && !open;\n wasOpenRef.current = open;\n\n // Only a real close can trigger Zag's refocus, so mount (never-opened) must not arm the block\n // — otherwise an autoFocus or a programmatic focus right after mount gets swallowed.\n if (!justClosed) {\n return undefined;\n }\n\n reopenBlockedRef.current = true;\n const timeout = setTimeout(() => {\n reopenBlockedRef.current = false;\n }, 100);\n\n return () => clearTimeout(timeout);\n }, [open]);\n\n // Ark gates its own trigger handlers on the machine's interactive state, but `setOpen` is not —\n // it would happily open the calendar on a read-only or disabled field. A disabled input still\n // needs the check: Chrome fires focus events for a programmatic `.focus()` on one, even though\n // focus never actually lands there.\n const handleFocus = () => {\n if (!reopenBlockedRef.current && !readOnly && !disabled) {\n setOpen(true);\n }\n };\n\n return <DatePicker.Input aria-describedby={ariaDescribedby} css={css} index={0} onFocus={handleFocus} unstyled />;\n};\n\nconst BitkitDatePicker = forwardRef<HTMLDivElement, BitkitDatePickerProps>((props, ref) => {\n const {\n controlProps,\n dayTooltip,\n defaultValue,\n disablePortal,\n max,\n min,\n onValueChange,\n placeholder,\n showOutsideDays,\n size = 'lg',\n state,\n value,\n ...fieldProps\n } = props;\n\n const recipe = useSlotRecipe({ key: 'textInput' });\n const styles = recipe({ size });\n\n const { disablePortal: disablePortalFromContext } = useContext(BitkitPortalContext);\n\n const minCalendarDate = useMemo(() => (min ? dateToCalendarDate(min) : undefined), [min]);\n const maxCalendarDate = useMemo(() => (max ? dateToCalendarDate(max) : undefined), [max]);\n\n const iconSize = size === 'md' ? '16' : '24';\n const hasWarning = state === 'warning' || !!fieldProps.warningText;\n const isInvalid = state === 'error' || !!fieldProps.errorText;\n\n let statusIcon: ReactNode = null;\n if (isInvalid) {\n statusIcon = <IconErrorCircleFilled color=\"icon/negative\" size={iconSize} />;\n } else if (hasWarning) {\n statusIcon = <IconWarningYellow size={iconSize} />;\n }\n\n const stateAttrs = {\n 'data-disabled': state === 'disabled' ? '' : undefined,\n 'data-invalid': isInvalid ? '' : undefined,\n 'data-readonly': state === 'readOnly' ? '' : undefined,\n };\n\n const isInteractive = state !== 'disabled' && state !== 'readOnly';\n\n const calendarButton = (\n <BitkitControlButton\n icon={IconCalendar}\n label=\"Open calendar\"\n // Pulled out by its own padding so the glyph lands where a plain end icon would sit in\n // BitkitTextInput — 16px (lg) / 12px (md) from the field's outer edge.\n marginInlineEnd={size === 'md' ? '-8' : '-4'}\n size={size === 'md' ? 'xs' : 'sm'}\n state={isInteractive ? undefined : 'disabled'}\n />\n );\n\n return (\n <BitkitField ref={ref} state={state} {...fieldProps}>\n <DatePicker.Root\n defaultValue={defaultValue ? [dateToCalendarDate(defaultValue)] : undefined}\n device=\"desktop\"\n disabled={state === 'disabled'}\n invalid={isInvalid}\n layout=\"1-month\"\n max={maxCalendarDate}\n min={minCalendarDate}\n numOfMonths={1}\n onValueChange={\n onValueChange\n ? (details: DatePickerValueChangeDetails) =>\n onValueChange(details.value?.[0] ? calendarDateToDate(details.value[0]) : null)\n : undefined\n }\n placeholder={state === 'readOnly' ? '(not set)' : placeholder}\n positioning={{ placement: 'bottom-start' }}\n readOnly={state === 'readOnly'}\n selectionMode=\"single\"\n showOutsideDays={showOutsideDays ?? true}\n startOfWeek={1}\n // Stay controlled whenever `value` is provided — even for `null` (\"nothing selected\").\n // Passing `undefined` would flip Root to uncontrolled. See BitkitCombobox.\n value={value === undefined ? undefined : value ? [dateToCalendarDate(value)] : []}\n >\n <DatePicker.Control asChild>\n <Box {...controlProps} css={[styles.field, controlProps?.css]} {...stateAttrs}>\n <DatePickerInput css={styles.input} />\n {statusIcon != null && (\n <Box css={styles.element} {...stateAttrs}>\n {statusIcon}\n </Box>\n )}\n {/* Ark keeps the trigger interactive in read-only mode and forces `disabled={false}`\n onto whatever it wraps, so the button is left unwrapped there to stay inert. */}\n {state === 'readOnly' ? calendarButton : <DatePicker.Trigger asChild>{calendarButton}</DatePicker.Trigger>}\n </Box>\n </DatePicker.Control>\n <Portal disabled={disablePortal || disablePortalFromContext}>\n <DatePicker.Positioner>\n <CalendarContent\n dayTooltip={dayTooltip}\n layout=\"1-month\"\n max={maxCalendarDate}\n min={minCalendarDate}\n selectionMode=\"day\"\n />\n </DatePicker.Positioner>\n </Portal>\n </DatePicker.Root>\n </BitkitField>\n );\n});\n\nBitkitDatePicker.displayName = 'BitkitDatePicker';\n\nexport default BitkitDatePicker;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAuCA,IAAM,mBAAmB,EAAE,UAAsC;CAC/D,MAAM,EAAE,UAAU,MAAM,UAAU,YAAY,qBAAqB;CAGnE,MAAM,kBAAkB,gBAAgB,CAAC,EAAE;CAC3C,MAAM,mBAAmB,OAAO,KAAK;CACrC,MAAM,aAAa,OAAO,KAAK;CAM/B,0BAA0B;EACxB,MAAM,aAAa,WAAW,WAAW,CAAC;EAC1C,WAAW,UAAU;EAIrB,IAAI,CAAC,YACH;EAGF,iBAAiB,UAAU;EAC3B,MAAM,UAAU,iBAAiB;GAC/B,iBAAiB,UAAU;EAC7B,GAAG,GAAG;EAEN,aAAa,aAAa,OAAO;CACnC,GAAG,CAAC,IAAI,CAAC;CAMT,MAAM,oBAAoB;EACxB,IAAI,CAAC,iBAAiB,WAAW,CAAC,YAAY,CAAC,UAC7C,QAAQ,IAAI;CAEhB;CAEA,OAAO,oBAAC,WAAW,OAAZ;EAAkB,oBAAkB;EAAsB;EAAK,OAAO;EAAG,SAAS;EAAa,UAAA;CAAU,CAAA;AAClH;AAEA,IAAM,mBAAmB,YAAmD,OAAO,QAAQ;CACzF,MAAM,EACJ,cACA,YACA,cACA,eACA,KACA,KACA,eACA,aACA,iBACA,OAAO,MACP,OACA,OACA,GAAG,eACD;CAGJ,MAAM,SADS,cAAc,EAAE,KAAK,YAAY,CACjC,CAAA,CAAO,EAAE,KAAK,CAAC;CAE9B,MAAM,EAAE,eAAe,6BAA6B,WAAW,mBAAmB;CAElF,MAAM,kBAAkB,cAAe,MAAM,mBAAmB,GAAG,IAAI,KAAA,GAAY,CAAC,GAAG,CAAC;CACxF,MAAM,kBAAkB,cAAe,MAAM,mBAAmB,GAAG,IAAI,KAAA,GAAY,CAAC,GAAG,CAAC;CAExF,MAAM,WAAW,SAAS,OAAO,OAAO;CACxC,MAAM,aAAa,UAAU,aAAa,CAAC,CAAC,WAAW;CACvD,MAAM,YAAY,UAAU,WAAW,CAAC,CAAC,WAAW;CAEpD,IAAI,aAAwB;CAC5B,IAAI,WACF,aAAa,oBAAC,uBAAD;EAAuB,OAAM;EAAgB,MAAM;CAAW,CAAA;MACtE,IAAI,YACT,aAAa,oBAAC,mBAAD,EAAmB,MAAM,SAAW,CAAA;CAGnD,MAAM,aAAa;EACjB,iBAAiB,UAAU,aAAa,KAAK,KAAA;EAC7C,gBAAgB,YAAY,KAAK,KAAA;EACjC,iBAAiB,UAAU,aAAa,KAAK,KAAA;CAC/C;CAIA,MAAM,iBACJ,oBAAC,qBAAD;EACE,MAAM;EACN,OAAM;EAGN,iBAAiB,SAAS,OAAO,OAAO;EACxC,MAAM,SAAS,OAAO,OAAO;EAC7B,OAVkB,UAAU,cAAc,UAAU,aAU7B,KAAA,IAAY;CACpC,CAAA;CAGH,OACE,oBAAC,aAAD;EAAkB;EAAY;EAAO,GAAI;EACvC,UAAA,qBAAC,WAAW,MAAZ;GACE,cAAc,eAAe,CAAC,mBAAmB,YAAY,CAAC,IAAI,KAAA;GAClE,QAAO;GACP,UAAU,UAAU;GACpB,SAAS;GACT,QAAO;GACP,KAAK;GACL,KAAK;GACL,aAAa;GACb,eACE,iBACK,YACC,cAAc,QAAQ,QAAQ,KAAK,mBAAmB,QAAQ,MAAM,EAAE,IAAI,IAAI,IAChF,KAAA;GAEN,aAAa,UAAU,aAAa,cAAc;GAClD,aAAa,EAAE,WAAW,eAAe;GACzC,UAAU,UAAU;GACpB,eAAc;GACd,iBAAiB,mBAAmB;GACpC,aAAa;GAGb,OAAO,UAAU,KAAA,IAAY,KAAA,IAAY,QAAQ,CAAC,mBAAmB,KAAK,CAAC,IAAI,CAAC;GAvBlF,UAAA,CAyBE,oBAAC,WAAW,SAAZ;IAAoB,SAAA;IAClB,UAAA,qBAAC,KAAD;KAAK,GAAI;KAAc,KAAK,CAAC,OAAO,OAAO,cAAc,GAAG;KAAG,GAAI;KAAnE,UAAA;MACE,oBAAC,iBAAD,EAAiB,KAAK,OAAO,MAAQ,CAAA;MACpC,cAAc,QACb,oBAAC,KAAD;OAAK,KAAK,OAAO;OAAS,GAAI;OAC3B,UAAA;MACE,CAAA;MAIN,UAAU,aAAa,iBAAiB,oBAAC,WAAW,SAAZ;OAAoB,SAAA;OAAS,UAAA;MAAmC,CAAA;KACtG;;GACa,CAAA,GACpB,oBAAC,QAAD;IAAQ,UAAU,iBAAiB;IACjC,UAAA,oBAAC,WAAW,YAAZ,EAAA,UACE,oBAAC,iBAAD;KACc;KACZ,QAAO;KACP,KAAK;KACL,KAAK;KACL,eAAc;IACf,CAAA,EACoB,CAAA;GACjB,CAAA,CACO;;CACN,CAAA;AAEjB,CAAC;AAED,iBAAiB,cAAc"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { SystemStyleObject } from '@chakra-ui/react/styled-system';
|
|
2
|
+
import { FilterValue } from './BitkitFilter.utils';
|
|
3
|
+
export interface FilterStatus {
|
|
4
|
+
/** Holds a selection — counted on the mobile "Filters" button. */
|
|
5
|
+
isActive: boolean;
|
|
6
|
+
/** Differs from its `resetValue` — shows "Clear filters". */
|
|
7
|
+
isDirty: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface FilterContext {
|
|
10
|
+
/** The page is loading: the filters can't be opened or cleared. */
|
|
11
|
+
isDisabled: boolean;
|
|
12
|
+
/** Below the tablet breakpoint the filters render as fields in a full-screen dialog. */
|
|
13
|
+
isMobile: boolean;
|
|
14
|
+
register: (id: string, reset: () => void) => void;
|
|
15
|
+
setStatus: (id: string, status: FilterStatus) => void;
|
|
16
|
+
styles: Record<string, SystemStyleObject>;
|
|
17
|
+
unregister: (id: string) => void;
|
|
18
|
+
}
|
|
19
|
+
export declare const FilterContextProvider: import('react').Provider<FilterContext>, useFilterContext: () => FilterContext;
|
|
20
|
+
/** What a filter inside `BitkitFilter.Add` tells it, to list itself in the "Add filter" menu. */
|
|
21
|
+
export interface FilterAddEntry {
|
|
22
|
+
disabledReason?: string;
|
|
23
|
+
/** Has options to pick, or a search to find them. */
|
|
24
|
+
hasOptions: boolean;
|
|
25
|
+
/** Holds no value, so it is offered in the menu rather than shown as a chip. */
|
|
26
|
+
isEmpty: boolean;
|
|
27
|
+
label: string;
|
|
28
|
+
}
|
|
29
|
+
interface FilterAddContext {
|
|
30
|
+
/** The filter picked from the menu, whose panel is open. */
|
|
31
|
+
addingId?: string;
|
|
32
|
+
/** Moves focus to the "Add filter" button once it is enabled, e.g. after a chip is removed. */
|
|
33
|
+
focusTrigger: () => void;
|
|
34
|
+
/** The "Add filter" button: the panels anchor to it and hand focus back to it. */
|
|
35
|
+
getTrigger: () => HTMLElement | null;
|
|
36
|
+
register: (id: string, entry: FilterAddEntry) => void;
|
|
37
|
+
stopAdding: () => void;
|
|
38
|
+
unregister: (id: string) => void;
|
|
39
|
+
}
|
|
40
|
+
/** Set by `BitkitFilter.Add` around its filters: they render as removable chips, only while they hold a value. */
|
|
41
|
+
export declare const FilterAddContextProvider: import('react').Provider<FilterAddContext | undefined>, useFilterAddContext: () => FilterAddContext | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Lists a filter in its `BitkitFilter.Add` menu, wherever it sits in the tree, so a filter wrapped in
|
|
44
|
+
* a consumer component is listed too. Returns nothing outside `Add`.
|
|
45
|
+
*/
|
|
46
|
+
export declare const useFilterAddItem: ({ disabledReason, hasOptions, isEmpty, label }: FilterAddEntry) => {
|
|
47
|
+
isAdding: boolean;
|
|
48
|
+
/** The filter picked from the menu, whose panel is open. */
|
|
49
|
+
addingId?: string;
|
|
50
|
+
/** Moves focus to the "Add filter" button once it is enabled, e.g. after a chip is removed. */
|
|
51
|
+
focusTrigger: () => void;
|
|
52
|
+
/** The "Add filter" button: the panels anchor to it and hand focus back to it. */
|
|
53
|
+
getTrigger: () => HTMLElement | null;
|
|
54
|
+
register: (id: string, entry: FilterAddEntry) => void;
|
|
55
|
+
stopAdding: () => void;
|
|
56
|
+
unregister: (id: string) => void;
|
|
57
|
+
} | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Registers a filter with the root, which shows "Clear filters" while any filter differs from its
|
|
60
|
+
* `resetValue` and hands each one its `resetValue` back when clicked, and counts the active ones.
|
|
61
|
+
*/
|
|
62
|
+
export declare const useFilterRegistration: <T extends FilterValue>(value: T, resetValue: T, onValueChange: (value: T) => void,
|
|
63
|
+
/** Off for the search, which sits outside the mobile dialog the count describes. */
|
|
64
|
+
isCounted?: boolean) => void;
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { isEmptyFilterValue, isEqualFilterValue } from "./BitkitFilter.utils.js";
|
|
2
|
+
import { useEffect, useId, useRef } from "react";
|
|
3
|
+
import { createContext as createContext$1 } from "@chakra-ui/react";
|
|
4
|
+
//#region lib/components/BitkitFilter/BitkitFilter.context.ts
|
|
5
|
+
var [FilterContextProvider, useFilterContext] = createContext$1({
|
|
6
|
+
hookName: "useFilterContext",
|
|
7
|
+
name: "FilterContext",
|
|
8
|
+
providerName: "<BitkitFilter />"
|
|
9
|
+
});
|
|
10
|
+
/** Set by `BitkitFilter.Add` around its filters: they render as removable chips, only while they hold a value. */
|
|
11
|
+
var [FilterAddContextProvider, useFilterAddContext] = createContext$1({
|
|
12
|
+
name: "FilterAddContext",
|
|
13
|
+
strict: false
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Lists a filter in its `BitkitFilter.Add` menu, wherever it sits in the tree, so a filter wrapped in
|
|
17
|
+
* a consumer component is listed too. Returns nothing outside `Add`.
|
|
18
|
+
*/
|
|
19
|
+
var useFilterAddItem = ({ disabledReason, hasOptions, isEmpty, label }) => {
|
|
20
|
+
const add = useFilterAddContext();
|
|
21
|
+
const id = useId();
|
|
22
|
+
const register = add?.register;
|
|
23
|
+
const unregister = add?.unregister;
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
register?.(id, {
|
|
26
|
+
disabledReason,
|
|
27
|
+
hasOptions,
|
|
28
|
+
isEmpty,
|
|
29
|
+
label
|
|
30
|
+
});
|
|
31
|
+
}, [
|
|
32
|
+
disabledReason,
|
|
33
|
+
hasOptions,
|
|
34
|
+
id,
|
|
35
|
+
isEmpty,
|
|
36
|
+
label,
|
|
37
|
+
register
|
|
38
|
+
]);
|
|
39
|
+
useEffect(() => () => unregister?.(id), [id, unregister]);
|
|
40
|
+
if (!add) return void 0;
|
|
41
|
+
return {
|
|
42
|
+
...add,
|
|
43
|
+
isAdding: add.addingId === id
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Registers a filter with the root, which shows "Clear filters" while any filter differs from its
|
|
48
|
+
* `resetValue` and hands each one its `resetValue` back when clicked, and counts the active ones.
|
|
49
|
+
*/
|
|
50
|
+
var useFilterRegistration = (value, resetValue, onValueChange, isCounted = true) => {
|
|
51
|
+
const { register, setStatus, unregister } = useFilterContext();
|
|
52
|
+
const id = useId();
|
|
53
|
+
const resetRef = useRef(() => onValueChange(resetValue));
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
resetRef.current = () => onValueChange(resetValue);
|
|
56
|
+
});
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
register(id, () => resetRef.current());
|
|
59
|
+
return () => unregister(id);
|
|
60
|
+
}, [
|
|
61
|
+
id,
|
|
62
|
+
register,
|
|
63
|
+
unregister
|
|
64
|
+
]);
|
|
65
|
+
const isDirty = !isEqualFilterValue(value, resetValue);
|
|
66
|
+
const isActive = isCounted && !isEmptyFilterValue(value);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
setStatus(id, {
|
|
69
|
+
isActive,
|
|
70
|
+
isDirty
|
|
71
|
+
});
|
|
72
|
+
}, [
|
|
73
|
+
id,
|
|
74
|
+
isActive,
|
|
75
|
+
isDirty,
|
|
76
|
+
setStatus
|
|
77
|
+
]);
|
|
78
|
+
};
|
|
79
|
+
//#endregion
|
|
80
|
+
export { FilterAddContextProvider, FilterContextProvider, useFilterAddContext, useFilterAddItem, useFilterContext, useFilterRegistration };
|
|
81
|
+
|
|
82
|
+
//# sourceMappingURL=BitkitFilter.context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BitkitFilter.context.js","names":["createContext"],"sources":["../../../lib/components/BitkitFilter/BitkitFilter.context.ts"],"sourcesContent":["// eslint-disable-next-line no-restricted-imports\nimport { createContext } from '@chakra-ui/react';\nimport { type SystemStyleObject } from '@chakra-ui/react/styled-system';\nimport { useEffect, useId, useRef } from 'react';\n\nimport { type FilterValue, isEmptyFilterValue, isEqualFilterValue } from './BitkitFilter.utils';\n\nexport interface FilterStatus {\n /** Holds a selection — counted on the mobile \"Filters\" button. */\n isActive: boolean;\n /** Differs from its `resetValue` — shows \"Clear filters\". */\n isDirty: boolean;\n}\n\ninterface FilterContext {\n /** The page is loading: the filters can't be opened or cleared. */\n isDisabled: boolean;\n /** Below the tablet breakpoint the filters render as fields in a full-screen dialog. */\n isMobile: boolean;\n register: (id: string, reset: () => void) => void;\n setStatus: (id: string, status: FilterStatus) => void;\n styles: Record<string, SystemStyleObject>;\n unregister: (id: string) => void;\n}\n\nexport const [FilterContextProvider, useFilterContext] = createContext<FilterContext>({\n hookName: 'useFilterContext',\n name: 'FilterContext',\n providerName: '<BitkitFilter />',\n});\n\n/** What a filter inside `BitkitFilter.Add` tells it, to list itself in the \"Add filter\" menu. */\nexport interface FilterAddEntry {\n disabledReason?: string;\n /** Has options to pick, or a search to find them. */\n hasOptions: boolean;\n /** Holds no value, so it is offered in the menu rather than shown as a chip. */\n isEmpty: boolean;\n label: string;\n}\n\ninterface FilterAddContext {\n /** The filter picked from the menu, whose panel is open. */\n addingId?: string;\n /** Moves focus to the \"Add filter\" button once it is enabled, e.g. after a chip is removed. */\n focusTrigger: () => void;\n /** The \"Add filter\" button: the panels anchor to it and hand focus back to it. */\n getTrigger: () => HTMLElement | null;\n register: (id: string, entry: FilterAddEntry) => void;\n stopAdding: () => void;\n unregister: (id: string) => void;\n}\n\n/** Set by `BitkitFilter.Add` around its filters: they render as removable chips, only while they hold a value. */\nexport const [FilterAddContextProvider, useFilterAddContext] = createContext<FilterAddContext | undefined>({\n name: 'FilterAddContext',\n strict: false,\n});\n\n/**\n * Lists a filter in its `BitkitFilter.Add` menu, wherever it sits in the tree, so a filter wrapped in\n * a consumer component is listed too. Returns nothing outside `Add`.\n */\nexport const useFilterAddItem = ({ disabledReason, hasOptions, isEmpty, label }: FilterAddEntry) => {\n const add = useFilterAddContext();\n const id = useId();\n const register = add?.register;\n const unregister = add?.unregister;\n\n useEffect(() => {\n register?.(id, { disabledReason, hasOptions, isEmpty, label });\n }, [disabledReason, hasOptions, id, isEmpty, label, register]);\n\n useEffect(() => () => unregister?.(id), [id, unregister]);\n\n if (!add) return undefined;\n return { ...add, isAdding: add.addingId === id };\n};\n\n/**\n * Registers a filter with the root, which shows \"Clear filters\" while any filter differs from its\n * `resetValue` and hands each one its `resetValue` back when clicked, and counts the active ones.\n */\nexport const useFilterRegistration = <T extends FilterValue>(\n value: T,\n resetValue: T,\n onValueChange: (value: T) => void,\n /** Off for the search, which sits outside the mobile dialog the count describes. */\n isCounted = true,\n) => {\n const { register, setStatus, unregister } = useFilterContext();\n const id = useId();\n const resetRef = useRef(() => onValueChange(resetValue));\n useEffect(() => {\n resetRef.current = () => onValueChange(resetValue);\n });\n\n useEffect(() => {\n register(id, () => resetRef.current());\n return () => unregister(id);\n }, [id, register, unregister]);\n\n const isDirty = !isEqualFilterValue(value, resetValue);\n const isActive = isCounted && !isEmptyFilterValue(value);\n useEffect(() => {\n setStatus(id, { isActive, isDirty });\n }, [id, isActive, isDirty, setStatus]);\n};\n"],"mappings":";;;;AAyBA,IAAa,CAAC,uBAAuB,oBAAoBA,gBAA6B;CACpF,UAAU;CACV,MAAM;CACN,cAAc;AAChB,CAAC;;AAyBD,IAAa,CAAC,0BAA0B,uBAAuBA,gBAA4C;CACzG,MAAM;CACN,QAAQ;AACV,CAAC;;;;;AAMD,IAAa,oBAAoB,EAAE,gBAAgB,YAAY,SAAS,YAA4B;CAClG,MAAM,MAAM,oBAAoB;CAChC,MAAM,KAAK,MAAM;CACjB,MAAM,WAAW,KAAK;CACtB,MAAM,aAAa,KAAK;CAExB,gBAAgB;EACd,WAAW,IAAI;GAAE;GAAgB;GAAY;GAAS;EAAM,CAAC;CAC/D,GAAG;EAAC;EAAgB;EAAY;EAAI;EAAS;EAAO;CAAQ,CAAC;CAE7D,sBAAsB,aAAa,EAAE,GAAG,CAAC,IAAI,UAAU,CAAC;CAExD,IAAI,CAAC,KAAK,OAAO,KAAA;CACjB,OAAO;EAAE,GAAG;EAAK,UAAU,IAAI,aAAa;CAAG;AACjD;;;;;AAMA,IAAa,yBACX,OACA,YACA,eAEA,YAAY,SACT;CACH,MAAM,EAAE,UAAU,WAAW,eAAe,iBAAiB;CAC7D,MAAM,KAAK,MAAM;CACjB,MAAM,WAAW,aAAa,cAAc,UAAU,CAAC;CACvD,gBAAgB;EACd,SAAS,gBAAgB,cAAc,UAAU;CACnD,CAAC;CAED,gBAAgB;EACd,SAAS,UAAU,SAAS,QAAQ,CAAC;EACrC,aAAa,WAAW,EAAE;CAC5B,GAAG;EAAC;EAAI;EAAU;CAAU,CAAC;CAE7B,MAAM,UAAU,CAAC,mBAAmB,OAAO,UAAU;CACrD,MAAM,WAAW,aAAa,CAAC,mBAAmB,KAAK;CACvD,gBAAgB;EACd,UAAU,IAAI;GAAE;GAAU;EAAQ,CAAC;CACrC,GAAG;EAAC;EAAI;EAAU;EAAS;CAAS,CAAC;AACvC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { BoxProps } from '@chakra-ui/react/box';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
export interface BitkitFilterProps extends Omit<BoxProps, 'children'> {
|
|
4
|
+
/**
|
|
5
|
+
* The filters: `BitkitFilter.Switch`, `.Select`, `.DateRange`, `.Add` and `.Search`, in the
|
|
6
|
+
* order they appear. `.Search` always sits at the end of the bar.
|
|
7
|
+
*/
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* Where a capped content block sits: `center` over a centred page column, `start` over one that
|
|
11
|
+
* starts against a sidebar. Pass the same alignment as the page header and content below it.
|
|
12
|
+
* Only applies with a `contentWidth` in rems. Desktop only.
|
|
13
|
+
*/
|
|
14
|
+
contentAlign?: 'center' | 'start';
|
|
15
|
+
/**
|
|
16
|
+
* Lines the bar up with the page content below it, for a bar under a page header: it gains a
|
|
17
|
+
* bottom border and the page's inline padding. Pass the content column's `max-width` in rems to
|
|
18
|
+
* cap the bar's content with it (padded 32 on both sides), or `'full'` for a full-width column
|
|
19
|
+
* (24 at the start, like the default `BitkitPageHeader`, and 32 at the end).
|
|
20
|
+
*
|
|
21
|
+
* Without it the bar has the tighter inset of a filter above a table. Desktop only.
|
|
22
|
+
*/
|
|
23
|
+
contentWidth?: `${number}rem` | 'full';
|
|
24
|
+
/**
|
|
25
|
+
* Forces the desktop bar or the mobile "Filters" dialog. Defaults to the viewport: mobile below the
|
|
26
|
+
* tablet breakpoint. The mobile dialog is made for narrow screens; from tablet up it is a 320px drawer.
|
|
27
|
+
*/
|
|
28
|
+
device?: 'desktop' | 'mobile';
|
|
29
|
+
/**
|
|
30
|
+
* Called once on "Clear filters" in place of handing each changed filter its `resetValue` — for a
|
|
31
|
+
* page that resets them all in one write, e.g. one URL update. The `resetValue`s still decide when
|
|
32
|
+
* the button shows.
|
|
33
|
+
*/
|
|
34
|
+
onClear?: () => void;
|
|
35
|
+
/**
|
|
36
|
+
* Shows "Clear filters" while any filter differs from its `resetValue`. It hands each of those
|
|
37
|
+
* filters its `resetValue` back through its own `onValueChange`, or calls `onClear`.
|
|
38
|
+
*/
|
|
39
|
+
showClear?: boolean;
|
|
40
|
+
/** Shows the filter icon at the start of the bar. Desktop only. */
|
|
41
|
+
showIcon?: boolean;
|
|
42
|
+
/** `loading`: while the page's data loads, the filters can't be opened, removed or cleared. The search stays usable. */
|
|
43
|
+
state?: 'loading';
|
|
44
|
+
}
|
|
45
|
+
declare const _default: import('react').ForwardRefExoticComponent<BitkitFilterProps & import('react').RefAttributes<HTMLDivElement>> & {
|
|
46
|
+
Add: {
|
|
47
|
+
({ children }: import('./BitkitFilterAdd').BitkitFilterAddProps): import('react').JSX.Element;
|
|
48
|
+
displayName: string;
|
|
49
|
+
};
|
|
50
|
+
DateRange: import('react').ForwardRefExoticComponent<import('./BitkitFilterDateRange').BitkitFilterDateRangeProps & import('react').RefAttributes<HTMLButtonElement>>;
|
|
51
|
+
Search: import('react').ForwardRefExoticComponent<import('./BitkitFilterSearch').BitkitFilterSearchProps & import('react').RefAttributes<HTMLInputElement>>;
|
|
52
|
+
Select: import('react').ForwardRefExoticComponent<import('./BitkitFilterSelect').BitkitFilterSelectProps & import('react').RefAttributes<HTMLButtonElement>>;
|
|
53
|
+
Switch: import('react').ForwardRefExoticComponent<import('./BitkitFilterSwitch').BitkitFilterSwitchProps & import('react').RefAttributes<HTMLElement>>;
|
|
54
|
+
};
|
|
55
|
+
export default _default;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { withSubComponents } from "../../utilities/withSubComponents.js";
|
|
2
|
+
import IconCross from "../../icons/IconCross.js";
|
|
3
|
+
import IconFilter from "../../icons/IconFilter.js";
|
|
4
|
+
import { usePortalContext } from "../../utilities/BitkitPortalContext.js";
|
|
5
|
+
import BitkitCloseButton from "../BitkitCloseButton/BitkitCloseButton.js";
|
|
6
|
+
import BitkitBadge from "../BitkitBadge/BitkitBadge.js";
|
|
7
|
+
import { flattenChildren } from "../../utilities/flattenChildren.js";
|
|
8
|
+
import BitkitButton from "../BitkitButton/BitkitButton.js";
|
|
9
|
+
import useResponsive from "../../hooks/useResponsive.js";
|
|
10
|
+
import { FilterContextProvider } from "./BitkitFilter.context.js";
|
|
11
|
+
import BitkitFilterAdd from "./BitkitFilterAdd.js";
|
|
12
|
+
import BitkitFilterDateRange from "./BitkitFilterDateRange.js";
|
|
13
|
+
import BitkitFilterSearch from "./BitkitFilterSearch.js";
|
|
14
|
+
import BitkitFilterSelect from "./BitkitFilterSelect.js";
|
|
15
|
+
import BitkitFilterSwitch from "./BitkitFilterSwitch.js";
|
|
16
|
+
import { Box } from "@chakra-ui/react/box";
|
|
17
|
+
import { chakra, useSlotRecipe } from "@chakra-ui/react/styled-system";
|
|
18
|
+
import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
19
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
20
|
+
import { Portal } from "@chakra-ui/react/portal";
|
|
21
|
+
import { Drawer } from "@chakra-ui/react/drawer";
|
|
22
|
+
//#region lib/components/BitkitFilter/BitkitFilter.tsx
|
|
23
|
+
var toggle = (set, id, isIn) => {
|
|
24
|
+
if (set.has(id) === isIn) return set;
|
|
25
|
+
const next = new Set(set);
|
|
26
|
+
if (isIn) next.add(id);
|
|
27
|
+
else next.delete(id);
|
|
28
|
+
return next;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* A bar of filters above a list. Each filter is a controlled sub-component with its own `value` and
|
|
32
|
+
* `onValueChange`; the bar lays them out and offers "Clear filters" across all of them.
|
|
33
|
+
*
|
|
34
|
+
* Below the tablet breakpoint the bar collapses into a "Filters" button opening a full-screen dialog,
|
|
35
|
+
* where every filter is a field opening its own view. A filter still applies on its view's "Apply".
|
|
36
|
+
*/
|
|
37
|
+
var BitkitFilter = forwardRef((props, ref) => {
|
|
38
|
+
const { children, contentAlign, contentWidth, css, device, onClear, showClear = true, showIcon = true, state, ...rest } = props;
|
|
39
|
+
const { isMobile: isMobileViewport } = useResponsive();
|
|
40
|
+
const isMobile = device ? device === "mobile" : isMobileViewport;
|
|
41
|
+
const isDisabled = state === "loading";
|
|
42
|
+
const { disablePortal } = usePortalContext();
|
|
43
|
+
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
44
|
+
const recipe = useSlotRecipe({ key: "filter" });
|
|
45
|
+
let pageLayout;
|
|
46
|
+
if (contentWidth) pageLayout = contentWidth === "full" ? "full" : "capped";
|
|
47
|
+
const styles = recipe({
|
|
48
|
+
contentAlign: pageLayout === "capped" ? contentAlign : void 0,
|
|
49
|
+
pageLayout
|
|
50
|
+
});
|
|
51
|
+
const resets = useRef(/* @__PURE__ */ new Map());
|
|
52
|
+
const [dirtyIds, setDirtyIds] = useState(/* @__PURE__ */ new Set());
|
|
53
|
+
const [activeIds, setActiveIds] = useState(/* @__PURE__ */ new Set());
|
|
54
|
+
const register = useCallback((id, reset) => {
|
|
55
|
+
resets.current.set(id, reset);
|
|
56
|
+
}, []);
|
|
57
|
+
const setStatus = useCallback((id, { isActive, isDirty }) => {
|
|
58
|
+
setDirtyIds((prev) => toggle(prev, id, isDirty));
|
|
59
|
+
setActiveIds((prev) => toggle(prev, id, isActive));
|
|
60
|
+
}, []);
|
|
61
|
+
const unregister = useCallback((id) => {
|
|
62
|
+
resets.current.delete(id);
|
|
63
|
+
setStatus(id, {
|
|
64
|
+
isActive: false,
|
|
65
|
+
isDirty: false
|
|
66
|
+
});
|
|
67
|
+
}, [setStatus]);
|
|
68
|
+
const context = useMemo(() => ({
|
|
69
|
+
isDisabled,
|
|
70
|
+
isMobile,
|
|
71
|
+
register,
|
|
72
|
+
setStatus,
|
|
73
|
+
styles,
|
|
74
|
+
unregister
|
|
75
|
+
}), [
|
|
76
|
+
isDisabled,
|
|
77
|
+
isMobile,
|
|
78
|
+
register,
|
|
79
|
+
setStatus,
|
|
80
|
+
styles,
|
|
81
|
+
unregister
|
|
82
|
+
]);
|
|
83
|
+
const filters = [];
|
|
84
|
+
const searches = [];
|
|
85
|
+
flattenChildren(children).forEach((child) => {
|
|
86
|
+
if (child.type === BitkitFilterSearch) searches.push(child);
|
|
87
|
+
else filters.push(child);
|
|
88
|
+
});
|
|
89
|
+
const isDirty = dirtyIds.size > 0;
|
|
90
|
+
const activeCount = activeIds.size;
|
|
91
|
+
const focusTarget = useRef(null);
|
|
92
|
+
const isFocusPending = useRef(false);
|
|
93
|
+
const clearAll = () => {
|
|
94
|
+
if (isDisabled) return;
|
|
95
|
+
isFocusPending.current = true;
|
|
96
|
+
document.addEventListener("focusin", () => {
|
|
97
|
+
isFocusPending.current = false;
|
|
98
|
+
}, { once: true });
|
|
99
|
+
if (onClear) onClear();
|
|
100
|
+
else dirtyIds.forEach((id) => resets.current.get(id)?.());
|
|
101
|
+
};
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (!isFocusPending.current || isDirty) return;
|
|
104
|
+
isFocusPending.current = false;
|
|
105
|
+
const active = document.activeElement;
|
|
106
|
+
if (active && active !== document.body && !active.matches("[data-filter-clear]")) return;
|
|
107
|
+
focusTarget.current?.querySelector("button:not(:disabled), input:not(:disabled)")?.focus();
|
|
108
|
+
}, [isDirty]);
|
|
109
|
+
const disabledProps = {
|
|
110
|
+
"aria-disabled": isDisabled || void 0,
|
|
111
|
+
"data-disabled": isDisabled ? "" : void 0
|
|
112
|
+
};
|
|
113
|
+
const clearProps = {
|
|
114
|
+
...disabledProps,
|
|
115
|
+
"data-filter-clear": "",
|
|
116
|
+
onClick: clearAll
|
|
117
|
+
};
|
|
118
|
+
if (isMobile) return /* @__PURE__ */ jsx(FilterContextProvider, {
|
|
119
|
+
value: context,
|
|
120
|
+
children: /* @__PURE__ */ jsxs(Box, {
|
|
121
|
+
css,
|
|
122
|
+
ref,
|
|
123
|
+
...rest,
|
|
124
|
+
children: [
|
|
125
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
126
|
+
css: styles.mobileBar,
|
|
127
|
+
ref: isDialogOpen ? void 0 : focusTarget,
|
|
128
|
+
children: [/* @__PURE__ */ jsxs(chakra.button, {
|
|
129
|
+
"aria-expanded": isDialogOpen,
|
|
130
|
+
"aria-haspopup": "dialog",
|
|
131
|
+
"aria-label": `Filters, ${activeCount} applied`,
|
|
132
|
+
...disabledProps,
|
|
133
|
+
css: styles.mobileTrigger,
|
|
134
|
+
onClick: () => !isDisabled && setIsDialogOpen(true),
|
|
135
|
+
type: "button",
|
|
136
|
+
children: [
|
|
137
|
+
/* @__PURE__ */ jsx(IconFilter, { size: "16" }),
|
|
138
|
+
"Filters",
|
|
139
|
+
/* @__PURE__ */ jsx(BitkitBadge, {
|
|
140
|
+
colorVariant: activeCount ? "purple" : "neutral",
|
|
141
|
+
children: activeCount
|
|
142
|
+
})
|
|
143
|
+
]
|
|
144
|
+
}), showClear && isDirty && /* @__PURE__ */ jsx(BitkitButton, {
|
|
145
|
+
...clearProps,
|
|
146
|
+
icon: IconCross,
|
|
147
|
+
size: "sm",
|
|
148
|
+
variant: "tertiary",
|
|
149
|
+
children: "Clear filters"
|
|
150
|
+
})]
|
|
151
|
+
}),
|
|
152
|
+
searches,
|
|
153
|
+
/* @__PURE__ */ jsx(Drawer.Root, {
|
|
154
|
+
lazyMount: false,
|
|
155
|
+
onOpenChange: ({ open }) => setIsDialogOpen(open),
|
|
156
|
+
open: isDialogOpen,
|
|
157
|
+
unmountOnExit: false,
|
|
158
|
+
children: /* @__PURE__ */ jsx(Portal, {
|
|
159
|
+
disabled: disablePortal,
|
|
160
|
+
children: /* @__PURE__ */ jsx(Drawer.Positioner, { children: /* @__PURE__ */ jsxs(Drawer.Content, { children: [
|
|
161
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
162
|
+
css: styles.mobileHeader,
|
|
163
|
+
children: [/* @__PURE__ */ jsx(Drawer.Title, {
|
|
164
|
+
css: styles.mobileTitle,
|
|
165
|
+
unstyled: true,
|
|
166
|
+
children: "Filters"
|
|
167
|
+
}), /* @__PURE__ */ jsx(Drawer.CloseTrigger, {
|
|
168
|
+
asChild: true,
|
|
169
|
+
unstyled: true,
|
|
170
|
+
children: /* @__PURE__ */ jsx(BitkitCloseButton, { size: "sm" })
|
|
171
|
+
})]
|
|
172
|
+
}),
|
|
173
|
+
/* @__PURE__ */ jsx(Drawer.Body, {
|
|
174
|
+
css: styles.mobileBody,
|
|
175
|
+
ref: isDialogOpen ? focusTarget : void 0,
|
|
176
|
+
children: filters
|
|
177
|
+
}),
|
|
178
|
+
/* @__PURE__ */ jsx(Drawer.Footer, { children: /* @__PURE__ */ jsxs(Box, {
|
|
179
|
+
css: styles.mobileFooter,
|
|
180
|
+
children: [showClear && /* @__PURE__ */ jsx(BitkitButton, {
|
|
181
|
+
...clearProps,
|
|
182
|
+
size: "md",
|
|
183
|
+
state: isDirty ? void 0 : "disabled",
|
|
184
|
+
variant: "secondary",
|
|
185
|
+
children: "Clear filters"
|
|
186
|
+
}), /* @__PURE__ */ jsx(BitkitButton, {
|
|
187
|
+
onClick: () => setIsDialogOpen(false),
|
|
188
|
+
size: "md",
|
|
189
|
+
children: "Show results"
|
|
190
|
+
})]
|
|
191
|
+
}) })
|
|
192
|
+
] }) })
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
]
|
|
196
|
+
})
|
|
197
|
+
});
|
|
198
|
+
return /* @__PURE__ */ jsx(FilterContextProvider, {
|
|
199
|
+
value: context,
|
|
200
|
+
children: /* @__PURE__ */ jsx(Box, {
|
|
201
|
+
css: [styles.root, css],
|
|
202
|
+
ref,
|
|
203
|
+
...rest,
|
|
204
|
+
children: /* @__PURE__ */ jsxs(Box, {
|
|
205
|
+
css: styles.content,
|
|
206
|
+
maxWidth: contentWidth === "full" ? void 0 : contentWidth,
|
|
207
|
+
ref: focusTarget,
|
|
208
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
209
|
+
css: styles.filters,
|
|
210
|
+
children: [
|
|
211
|
+
showIcon && /* @__PURE__ */ jsx(Box, {
|
|
212
|
+
css: styles.icon,
|
|
213
|
+
children: /* @__PURE__ */ jsx(IconFilter, {})
|
|
214
|
+
}),
|
|
215
|
+
/* @__PURE__ */ jsx(Box, {
|
|
216
|
+
css: styles.items,
|
|
217
|
+
children: filters
|
|
218
|
+
}),
|
|
219
|
+
showClear && isDirty && /* @__PURE__ */ jsx(BitkitButton, {
|
|
220
|
+
...clearProps,
|
|
221
|
+
flexShrink: "0",
|
|
222
|
+
icon: IconCross,
|
|
223
|
+
size: "sm",
|
|
224
|
+
variant: "tertiary",
|
|
225
|
+
children: "Clear filters"
|
|
226
|
+
})
|
|
227
|
+
]
|
|
228
|
+
}), searches.length > 0 && /* @__PURE__ */ jsx(Box, {
|
|
229
|
+
css: styles.searchBlock,
|
|
230
|
+
children: searches
|
|
231
|
+
})]
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
BitkitFilter.displayName = "BitkitFilter";
|
|
237
|
+
var BitkitFilter_default = withSubComponents(BitkitFilter, {
|
|
238
|
+
Add: BitkitFilterAdd,
|
|
239
|
+
DateRange: BitkitFilterDateRange,
|
|
240
|
+
Search: BitkitFilterSearch,
|
|
241
|
+
Select: BitkitFilterSelect,
|
|
242
|
+
Switch: BitkitFilterSwitch
|
|
243
|
+
});
|
|
244
|
+
//#endregion
|
|
245
|
+
export { BitkitFilter_default as default };
|
|
246
|
+
|
|
247
|
+
//# sourceMappingURL=BitkitFilter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BitkitFilter.js","names":[],"sources":["../../../lib/components/BitkitFilter/BitkitFilter.tsx"],"sourcesContent":["import { Box, type BoxProps } from '@chakra-ui/react/box';\nimport { Drawer } from '@chakra-ui/react/drawer';\nimport { Portal } from '@chakra-ui/react/portal';\n// `chakra.button`: the mobile \"Filters\" button carries a count badge, which BitkitButton's string-only label can't.\nimport { chakra, useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { forwardRef, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport useResponsive from '../../hooks/useResponsive';\nimport { IconCross, IconFilter } from '../../icons';\nimport { usePortalContext } from '../../utilities/BitkitPortalContext';\nimport { flattenChildren } from '../../utilities/flattenChildren';\nimport { withSubComponents } from '../../utilities/withSubComponents';\nimport BitkitBadge from '../BitkitBadge/BitkitBadge';\nimport BitkitButton from '../BitkitButton/BitkitButton';\nimport BitkitCloseButton from '../BitkitCloseButton/BitkitCloseButton';\nimport { FilterContextProvider, type FilterStatus } from './BitkitFilter.context';\nimport BitkitFilterAdd from './BitkitFilterAdd';\nimport BitkitFilterDateRange from './BitkitFilterDateRange';\nimport BitkitFilterSearch from './BitkitFilterSearch';\nimport BitkitFilterSelect from './BitkitFilterSelect';\nimport BitkitFilterSwitch from './BitkitFilterSwitch';\n\nexport interface BitkitFilterProps extends Omit<BoxProps, 'children'> {\n /**\n * The filters: `BitkitFilter.Switch`, `.Select`, `.DateRange`, `.Add` and `.Search`, in the\n * order they appear. `.Search` always sits at the end of the bar.\n */\n children: ReactNode;\n /**\n * Where a capped content block sits: `center` over a centred page column, `start` over one that\n * starts against a sidebar. Pass the same alignment as the page header and content below it.\n * Only applies with a `contentWidth` in rems. Desktop only.\n */\n contentAlign?: 'center' | 'start';\n /**\n * Lines the bar up with the page content below it, for a bar under a page header: it gains a\n * bottom border and the page's inline padding. Pass the content column's `max-width` in rems to\n * cap the bar's content with it (padded 32 on both sides), or `'full'` for a full-width column\n * (24 at the start, like the default `BitkitPageHeader`, and 32 at the end).\n *\n * Without it the bar has the tighter inset of a filter above a table. Desktop only.\n */\n contentWidth?: `${number}rem` | 'full';\n /**\n * Forces the desktop bar or the mobile \"Filters\" dialog. Defaults to the viewport: mobile below the\n * tablet breakpoint. The mobile dialog is made for narrow screens; from tablet up it is a 320px drawer.\n */\n device?: 'desktop' | 'mobile';\n /**\n * Called once on \"Clear filters\" in place of handing each changed filter its `resetValue` — for a\n * page that resets them all in one write, e.g. one URL update. The `resetValue`s still decide when\n * the button shows.\n */\n onClear?: () => void;\n /**\n * Shows \"Clear filters\" while any filter differs from its `resetValue`. It hands each of those\n * filters its `resetValue` back through its own `onValueChange`, or calls `onClear`.\n */\n showClear?: boolean;\n /** Shows the filter icon at the start of the bar. Desktop only. */\n showIcon?: boolean;\n /** `loading`: while the page's data loads, the filters can't be opened, removed or cleared. The search stays usable. */\n state?: 'loading';\n}\n\nconst toggle = (set: ReadonlySet<string>, id: string, isIn: boolean) => {\n if (set.has(id) === isIn) return set;\n const next = new Set(set);\n if (isIn) next.add(id);\n else next.delete(id);\n return next;\n};\n\n/**\n * A bar of filters above a list. Each filter is a controlled sub-component with its own `value` and\n * `onValueChange`; the bar lays them out and offers \"Clear filters\" across all of them.\n *\n * Below the tablet breakpoint the bar collapses into a \"Filters\" button opening a full-screen dialog,\n * where every filter is a field opening its own view. A filter still applies on its view's \"Apply\".\n */\nconst BitkitFilter = forwardRef<HTMLDivElement, BitkitFilterProps>((props, ref) => {\n const {\n children,\n contentAlign,\n contentWidth,\n css,\n device,\n onClear,\n showClear = true,\n showIcon = true,\n state,\n ...rest\n } = props;\n const { isMobile: isMobileViewport } = useResponsive();\n const isMobile = device ? device === 'mobile' : isMobileViewport;\n const isDisabled = state === 'loading';\n const { disablePortal } = usePortalContext();\n const [isDialogOpen, setIsDialogOpen] = useState(false);\n\n const recipe = useSlotRecipe({ key: 'filter' });\n let pageLayout: 'capped' | 'full' | undefined;\n if (contentWidth) pageLayout = contentWidth === 'full' ? 'full' : 'capped';\n const styles = recipe({ contentAlign: pageLayout === 'capped' ? contentAlign : undefined, pageLayout });\n\n const resets = useRef(new Map<string, () => void>());\n const [dirtyIds, setDirtyIds] = useState<ReadonlySet<string>>(new Set());\n const [activeIds, setActiveIds] = useState<ReadonlySet<string>>(new Set());\n\n const register = useCallback((id: string, reset: () => void) => {\n resets.current.set(id, reset);\n }, []);\n\n const setStatus = useCallback((id: string, { isActive, isDirty }: FilterStatus) => {\n setDirtyIds((prev) => toggle(prev, id, isDirty));\n setActiveIds((prev) => toggle(prev, id, isActive));\n }, []);\n\n const unregister = useCallback(\n (id: string) => {\n resets.current.delete(id);\n setStatus(id, { isActive: false, isDirty: false });\n },\n [setStatus],\n );\n\n const context = useMemo(\n () => ({ isDisabled, isMobile, register, setStatus, styles, unregister }),\n [isDisabled, isMobile, register, setStatus, styles, unregister],\n );\n\n const filters: ReactNode[] = [];\n const searches: ReactNode[] = [];\n // Flattened, so a filter grouped in a fragment is still sorted to the right place.\n flattenChildren(children).forEach((child) => {\n if (child.type === BitkitFilterSearch) searches.push(child);\n else filters.push(child);\n });\n\n const isDirty = dirtyIds.size > 0;\n const activeCount = activeIds.size;\n // The \"Clear filters\" button goes away (or is disabled) once nothing differs, so focus then moves\n // to the first control of the cleared filters rather than dropping to the page. Only if it hasn't\n // gone anywhere else meanwhile, e.g. while a slow reset was under way.\n const focusTarget = useRef<HTMLDivElement>(null);\n const isFocusPending = useRef(false);\n const clearAll = () => {\n if (isDisabled) return;\n isFocusPending.current = true;\n // Called off once focus lands anywhere, so a clear that never fully lands can't pull it in later.\n document.addEventListener(\n 'focusin',\n () => {\n isFocusPending.current = false;\n },\n { once: true },\n );\n if (onClear) onClear();\n else dirtyIds.forEach((id) => resets.current.get(id)?.());\n };\n useEffect(() => {\n if (!isFocusPending.current || isDirty) return;\n isFocusPending.current = false;\n const active = document.activeElement;\n if (active && active !== document.body && !active.matches('[data-filter-clear]')) return;\n focusTarget.current?.querySelector<HTMLElement>('button:not(:disabled), input:not(:disabled)')?.focus();\n }, [isDirty]);\n\n // While the page loads, the controls are only `aria-disabled`, so they keep a focus handed to them.\n // `data-disabled` takes them out of the hover and active styles too.\n const disabledProps = { 'aria-disabled': isDisabled || undefined, 'data-disabled': isDisabled ? '' : undefined };\n const clearProps = { ...disabledProps, 'data-filter-clear': '', onClick: clearAll };\n\n if (isMobile) {\n return (\n <FilterContextProvider value={context}>\n <Box css={css} ref={ref} {...rest}>\n <Box css={styles.mobileBar} ref={isDialogOpen ? undefined : focusTarget}>\n <chakra.button\n aria-expanded={isDialogOpen}\n aria-haspopup=\"dialog\"\n aria-label={`Filters, ${activeCount} applied`}\n {...disabledProps}\n css={styles.mobileTrigger}\n onClick={() => !isDisabled && setIsDialogOpen(true)}\n type=\"button\"\n >\n <IconFilter size=\"16\" />\n Filters\n <BitkitBadge colorVariant={activeCount ? 'purple' : 'neutral'}>{activeCount}</BitkitBadge>\n </chakra.button>\n {showClear && isDirty && (\n <BitkitButton {...clearProps} icon={IconCross} size=\"sm\" variant=\"tertiary\">\n Clear filters\n </BitkitButton>\n )}\n </Box>\n {searches}\n {/* Kept mounted while closed: the filters inside register their values with the bar. */}\n <Drawer.Root\n lazyMount={false}\n onOpenChange={({ open }) => setIsDialogOpen(open)}\n open={isDialogOpen}\n unmountOnExit={false}\n >\n <Portal disabled={disablePortal}>\n <Drawer.Positioner>\n <Drawer.Content>\n <Box css={styles.mobileHeader}>\n <Drawer.Title css={styles.mobileTitle} unstyled>\n Filters\n </Drawer.Title>\n <Drawer.CloseTrigger asChild unstyled>\n <BitkitCloseButton size=\"sm\" />\n </Drawer.CloseTrigger>\n </Box>\n <Drawer.Body css={styles.mobileBody} ref={isDialogOpen ? focusTarget : undefined}>\n {filters}\n </Drawer.Body>\n <Drawer.Footer>\n <Box css={styles.mobileFooter}>\n {showClear && (\n <BitkitButton\n {...clearProps}\n size=\"md\"\n state={isDirty ? undefined : 'disabled'}\n variant=\"secondary\"\n >\n Clear filters\n </BitkitButton>\n )}\n <BitkitButton onClick={() => setIsDialogOpen(false)} size=\"md\">\n Show results\n </BitkitButton>\n </Box>\n </Drawer.Footer>\n </Drawer.Content>\n </Drawer.Positioner>\n </Portal>\n </Drawer.Root>\n </Box>\n </FilterContextProvider>\n );\n }\n\n return (\n <FilterContextProvider value={context}>\n <Box css={[styles.root, css]} ref={ref} {...rest}>\n <Box css={styles.content} maxWidth={contentWidth === 'full' ? undefined : contentWidth} ref={focusTarget}>\n <Box css={styles.filters}>\n {showIcon && (\n <Box css={styles.icon}>\n <IconFilter />\n </Box>\n )}\n <Box css={styles.items}>{filters}</Box>\n {showClear && isDirty && (\n <BitkitButton {...clearProps} flexShrink=\"0\" icon={IconCross} size=\"sm\" variant=\"tertiary\">\n Clear filters\n </BitkitButton>\n )}\n </Box>\n {searches.length > 0 && <Box css={styles.searchBlock}>{searches}</Box>}\n </Box>\n </Box>\n </FilterContextProvider>\n );\n});\n\nBitkitFilter.displayName = 'BitkitFilter';\n\nexport default withSubComponents(BitkitFilter, {\n Add: BitkitFilterAdd,\n DateRange: BitkitFilterDateRange,\n Search: BitkitFilterSearch,\n Select: BitkitFilterSelect,\n Switch: BitkitFilterSwitch,\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type FilterValue = string | string[] | Date[] | null;
|
|
2
|
+
/** Typing in a search settles for this long before its callback fires. */
|
|
3
|
+
export declare const SEARCH_DEBOUNCE_MS = 300;
|
|
4
|
+
/** Compares option lists as sets, since checking options in another order picks the same ones; dates by position. */
|
|
5
|
+
export declare const isEqualFilterValue: (a: FilterValue, b: FilterValue) => boolean;
|
|
6
|
+
/** A filter value that holds no selection. */
|
|
7
|
+
export declare const isEmptyFilterValue: (value: FilterValue) => boolean;
|
|
8
|
+
export declare const isPattern: (value: string) => boolean;
|
|
9
|
+
/** Compiles a wildcard `pattern`, where `*` stands for any run of characters, to match whole texts. */
|
|
10
|
+
export declare const compilePattern: (pattern: string) => RegExp;
|
|
11
|
+
/**
|
|
12
|
+
* Lowercases a noun for running text, keeping any word with a capital after its first letter as an
|
|
13
|
+
* acronym or name: "CI Providers" reads "CI providers", "PRs" and "iOS apps" stay as they are.
|
|
14
|
+
*/
|
|
15
|
+
export declare const toRunningText: (noun: string) => string;
|
|
16
|
+
export declare const formatDateRange: (range: Date[]) => string;
|
|
17
|
+
/** The last moment of the day `date` falls on, so a range ending that day includes all of it. */
|
|
18
|
+
export declare const endOfDay: (date: Date) => Date;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//#region lib/components/BitkitFilter/BitkitFilter.utils.ts
|
|
2
|
+
/** Compares option lists as sets, since checking options in another order picks the same ones; dates by position. */
|
|
3
|
+
var isEqualFilterValue = (a, b) => {
|
|
4
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
5
|
+
if (a.length !== b.length) return false;
|
|
6
|
+
if (a.every((item) => typeof item === "string")) {
|
|
7
|
+
const others = new Set(b);
|
|
8
|
+
return a.every((item) => others.has(item));
|
|
9
|
+
}
|
|
10
|
+
return a.every((item, index) => {
|
|
11
|
+
const other = b[index];
|
|
12
|
+
return item instanceof Date && other instanceof Date ? item.getTime() === other.getTime() : item === other;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return a === b;
|
|
16
|
+
};
|
|
17
|
+
/** A filter value that holds no selection. */
|
|
18
|
+
var isEmptyFilterValue = (value) => value === null || value === "" || Array.isArray(value) && value.length === 0;
|
|
19
|
+
var regExpEscape = (text) => text.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
20
|
+
var isPattern = (value) => value.includes("*");
|
|
21
|
+
/** Compiles a wildcard `pattern`, where `*` stands for any run of characters, to match whole texts. */
|
|
22
|
+
var compilePattern = (pattern) => new RegExp(`^${pattern.split(/\*+/).map(regExpEscape).join(".*")}$`);
|
|
23
|
+
/**
|
|
24
|
+
* Lowercases a noun for running text, keeping any word with a capital after its first letter as an
|
|
25
|
+
* acronym or name: "CI Providers" reads "CI providers", "PRs" and "iOS apps" stay as they are.
|
|
26
|
+
*/
|
|
27
|
+
var toRunningText = (noun) => noun.split(" ").map((word) => /[A-Z]/.test(word.slice(1)) ? word : word.toLowerCase()).join(" ");
|
|
28
|
+
var dateFormat = new Intl.DateTimeFormat("en-US", {
|
|
29
|
+
day: "numeric",
|
|
30
|
+
month: "short"
|
|
31
|
+
});
|
|
32
|
+
var formatDateRange = (range) => range.map((date) => dateFormat.format(date)).join(" – ");
|
|
33
|
+
/** The last moment of the day `date` falls on, so a range ending that day includes all of it. */
|
|
34
|
+
var endOfDay = (date) => {
|
|
35
|
+
const end = new Date(date);
|
|
36
|
+
end.setHours(23, 59, 59, 999);
|
|
37
|
+
return end;
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
export { compilePattern, endOfDay, formatDateRange, isEmptyFilterValue, isEqualFilterValue, isPattern, toRunningText };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=BitkitFilter.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BitkitFilter.utils.js","names":[],"sources":["../../../lib/components/BitkitFilter/BitkitFilter.utils.ts"],"sourcesContent":["export type FilterValue = string | string[] | Date[] | null;\n\n/** Typing in a search settles for this long before its callback fires. */\nexport const SEARCH_DEBOUNCE_MS = 300;\n\n/** Compares option lists as sets, since checking options in another order picks the same ones; dates by position. */\nexport const isEqualFilterValue = (a: FilterValue, b: FilterValue): boolean => {\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n if (a.every((item) => typeof item === 'string')) {\n const others = new Set<unknown>(b);\n return a.every((item) => others.has(item));\n }\n return a.every((item, index) => {\n const other = b[index];\n return item instanceof Date && other instanceof Date ? item.getTime() === other.getTime() : item === other;\n });\n }\n return a === b;\n};\n\n/** A filter value that holds no selection. */\nexport const isEmptyFilterValue = (value: FilterValue): boolean =>\n value === null || value === '' || (Array.isArray(value) && value.length === 0);\n\nconst regExpEscape = (text: string) => text.replace(/[|\\\\{}()[\\]^$+?.]/g, '\\\\$&');\n\nexport const isPattern = (value: string) => value.includes('*');\n\n/** Compiles a wildcard `pattern`, where `*` stands for any run of characters, to match whole texts. */\nexport const compilePattern = (pattern: string) => new RegExp(`^${pattern.split(/\\*+/).map(regExpEscape).join('.*')}$`);\n\n/**\n * Lowercases a noun for running text, keeping any word with a capital after its first letter as an\n * acronym or name: \"CI Providers\" reads \"CI providers\", \"PRs\" and \"iOS apps\" stay as they are.\n */\nexport const toRunningText = (noun: string) =>\n noun\n .split(' ')\n .map((word) => (/[A-Z]/.test(word.slice(1)) ? word : word.toLowerCase()))\n .join(' ');\n\nconst dateFormat = new Intl.DateTimeFormat('en-US', { day: 'numeric', month: 'short' });\n\nexport const formatDateRange = (range: Date[]) => range.map((date) => dateFormat.format(date)).join(' – ');\n\n/** The last moment of the day `date` falls on, so a range ending that day includes all of it. */\nexport const endOfDay = (date: Date) => {\n const end = new Date(date);\n end.setHours(23, 59, 59, 999);\n return end;\n};\n"],"mappings":";;AAMA,IAAa,sBAAsB,GAAgB,MAA4B;CAC7E,IAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GAAG;EACxC,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;EAClC,IAAI,EAAE,OAAO,SAAS,OAAO,SAAS,QAAQ,GAAG;GAC/C,MAAM,SAAS,IAAI,IAAa,CAAC;GACjC,OAAO,EAAE,OAAO,SAAS,OAAO,IAAI,IAAI,CAAC;EAC3C;EACA,OAAO,EAAE,OAAO,MAAM,UAAU;GAC9B,MAAM,QAAQ,EAAE;GAChB,OAAO,gBAAgB,QAAQ,iBAAiB,OAAO,KAAK,QAAQ,MAAM,MAAM,QAAQ,IAAI,SAAS;EACvG,CAAC;CACH;CACA,OAAO,MAAM;AACf;;AAGA,IAAa,sBAAsB,UACjC,UAAU,QAAQ,UAAU,MAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW;AAE9E,IAAM,gBAAgB,SAAiB,KAAK,QAAQ,sBAAsB,MAAM;AAEhF,IAAa,aAAa,UAAkB,MAAM,SAAS,GAAG;;AAG9D,IAAa,kBAAkB,YAAoB,IAAI,OAAO,IAAI,QAAQ,MAAM,KAAK,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,KAAK,IAAI,EAAE,EAAE;;;;;AAMtH,IAAa,iBAAiB,SAC5B,KACG,MAAM,GAAG,CAAC,CACV,KAAK,SAAU,QAAQ,KAAK,KAAK,MAAM,CAAC,CAAC,IAAI,OAAO,KAAK,YAAY,CAAE,CAAC,CACxE,KAAK,GAAG;AAEb,IAAM,aAAa,IAAI,KAAK,eAAe,SAAS;CAAE,KAAK;CAAW,OAAO;AAAQ,CAAC;AAEtF,IAAa,mBAAmB,UAAkB,MAAM,KAAK,SAAS,WAAW,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK;;AAGzG,IAAa,YAAY,SAAe;CACtC,MAAM,MAAM,IAAI,KAAK,IAAI;CACzB,IAAI,SAAS,IAAI,IAAI,IAAI,GAAG;CAC5B,OAAO;AACT"}
|