@bitrise/bitkit-v2 0.3.478-beta.3036 → 0.3.479
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 +0 -12
- package/dist/components/BitkitCalendar/BitkitCalendar.js +1 -7
- package/dist/components/BitkitCalendar/BitkitCalendar.js.map +1 -1
- package/dist/components/BitkitCalendar/components/CalendarContent.d.ts +1 -5
- package/dist/components/BitkitCalendar/components/CalendarContent.js +43 -60
- package/dist/components/BitkitCalendar/components/CalendarContent.js.map +1 -1
- package/dist/components/BitkitCalendar/components/CalendarMonth.d.ts +2 -4
- package/dist/components/BitkitCalendar/components/CalendarMonth.js +5 -15
- package/dist/components/BitkitCalendar/components/CalendarMonth.js.map +1 -1
- package/dist/components/BitkitCalendar/components/CalendarMonthSelect.d.ts +1 -2
- package/dist/components/BitkitCalendar/components/CalendarMonthSelect.js +2 -3
- package/dist/components/BitkitCalendar/components/CalendarMonthSelect.js.map +1 -1
- package/dist/components/BitkitCalendar/components/CalendarNextTrigger.d.ts +1 -3
- 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 +1 -3
- 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 +0 -1
- package/dist/components/BitkitCalendar/components/CalendarSelect.js +3 -5
- package/dist/components/BitkitCalendar/components/CalendarSelect.js.map +1 -1
- package/dist/components/BitkitCalendar/components/CalendarTrigger.js +0 -1
- package/dist/components/BitkitCalendar/components/CalendarTrigger.js.map +1 -1
- package/dist/components/BitkitCalendar/components/CalendarYearSelect.d.ts +1 -2
- package/dist/components/BitkitCalendar/components/CalendarYearSelect.js +2 -3
- package/dist/components/BitkitCalendar/components/CalendarYearSelect.js.map +1 -1
- package/dist/components/BitkitDatePicker/BitkitDatePicker.d.ts +0 -2
- package/dist/components/BitkitDatePicker/BitkitDatePicker.js +1 -2
- package/dist/components/BitkitDatePicker/BitkitDatePicker.js.map +1 -1
- package/dist/components/BitkitLabelTooltip/BitkitLabelTooltip.d.ts +3 -4
- package/dist/components/BitkitLabelTooltip/BitkitLabelTooltip.js +2 -3
- 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/BitkitPageHeader/BitkitPageHeader.d.ts +14 -0
- package/dist/components/BitkitPageHeader/BitkitPageHeader.js +62 -36
- package/dist/components/BitkitPageHeader/BitkitPageHeader.js.map +1 -1
- package/dist/components/BitkitPageSidebar/BitkitPageSidebar.d.ts +24 -0
- package/dist/components/BitkitPageSidebar/BitkitPageSidebar.js +74 -6
- package/dist/components/BitkitPageSidebar/BitkitPageSidebar.js.map +1 -1
- package/dist/components/BitkitSelect/BitkitSelect.js +1 -0
- package/dist/components/BitkitSelect/BitkitSelect.js.map +1 -1
- package/dist/components/index.d.ts +1 -7
- package/dist/main.js +7 -8
- package/dist/theme/slot-recipes/DatePicker.recipe.d.ts +0 -15
- package/dist/theme/slot-recipes/DatePicker.recipe.js +22 -72
- package/dist/theme/slot-recipes/DatePicker.recipe.js.map +1 -1
- package/dist/theme/slot-recipes/DatePickerSelect.recipe.d.ts +0 -85
- package/dist/theme/slot-recipes/DatePickerSelect.recipe.js +10 -35
- package/dist/theme/slot-recipes/DatePickerSelect.recipe.js.map +1 -1
- package/dist/theme/slot-recipes/Menu.recipe.js +1 -6
- package/dist/theme/slot-recipes/Menu.recipe.js.map +1 -1
- package/dist/theme/slot-recipes/PageHeader.recipe.d.ts +23 -1
- package/dist/theme/slot-recipes/PageHeader.recipe.js +63 -0
- package/dist/theme/slot-recipes/PageHeader.recipe.js.map +1 -1
- package/dist/theme/slot-recipes/PageSidebar.recipe.d.ts +49 -1
- package/dist/theme/slot-recipes/PageSidebar.recipe.js +93 -0
- package/dist/theme/slot-recipes/PageSidebar.recipe.js.map +1 -1
- package/dist/theme/slot-recipes/Tag.recipe.d.ts +1 -1
- package/dist/theme/slot-recipes/index.d.ts +59 -160
- package/dist/theme/slot-recipes/index.js +0 -4
- package/dist/theme/slot-recipes/index.js.map +1 -1
- package/dist/utilities/BitkitPortalContext.js +2 -3
- package/dist/utilities/BitkitPortalContext.js.map +1 -1
- package/docs/v1-to-v2-migration-guide.md +1 -48
- package/package.json +1 -1
- package/dist/components/BitkitFilter/BitkitFilter.context.d.ts +0 -65
- package/dist/components/BitkitFilter/BitkitFilter.context.js +0 -82
- package/dist/components/BitkitFilter/BitkitFilter.context.js.map +0 -1
- package/dist/components/BitkitFilter/BitkitFilter.d.ts +0 -55
- package/dist/components/BitkitFilter/BitkitFilter.js +0 -247
- package/dist/components/BitkitFilter/BitkitFilter.js.map +0 -1
- package/dist/components/BitkitFilter/BitkitFilter.utils.d.ts +0 -18
- package/dist/components/BitkitFilter/BitkitFilter.utils.js +0 -42
- package/dist/components/BitkitFilter/BitkitFilter.utils.js.map +0 -1
- package/dist/components/BitkitFilter/BitkitFilterAdd.d.ts +0 -18
- package/dist/components/BitkitFilter/BitkitFilterAdd.js +0 -200
- package/dist/components/BitkitFilter/BitkitFilterAdd.js.map +0 -1
- package/dist/components/BitkitFilter/BitkitFilterDateRange.d.ts +0 -26
- package/dist/components/BitkitFilter/BitkitFilterDateRange.js +0 -118
- package/dist/components/BitkitFilter/BitkitFilterDateRange.js.map +0 -1
- package/dist/components/BitkitFilter/BitkitFilterSearch.d.ts +0 -23
- package/dist/components/BitkitFilter/BitkitFilterSearch.js +0 -72
- package/dist/components/BitkitFilter/BitkitFilterSearch.js.map +0 -1
- package/dist/components/BitkitFilter/BitkitFilterSelect.d.ts +0 -61
- package/dist/components/BitkitFilter/BitkitFilterSelect.js +0 -149
- package/dist/components/BitkitFilter/BitkitFilterSelect.js.map +0 -1
- package/dist/components/BitkitFilter/BitkitFilterSwitch.d.ts +0 -28
- package/dist/components/BitkitFilter/BitkitFilterSwitch.js +0 -87
- package/dist/components/BitkitFilter/BitkitFilterSwitch.js.map +0 -1
- package/dist/components/BitkitFilter/components/FilterChip.d.ts +0 -23
- package/dist/components/BitkitFilter/components/FilterChip.js +0 -66
- package/dist/components/BitkitFilter/components/FilterChip.js.map +0 -1
- package/dist/components/BitkitFilter/components/FilterField.d.ts +0 -15
- package/dist/components/BitkitFilter/components/FilterField.js +0 -60
- package/dist/components/BitkitFilter/components/FilterField.js.map +0 -1
- package/dist/components/BitkitFilter/components/FilterPanel.d.ts +0 -42
- package/dist/components/BitkitFilter/components/FilterPanel.js +0 -126
- package/dist/components/BitkitFilter/components/FilterPanel.js.map +0 -1
- package/dist/components/BitkitFilter/components/FilterSearchInput.d.ts +0 -15
- package/dist/components/BitkitFilter/components/FilterSearchInput.js +0 -41
- package/dist/components/BitkitFilter/components/FilterSearchInput.js.map +0 -1
- package/dist/components/BitkitFilter/components/FilterSelectForm.d.ts +0 -45
- package/dist/components/BitkitFilter/components/FilterSelectForm.js +0 -324
- package/dist/components/BitkitFilter/components/FilterSelectForm.js.map +0 -1
- package/dist/theme/slot-recipes/Filter.recipe.d.ts +0 -37
- package/dist/theme/slot-recipes/Filter.recipe.js +0 -649
- package/dist/theme/slot-recipes/Filter.recipe.js.map +0 -1
- package/dist/theme/slot-recipes/Listbox.recipe.d.ts +0 -22
- package/dist/theme/slot-recipes/Listbox.recipe.js +0 -92
- package/dist/theme/slot-recipes/Listbox.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 /** 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":"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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 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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"}
|
|
@@ -12,13 +12,12 @@ export type BitkitLabelTooltipProps = {
|
|
|
12
12
|
disablePortal?: boolean;
|
|
13
13
|
/** Where the tooltip sits relative to the trigger (default `top`). It flips if space is short. */
|
|
14
14
|
placement?: NonNullable<TooltipRootProps['positioning']>['placement'];
|
|
15
|
-
/** The label. Keep it to a
|
|
15
|
+
/** The label. Keep it to a couple of words — this is a name, not an explanation. */
|
|
16
16
|
text: string;
|
|
17
17
|
};
|
|
18
18
|
/**
|
|
19
|
-
* Names a control that shows no text of its own, typically an icon button,
|
|
20
|
-
*
|
|
21
|
-
* `BitkitTooltip`, which explains rather than labels.
|
|
19
|
+
* Names a control that shows no text of its own, typically an icon button. Small, tight and
|
|
20
|
+
* semibold, in contrast to `BitkitTooltip`, which explains rather than labels.
|
|
22
21
|
*
|
|
23
22
|
* Note this only *describes* the trigger to sighted users — it does not give it an accessible name.
|
|
24
23
|
* An icon-only control still needs its own `aria-label`.
|
|
@@ -5,9 +5,8 @@ import { Portal } from "@chakra-ui/react/portal";
|
|
|
5
5
|
import { Tooltip } from "@chakra-ui/react/tooltip";
|
|
6
6
|
//#region lib/components/BitkitLabelTooltip/BitkitLabelTooltip.tsx
|
|
7
7
|
/**
|
|
8
|
-
* Names a control that shows no text of its own, typically an icon button,
|
|
9
|
-
*
|
|
10
|
-
* `BitkitTooltip`, which explains rather than labels.
|
|
8
|
+
* Names a control that shows no text of its own, typically an icon button. Small, tight and
|
|
9
|
+
* semibold, in contrast to `BitkitTooltip`, which explains rather than labels.
|
|
11
10
|
*
|
|
12
11
|
* Note this only *describes* the trigger to sighted users — it does not give it an accessible name.
|
|
13
12
|
* An icon-only control still needs its own `aria-label`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitLabelTooltip.js","names":[],"sources":["../../../lib/components/BitkitLabelTooltip/BitkitLabelTooltip.tsx"],"sourcesContent":["import { Portal } from '@chakra-ui/react/portal';\nimport { Tooltip, type TooltipRootProps } from '@chakra-ui/react/tooltip';\nimport { type ReactNode, type Ref, useContext, useRef } from 'react';\n\nimport BitkitPortalContext from '../../utilities/BitkitPortalContext';\n\nexport type BitkitLabelTooltipProps = {\n /** The control the tooltip labels. Rendered via `asChild`, so it must accept a ref. */\n children: ReactNode;\n /** Render the trigger without a tooltip — for labels that only apply in some states. */\n disabled?: TooltipRootProps['disabled'];\n /**\n * Render the tooltip inline instead of in a portal. Only needed when it must stay inside its\n * parent's DOM subtree — a dialog already sets this for you.\n */\n disablePortal?: boolean;\n /** Where the tooltip sits relative to the trigger (default `top`). It flips if space is short. */\n placement?: NonNullable<TooltipRootProps['positioning']>['placement'];\n /** The label. Keep it to a
|
|
1
|
+
{"version":3,"file":"BitkitLabelTooltip.js","names":[],"sources":["../../../lib/components/BitkitLabelTooltip/BitkitLabelTooltip.tsx"],"sourcesContent":["import { Portal } from '@chakra-ui/react/portal';\nimport { Tooltip, type TooltipRootProps } from '@chakra-ui/react/tooltip';\nimport { type ReactNode, type Ref, useContext, useRef } from 'react';\n\nimport BitkitPortalContext from '../../utilities/BitkitPortalContext';\n\nexport type BitkitLabelTooltipProps = {\n /** The control the tooltip labels. Rendered via `asChild`, so it must accept a ref. */\n children: ReactNode;\n /** Render the trigger without a tooltip — for labels that only apply in some states. */\n disabled?: TooltipRootProps['disabled'];\n /**\n * Render the tooltip inline instead of in a portal. Only needed when it must stay inside its\n * parent's DOM subtree — a dialog already sets this for you.\n */\n disablePortal?: boolean;\n /** Where the tooltip sits relative to the trigger (default `top`). It flips if space is short. */\n placement?: NonNullable<TooltipRootProps['positioning']>['placement'];\n /** The label. Keep it to a couple of words — this is a name, not an explanation. */\n text: string;\n};\n\n/**\n * Names a control that shows no text of its own, typically an icon button. Small, tight and\n * semibold, in contrast to `BitkitTooltip`, which explains rather than labels.\n *\n * Note this only *describes* the trigger to sighted users — it does not give it an accessible name.\n * An icon-only control still needs its own `aria-label`.\n *\n * The content renders in a portal so it escapes ancestors that trap stacking or clip overflow.\n * Dialogs turn that off through `BitkitPortalContext`; `disablePortal` is the manual override.\n */\nconst BitkitLabelTooltip = ({\n children,\n disablePortal,\n disabled,\n placement = 'top',\n text,\n}: BitkitLabelTooltipProps) => {\n const { disablePortal: disablePortalFromContext } = useContext(BitkitPortalContext);\n\n // Anchor positioning to the trigger node by ref rather than relying on Zag's\n // id/data-scope lookup. When the trigger is also wrapped by another `asChild`\n // trigger (e.g. Menu.Trigger, Dialog.CloseTrigger), the outer trigger's id and\n // data-scope/data-part overwrite ours on the shared node, so `getTriggerEl()`\n // can't find it and the tooltip loses its anchor. `getAnchorElement` takes\n // precedence over the id lookup, so this is safe in the standalone case too.\n const triggerRef = useRef<HTMLElement>(null);\n\n return (\n <Tooltip.Root\n disabled={disabled}\n paddingSize=\"sm\"\n positioning={{ getAnchorElement: () => triggerRef.current, placement, offset: { mainAxis: 4, crossAxis: 0 } }}\n textStyle=\"sm/semibold\"\n >\n <Tooltip.Trigger ref={triggerRef as Ref<HTMLButtonElement>} asChild>\n {children}\n </Tooltip.Trigger>\n <Portal disabled={disablePortal || disablePortalFromContext}>\n <Tooltip.Positioner>\n <Tooltip.Content>{text}</Tooltip.Content>\n </Tooltip.Positioner>\n </Portal>\n </Tooltip.Root>\n );\n};\n\nBitkitLabelTooltip.displayName = 'BitkitLabelTooltip';\n\nexport default BitkitLabelTooltip;\n"],"mappings":";;;;;;;;;;;;;;;;AAgCA,IAAM,sBAAsB,EAC1B,UACA,eACA,UACA,YAAY,OACZ,WAC6B;CAC7B,MAAM,EAAE,eAAe,6BAA6B,WAAW,mBAAmB;CAQlF,MAAM,aAAa,OAAoB,IAAI;CAE3C,OACE,qBAAC,QAAQ,MAAT;EACY;EACV,aAAY;EACZ,aAAa;GAAE,wBAAwB,WAAW;GAAS;GAAW,QAAQ;IAAE,UAAU;IAAG,WAAW;GAAE;EAAE;EAC5G,WAAU;EAJZ,UAAA,CAME,oBAAC,QAAQ,SAAT;GAAiB,KAAK;GAAsC,SAAA;GACzD;EACc,CAAA,GACjB,oBAAC,QAAD;GAAQ,UAAU,iBAAiB;GACjC,UAAA,oBAAC,QAAQ,YAAT,EAAA,UACE,oBAAC,QAAQ,SAAT,EAAA,UAAkB,KAAsB,CAAA,EACtB,CAAA;EACd,CAAA,CACI;;AAElB;AAEA,mBAAmB,cAAc"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import BitkitCodeSnippet from "../BitkitCodeSnippet/BitkitCodeSnippet.js";
|
|
2
|
-
import BitkitList_default from "../BitkitList/BitkitList.js";
|
|
3
2
|
import BitkitHeading from "../BitkitHeading/BitkitHeading.js";
|
|
4
3
|
import BitkitLink from "../BitkitLink/BitkitLink.js";
|
|
4
|
+
import BitkitList_default from "../BitkitList/BitkitList.js";
|
|
5
5
|
import { getTextContent } from "../../utilities/getTextContent.js";
|
|
6
6
|
import { chakra, useSlotRecipe } from "@chakra-ui/react/styled-system";
|
|
7
7
|
import { forwardRef } from "react";
|
|
@@ -3,8 +3,8 @@ import IconChevronDown from "../../icons/IconChevronDown.js";
|
|
|
3
3
|
import IconChevronUp from "../../icons/IconChevronUp.js";
|
|
4
4
|
import { ICON_COMPONENTS_MAP } from "../common/notificationMaps.js";
|
|
5
5
|
import BitkitButton from "../BitkitButton/BitkitButton.js";
|
|
6
|
-
import BitkitList_default from "../BitkitList/BitkitList.js";
|
|
7
6
|
import BitkitLinkButton from "../BitkitLinkButton/BitkitLinkButton.js";
|
|
7
|
+
import BitkitList_default from "../BitkitList/BitkitList.js";
|
|
8
8
|
import { Box } from "@chakra-ui/react/box";
|
|
9
9
|
import { useSlotRecipe } from "@chakra-ui/react/styled-system";
|
|
10
10
|
import { forwardRef, useState } from "react";
|
|
@@ -95,6 +95,14 @@ export interface BitkitPageHeaderProps extends Omit<BoxProps, 'title'> {
|
|
|
95
95
|
* Pair it with `size="sm"` actions — see `children`.
|
|
96
96
|
*/
|
|
97
97
|
isSticky?: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Opens the page's sidebar on mobile. Its presence switches the header to the design's
|
|
100
|
+
* `hasSidebar` layout below `tablet`: the title shrinks beside a menu button, and the
|
|
101
|
+
* `sectionTitle` takes over as the large heading under a rule, with the actions below it. Wire it
|
|
102
|
+
* to a `BitkitPageSidebar`'s `open` / `onOpenChange`. From `tablet` the button is hidden and the
|
|
103
|
+
* header is the ordinary one — the sidebar is beside the page there.
|
|
104
|
+
*/
|
|
105
|
+
onMenuClick?: () => void;
|
|
98
106
|
/**
|
|
99
107
|
* Descriptive text below the title. Hidden in the collapsed layout.
|
|
100
108
|
*
|
|
@@ -116,6 +124,12 @@ export interface BitkitPageHeaderProps extends Omit<BoxProps, 'title'> {
|
|
|
116
124
|
* the tab list from `tablet`: the line stays although no tabs show above it.
|
|
117
125
|
*/
|
|
118
126
|
showDivider?: boolean;
|
|
127
|
+
/**
|
|
128
|
+
* The sidebar section the page is showing, for the `hasSidebar` layout — see `onMenuClick`. It
|
|
129
|
+
* is the page's visible `h2` on mobile, where the sidebar that would name it is behind the menu
|
|
130
|
+
* button. Not rendered from `tablet`, and never without `onMenuClick`.
|
|
131
|
+
*/
|
|
132
|
+
sectionTitle?: string;
|
|
119
133
|
/**
|
|
120
134
|
* Tab bar along the bottom edge — pass a `BitkitTabs.List`. Hidden in the collapsed layout.
|
|
121
135
|
*
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import IconMenuHamburger from "../../icons/IconMenuHamburger.js";
|
|
1
2
|
import { isEmptyNode } from "../../utilities/isEmptyNode.js";
|
|
3
|
+
import BitkitIconButton from "../BitkitIconButton/BitkitIconButton.js";
|
|
2
4
|
import BitkitOverflowTooltip from "../BitkitOverflowTooltip/BitkitOverflowTooltip.js";
|
|
3
5
|
import useIsTruncated from "../../utilities/useIsTruncated.js";
|
|
4
6
|
import { Box } from "@chakra-ui/react/box";
|
|
@@ -8,12 +10,14 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
8
10
|
import { Text } from "@chakra-ui/react/text";
|
|
9
11
|
//#region lib/components/BitkitPageHeader/BitkitPageHeader.tsx
|
|
10
12
|
var BitkitPageHeader = forwardRef((props, ref) => {
|
|
11
|
-
const { avatar, badge, breadcrumb, children, contentAlign, contentVariant, contentWidth, controls, css, isSticky, secondaryText, showDivider = true, tabs, title, variant, ...rest } = props;
|
|
13
|
+
const { avatar, badge, breadcrumb, children, contentAlign, contentVariant, contentWidth, controls, css, isSticky, onMenuClick, secondaryText, sectionTitle, showDivider = true, tabs, title, variant, ...rest } = props;
|
|
12
14
|
const hasAvatar = !isEmptyNode(avatar);
|
|
13
15
|
const hasBadge = !isEmptyNode(badge);
|
|
14
16
|
const hasBreadcrumb = !isEmptyNode(breadcrumb);
|
|
15
17
|
const hasActions = !isEmptyNode(children);
|
|
16
18
|
const hasSecondaryText = !isEmptyNode(secondaryText);
|
|
19
|
+
const hasSidebar = onMenuClick !== void 0;
|
|
20
|
+
const hasSectionTitle = hasSidebar && !isEmptyNode(sectionTitle);
|
|
17
21
|
const hasTabs = !isEmptyNode(tabs);
|
|
18
22
|
const { isTruncated: isTitleTruncated, ref: titleRef } = useIsTruncated(title);
|
|
19
23
|
const { isTruncated: isSecondaryTruncated, ref: secondaryRef } = useIsTruncated(secondaryText);
|
|
@@ -25,6 +29,7 @@ var BitkitPageHeader = forwardRef((props, ref) => {
|
|
|
25
29
|
hasBreadcrumb,
|
|
26
30
|
hasControls: showControls,
|
|
27
31
|
hasDivider: showDivider || hasTabs,
|
|
32
|
+
hasSidebar,
|
|
28
33
|
alignsToContent: Boolean(contentWidth),
|
|
29
34
|
contentAlign,
|
|
30
35
|
isSticky,
|
|
@@ -34,6 +39,43 @@ var BitkitPageHeader = forwardRef((props, ref) => {
|
|
|
34
39
|
css: styles.controls,
|
|
35
40
|
children: controls
|
|
36
41
|
}) : null;
|
|
42
|
+
const titleBlock = /* @__PURE__ */ jsxs(Box, {
|
|
43
|
+
css: styles.titleBlock,
|
|
44
|
+
children: [hasAvatar && /* @__PURE__ */ jsx(Box, {
|
|
45
|
+
css: styles.avatar,
|
|
46
|
+
children: avatar
|
|
47
|
+
}), /* @__PURE__ */ jsxs(Box, {
|
|
48
|
+
css: styles.textBlock,
|
|
49
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
50
|
+
css: styles.titleRow,
|
|
51
|
+
children: [/* @__PURE__ */ jsx(BitkitOverflowTooltip, {
|
|
52
|
+
disabled: !canShowTitleTooltip,
|
|
53
|
+
placement: "bottom-start",
|
|
54
|
+
text: title,
|
|
55
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
56
|
+
as: "h1",
|
|
57
|
+
css: styles.title,
|
|
58
|
+
ref: titleRef,
|
|
59
|
+
tabIndex: canShowTitleTooltip ? 0 : void 0,
|
|
60
|
+
children: title
|
|
61
|
+
})
|
|
62
|
+
}), hasBadge && /* @__PURE__ */ jsx(Box, {
|
|
63
|
+
css: styles.badge,
|
|
64
|
+
children: badge
|
|
65
|
+
})]
|
|
66
|
+
}), hasSecondaryText && secondaryText !== void 0 && /* @__PURE__ */ jsx(BitkitOverflowTooltip, {
|
|
67
|
+
disabled: !isSecondaryTruncated,
|
|
68
|
+
placement: "bottom-start",
|
|
69
|
+
text: secondaryText,
|
|
70
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
71
|
+
css: styles.secondaryText,
|
|
72
|
+
ref: secondaryRef,
|
|
73
|
+
tabIndex: isSecondaryTruncated ? 0 : void 0,
|
|
74
|
+
children: secondaryText
|
|
75
|
+
})
|
|
76
|
+
})]
|
|
77
|
+
})]
|
|
78
|
+
});
|
|
37
79
|
return /* @__PURE__ */ jsxs(Box, {
|
|
38
80
|
as: "header",
|
|
39
81
|
css: [styles.root, css],
|
|
@@ -51,42 +93,26 @@ var BitkitPageHeader = forwardRef((props, ref) => {
|
|
|
51
93
|
/* @__PURE__ */ jsxs(Box, {
|
|
52
94
|
css: styles.contentBlock,
|
|
53
95
|
children: [
|
|
54
|
-
/* @__PURE__ */ jsxs(Box, {
|
|
55
|
-
css: styles.
|
|
56
|
-
children: [
|
|
57
|
-
css: styles.
|
|
58
|
-
children:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
placement: "bottom-start",
|
|
66
|
-
text: title,
|
|
67
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
68
|
-
as: "h1",
|
|
69
|
-
css: styles.title,
|
|
70
|
-
ref: titleRef,
|
|
71
|
-
tabIndex: canShowTitleTooltip ? 0 : void 0,
|
|
72
|
-
children: title
|
|
73
|
-
})
|
|
74
|
-
}), hasBadge && /* @__PURE__ */ jsx(Box, {
|
|
75
|
-
css: styles.badge,
|
|
76
|
-
children: badge
|
|
77
|
-
})]
|
|
78
|
-
}), hasSecondaryText && secondaryText !== void 0 && /* @__PURE__ */ jsx(BitkitOverflowTooltip, {
|
|
79
|
-
disabled: !isSecondaryTruncated,
|
|
80
|
-
placement: "bottom-start",
|
|
81
|
-
text: secondaryText,
|
|
82
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
83
|
-
css: styles.secondaryText,
|
|
84
|
-
ref: secondaryRef,
|
|
85
|
-
tabIndex: isSecondaryTruncated ? 0 : void 0,
|
|
86
|
-
children: secondaryText
|
|
87
|
-
})
|
|
88
|
-
})]
|
|
96
|
+
hasSidebar ? /* @__PURE__ */ jsxs(Box, {
|
|
97
|
+
css: styles.titleBar,
|
|
98
|
+
children: [titleBlock, /* @__PURE__ */ jsx(Box, {
|
|
99
|
+
css: styles.menuButton,
|
|
100
|
+
children: /* @__PURE__ */ jsx(BitkitIconButton, {
|
|
101
|
+
icon: IconMenuHamburger,
|
|
102
|
+
label: "Open navigation",
|
|
103
|
+
onClick: onMenuClick,
|
|
104
|
+
size: "sm",
|
|
105
|
+
variant: "secondary"
|
|
106
|
+
})
|
|
89
107
|
})]
|
|
108
|
+
}) : titleBlock,
|
|
109
|
+
hasSectionTitle && /* @__PURE__ */ jsx(Box, {
|
|
110
|
+
css: styles.sectionBlock,
|
|
111
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
112
|
+
as: "h2",
|
|
113
|
+
css: styles.sectionTitle,
|
|
114
|
+
children: sectionTitle
|
|
115
|
+
})
|
|
90
116
|
}),
|
|
91
117
|
!hasBreadcrumb && controlsNode,
|
|
92
118
|
hasActions && /* @__PURE__ */ jsx(Box, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitPageHeader.js","names":[],"sources":["../../../lib/components/BitkitPageHeader/BitkitPageHeader.tsx"],"sourcesContent":["import { Box, type BoxProps } from '@chakra-ui/react/box';\nimport { useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, type ReactNode } from 'react';\n\nimport { isEmptyNode } from '../../utilities/isEmptyNode';\nimport useIsTruncated from '../../utilities/useIsTruncated';\nimport BitkitOverflowTooltip from '../BitkitOverflowTooltip/BitkitOverflowTooltip';\n\nexport interface BitkitPageHeaderProps extends Omit<BoxProps, 'title'> {\n /**\n * Avatar, app icon or thumbnail rendered before the title. Hidden in the collapsed layout.\n *\n * Belongs to the sub-page layouts: the design's main-page header is a title and its subtitle with\n * no avatar, so pass this alongside a `breadcrumb`.\n *\n * Size it to match the design's two sub-page cards, since the node is yours to build: `size=\"40\"`\n * (8px radius) against a bare title, and `size=\"48\"` (10px radius) once `secondaryText` is\n * present — the same avatar the `app` and `user` content shapes use.\n */\n avatar?: ReactNode;\n /** Badge rendered next to the title. Hidden in the collapsed layout. */\n badge?: ReactNode;\n /**\n * Breadcrumb trail above the title — pass a `BitkitBreadcrumb`. Its presence is what switches the\n * header from the design's main-page layout to a sub-page one, so there is no separate page-type\n * prop: the recipe's own `hasBreadcrumb` variant is derived from it.\n *\n * The two layouts carry more than the trail. A main-page header is a title and its subtitle —\n * top-level pages and product overviews — with no avatar and no `app`/`user` content shape. Those\n * belong to sub-pages, so pass them together with a trail.\n */\n breadcrumb?: ReactNode;\n /**\n * Trailing action buttons. Stacked full-width below the title on mobile, primary action on top.\n *\n * The collapsed row is built for `size=\"sm\"` buttons and the expanded header for `size=\"md\"`.\n * Actions arrive as children, so the component cannot set that for you — pass a flat `size=\"sm\"`\n * whenever `minimal` or `isSticky` is in play.\n *\n * Flat, not responsive: Bitkit icons render a different SVG asset per size, so `BitkitButton`\n * resolves `size` in JS and a `{ base, tablet }` object matches no key, falling through to a 24px\n * icon in a 32px button. Since the header collapses in CSS from `tablet` up, a `sm` action is a\n * little small in the expanded mobile layout — but the design draws no mobile frame for the\n * collapsed variants, so `sm` is the closer of the two.\n */\n children?: ReactNode;\n /**\n * Where the content block sits when `contentWidth` caps it: `center` over a centred content\n * column, `start` over one that starts against a sidebar beside the header. Pass the same\n * alignment as that column, so the title and the content share their left edge at every width.\n *\n * No effect with `contentWidth=\"full\"` or without `contentWidth`, where nothing is capped.\n */\n contentAlign?: 'center' | 'start';\n /**\n * Shape of the title block, mirroring the design's `content` property. `title` is a page title\n * with an optional subtitle; `app` and `user` are the tighter entity layouts — a larger avatar\n * centred against a smaller title and 14px secondary text. They differ only in the gap between\n * the avatar and the text, 16 and 12 respectively.\n */\n contentVariant?: 'title' | 'app' | 'user';\n /**\n * Lines the header's content up with the page content below it. The root stays full-width, so\n * the background and the bottom border still run edge to edge — which a wrapper around the whole\n * header could not do.\n *\n * Pass the same value as that column's `max-width`, as rems — `rem()` builds one from a pixel\n * figure — and the content is capped with it, centred unless `contentAlign` says otherwise. Box-sizing is border-box throughout\n * Bitkit, so the value includes the header's own inline padding, which this layout puts at 32 on\n * both sides rather than the default 24 and 32.\n *\n * `'full'` is the same layout without the cap, for a content column that runs the full width: the\n * 32 on both sides, nothing to centre. The default 24 is for a header that runs over a sidebar and\n * lines up with its items instead.\n *\n * Its presence is the toggle: without it the header keeps the default inset.\n *\n * The cap applies at every breakpoint, deliberately — a content column's own `max-width` is not\n * breakpoint-scoped either, so scoping this one would misalign the two wherever the column is\n * narrow enough to bite on mobile. What does change with the breakpoint is the inline padding,\n * 16 below `tablet` and 32 above, which is the same pair such a column uses.\n */\n contentWidth?: `${number}rem` | 'full';\n /**\n * Inline controls between the trail and the actions — a view switcher, filters, a settings menu.\n * Their presence spreads the row into three groups instead of the usual trail-and-actions pair,\n * which is the shape the design file calls `PageHeader-WFE`.\n *\n * Belongs to the collapsed row, so it is honoured by `variant=\"minimal\"` *and* by `isSticky` —\n * the two produce the same row — and ignored in the expanded layouts, which the design draws no\n * controls in.\n */\n controls?: ReactNode;\n /**\n * Renders the compact single-row layout used while the header is stuck to the top of the viewport.\n * Appearance only — the consumer owns scroll detection and any portalling, since the sticky offset\n * and header container are application concerns.\n *\n * Ignored below the `tablet` breakpoint, where the header always stays expanded.\n *\n * Pair it with `size=\"sm\"` actions — see `children`.\n */\n isSticky?: boolean;\n /**\n * Descriptive text below the title. Hidden in the collapsed layout.\n *\n * A string, like `title`, so it can be truncated to one line and carried in full by the tooltip\n * that appears when it does not fit.\n */\n secondaryText?: string;\n /**\n * Draws the line along the header's bottom edge. Set it to `false` for a header that flows\n * straight into the page, and the bottom padding goes with the line: the header's background is\n * the page's, so without the line that padding would only be a margin the page cannot shrink. The\n * page spaces its content from the header itself.\n *\n * The collapsed row keeps its padding, which centres the row rather than spacing the page, and from\n * `tablet` a header with `isSticky` keeps its line as well, since content scrolls under it. Below\n * `tablet` `isSticky` is ignored, so there the line goes too.\n *\n * Ignored with `tabs`, whose list sits on that edge. That includes `variant=\"minimal\"`, which hides\n * the tab list from `tablet`: the line stays although no tabs show above it.\n */\n showDivider?: boolean;\n /**\n * Tab bar along the bottom edge — pass a `BitkitTabs.List`. Hidden in the collapsed layout.\n *\n * The node is rendered inside the header, so tab panels cannot be siblings of the row. To drive\n * page content from it, put `BitkitTabs.Root` around the header *and* the content, and pass only\n * the list here — `BitkitTabs.List` reads its Root from context and throws without an ancestor:\n *\n * ```tsx\n * <BitkitTabs.Root defaultValue=\"overview\">\n * <BitkitPageHeader tabs={<BitkitTabs.List>…</BitkitTabs.List>} title=\"Build Hub\" />\n * <BitkitTabs.ContentGroup>\n * <BitkitTabs.Content padding=\"32\" value=\"overview\">…</BitkitTabs.Content>\n * </BitkitTabs.ContentGroup>\n * </BitkitTabs.Root>\n * ```\n *\n * Only the row is hidden when the header collapses — the panels live outside it and keep\n * rendering, so the page never blanks.\n */\n tabs?: ReactNode;\n /**\n * The page title, as a string. Everything passed here lands inside the `h1`, so it is the\n * heading's accessible name and nothing else belongs in it — the avatar, badge and secondary\n * text each have their own slot. Required: it is what gives the header a page heading at every\n * breakpoint.\n *\n * Truncated to a single line with an ellipsis. When it does not fit, hovering or focusing it\n * shows the full string in a tooltip — so a long title never grows the header.\n *\n * In the collapsed layout (`minimal` or `isSticky`) *with* a `breadcrumb`, the design shows the\n * title in the trail rather than as a heading, so end the breadcrumb with a\n * `BitkitBreadcrumb.CurrentItem` carrying it — the title is then rendered as a visually hidden\n * `h1`, since `Breadcrumb.CurrentLink` is a link and not a heading. Below `tablet` the header\n * never collapses and the visible heading is used instead (`BitkitBreadcrumb` drops its\n * `CurrentItem` on mobile).\n */\n title: string;\n /**\n * `minimal` collapses the header to the single breadcrumb row used by sub-pages that don't repeat\n * their title as a heading. Like `isSticky`, it only collapses from `tablet` up, and expects\n * `size=\"sm\"` actions — see `children`.\n */\n variant?: 'default' | 'minimal';\n}\n\nconst BitkitPageHeader = forwardRef<HTMLElement, BitkitPageHeaderProps>((props, ref) => {\n const {\n avatar,\n badge,\n breadcrumb,\n children,\n contentAlign,\n contentVariant,\n contentWidth,\n controls,\n css,\n isSticky,\n secondaryText,\n showDivider = true,\n tabs,\n title,\n variant,\n ...rest\n } = props;\n\n // Which slots the collapsed row drops, and whether it collapses at all, is decided entirely by\n // the recipe's `tablet`-scoped styles. Nothing here branches on the viewport: a breakpoint hook\n // would render the wrong layout on first paint, since Chakra's `useMediaQuery` returns its\n // fallback until after mount.\n\n // A slot's presence is its own toggle, so all of them share one emptiness test rather than plain\n // truthiness: an array whose entries all render nothing — `items.map()` where every branch is\n // `null` — is still truthy, and would otherwise render a wrapper that only contributes gap. For\n // `controls` it would also spread the row into three groups around an empty middle one.\n const hasAvatar = !isEmptyNode(avatar);\n const hasBadge = !isEmptyNode(badge);\n const hasBreadcrumb = !isEmptyNode(breadcrumb);\n const hasActions = !isEmptyNode(children);\n const hasSecondaryText = !isEmptyNode(secondaryText);\n const hasTabs = !isEmptyNode(tabs);\n\n // `BitkitOverflowTooltip` does no measuring of its own — it shows the tooltip unless told not to,\n // so each truncating text reports whether it is actually clipped and suppresses its own tooltip\n // when it is not. Keyed on the text so a change re-measures without waiting for a resize.\n const { isTruncated: isTitleTruncated, ref: titleRef } = useIsTruncated<HTMLHeadingElement>(title);\n const { isTruncated: isSecondaryTruncated, ref: secondaryRef } = useIsTruncated<HTMLParagraphElement>(secondaryText);\n\n // The design only draws inline controls in the collapsed row, so they are ignored in the\n // expanded layouts rather than left to land somewhere undesigned. `minimal` and `isSticky`\n // produce that row through the same `collapsedSlots`, so both have to honour them — gating on\n // `minimal` alone left `isSticky` rendering the identical row with the middle group missing.\n const isCollapsed = variant === 'minimal' || Boolean(isSticky);\n const showControls = !isEmptyNode(controls) && isCollapsed;\n\n // The collapsed row with a trail makes the title block `srOnly`, which is a 1px box — so the\n // measurement reports \"truncated\" for a heading nobody can see. Left alone that puts a tab stop\n // on an invisible element and opens a tooltip against it. The trail already carries the title as\n // its current item in exactly that layout, so there is nothing to surface anyway.\n const canShowTitleTooltip = isTitleTruncated && !(isCollapsed && hasBreadcrumb);\n\n const recipe = useSlotRecipe({ key: 'pageHeader' });\n const styles = recipe({\n contentVariant,\n hasBreadcrumb,\n hasControls: showControls,\n hasDivider: showDivider || hasTabs,\n // The cap is a length the consumer chooses, so it cannot be a recipe variant -- the variant\n // carries the layout switch and the `maxWidth` below carries the value.\n alignsToContent: Boolean(contentWidth),\n contentAlign,\n isSticky,\n variant,\n });\n\n // The controls are the middle of three groups, so they follow whichever element holds the left\n // slot: the trail when there is one, otherwise the title — which lives inside `contentBlock`, so\n // they have to go in there with it rather than beside it.\n const controlsNode = showControls ? <Box css={styles.controls}>{controls}</Box> : null;\n\n return (\n <Box as=\"header\" css={[styles.root, css]} ref={ref} {...rest}>\n <Box css={styles.headerBlock} maxWidth={contentWidth === 'full' ? undefined : contentWidth}>\n {hasBreadcrumb && <Box css={styles.breadcrumb}>{breadcrumb}</Box>}\n\n {hasBreadcrumb && controlsNode}\n\n <Box css={styles.contentBlock}>\n <Box css={styles.titleBlock}>\n {hasAvatar && <Box css={styles.avatar}>{avatar}</Box>}\n\n <Box css={styles.textBlock}>\n <Box css={styles.titleRow}>\n {/*\n `bottom-start` rather than the tooltip's default `right`: these texts are\n full-width rows, so a right-hand tooltip lands over the actions. Opening below and\n aligned to the text's start keeps it clear of them and lines it up with the text\n it repeats.\n */}\n <BitkitOverflowTooltip disabled={!canShowTitleTooltip} placement=\"bottom-start\" text={title}>\n {/*\n `tabIndex` only while truncated: the tooltip trigger is rendered `asChild`, so an\n `h1` stays unfocusable and the full title would be mouse-only. Adding the tab\n stop unconditionally would put one in every header for no benefit.\n */}\n <Text as=\"h1\" css={styles.title} ref={titleRef} tabIndex={canShowTitleTooltip ? 0 : undefined}>\n {title}\n </Text>\n </BitkitOverflowTooltip>\n {hasBadge && <Box css={styles.badge}>{badge}</Box>}\n </Box>\n\n {hasSecondaryText && secondaryText !== undefined && (\n <BitkitOverflowTooltip disabled={!isSecondaryTruncated} placement=\"bottom-start\" text={secondaryText}>\n <Text css={styles.secondaryText} ref={secondaryRef} tabIndex={isSecondaryTruncated ? 0 : undefined}>\n {secondaryText}\n </Text>\n </BitkitOverflowTooltip>\n )}\n </Box>\n </Box>\n\n {!hasBreadcrumb && controlsNode}\n\n {hasActions && <Box css={styles.actionBlock}>{children}</Box>}\n </Box>\n </Box>\n\n {hasTabs && <Box css={styles.tabs}>{tabs}</Box>}\n </Box>\n );\n});\n\nBitkitPageHeader.displayName = 'BitkitPageHeader';\n\nexport default BitkitPageHeader;\n"],"mappings":";;;;;;;;;AA0KA,IAAM,mBAAmB,YAAgD,OAAO,QAAQ;CACtF,MAAM,EACJ,QACA,OACA,YACA,UACA,cACA,gBACA,cACA,UACA,KACA,UACA,eACA,cAAc,MACd,MACA,OACA,SACA,GAAG,SACD;CAWJ,MAAM,YAAY,CAAC,YAAY,MAAM;CACrC,MAAM,WAAW,CAAC,YAAY,KAAK;CACnC,MAAM,gBAAgB,CAAC,YAAY,UAAU;CAC7C,MAAM,aAAa,CAAC,YAAY,QAAQ;CACxC,MAAM,mBAAmB,CAAC,YAAY,aAAa;CACnD,MAAM,UAAU,CAAC,YAAY,IAAI;CAKjC,MAAM,EAAE,aAAa,kBAAkB,KAAK,aAAa,eAAmC,KAAK;CACjG,MAAM,EAAE,aAAa,sBAAsB,KAAK,iBAAiB,eAAqC,aAAa;CAMnH,MAAM,cAAc,YAAY,aAAa,QAAQ,QAAQ;CAC7D,MAAM,eAAe,CAAC,YAAY,QAAQ,KAAK;CAM/C,MAAM,sBAAsB,oBAAoB,EAAE,eAAe;CAGjE,MAAM,SADS,cAAc,EAAE,KAAK,aAAa,CAClC,CAAA,CAAO;EACpB;EACA;EACA,aAAa;EACb,YAAY,eAAe;EAG3B,iBAAiB,QAAQ,YAAY;EACrC;EACA;EACA;CACF,CAAC;CAKD,MAAM,eAAe,eAAe,oBAAC,KAAD;EAAK,KAAK,OAAO;EAAW,UAAA;CAAc,CAAA,IAAI;CAElF,OACE,qBAAC,KAAD;EAAK,IAAG;EAAS,KAAK,CAAC,OAAO,MAAM,GAAG;EAAQ;EAAK,GAAI;EAAxD,UAAA,CACE,qBAAC,KAAD;GAAK,KAAK,OAAO;GAAa,UAAU,iBAAiB,SAAS,KAAA,IAAY;GAA9E,UAAA;IACG,iBAAiB,oBAAC,KAAD;KAAK,KAAK,OAAO;KAAa,UAAA;IAAgB,CAAA;IAE/D,iBAAiB;IAElB,qBAAC,KAAD;KAAK,KAAK,OAAO;KAAjB,UAAA;MACE,qBAAC,KAAD;OAAK,KAAK,OAAO;OAAjB,UAAA,CACG,aAAa,oBAAC,KAAD;QAAK,KAAK,OAAO;QAAS,UAAA;OAAY,CAAA,GAEpD,qBAAC,KAAD;QAAK,KAAK,OAAO;QAAjB,UAAA,CACE,qBAAC,KAAD;SAAK,KAAK,OAAO;SAAjB,UAAA,CAOE,oBAAC,uBAAD;UAAuB,UAAU,CAAC;UAAqB,WAAU;UAAe,MAAM;UAMpF,UAAA,oBAAC,MAAD;WAAM,IAAG;WAAK,KAAK,OAAO;WAAO,KAAK;WAAU,UAAU,sBAAsB,IAAI,KAAA;WACjF,UAAA;UACG,CAAA;SACe,CAAA,GACtB,YAAY,oBAAC,KAAD;UAAK,KAAK,OAAO;UAAQ,UAAA;SAAW,CAAA,CAC9C;QAEJ,CAAA,GAAA,oBAAoB,kBAAkB,KAAA,KACrC,oBAAC,uBAAD;SAAuB,UAAU,CAAC;SAAsB,WAAU;SAAe,MAAM;SACrF,UAAA,oBAAC,MAAD;UAAM,KAAK,OAAO;UAAe,KAAK;UAAc,UAAU,uBAAuB,IAAI,KAAA;UACtF,UAAA;SACG,CAAA;QACe,CAAA,CAEtB;OACF,CAAA,CAAA;;MAEJ,CAAC,iBAAiB;MAElB,cAAc,oBAAC,KAAD;OAAK,KAAK,OAAO;OAAc;MAAc,CAAA;KACzD;;GACF;EAEJ,CAAA,GAAA,WAAW,oBAAC,KAAD;GAAK,KAAK,OAAO;GAAO,UAAA;EAAU,CAAA,CAC3C;;AAET,CAAC;AAED,iBAAiB,cAAc"}
|
|
1
|
+
{"version":3,"file":"BitkitPageHeader.js","names":[],"sources":["../../../lib/components/BitkitPageHeader/BitkitPageHeader.tsx"],"sourcesContent":["import { Box, type BoxProps } from '@chakra-ui/react/box';\nimport { useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, type ReactNode } from 'react';\n\nimport IconMenuHamburger from '../../icons/IconMenuHamburger';\nimport { isEmptyNode } from '../../utilities/isEmptyNode';\nimport useIsTruncated from '../../utilities/useIsTruncated';\nimport BitkitIconButton from '../BitkitIconButton/BitkitIconButton';\nimport BitkitOverflowTooltip from '../BitkitOverflowTooltip/BitkitOverflowTooltip';\n\nexport interface BitkitPageHeaderProps extends Omit<BoxProps, 'title'> {\n /**\n * Avatar, app icon or thumbnail rendered before the title. Hidden in the collapsed layout.\n *\n * Belongs to the sub-page layouts: the design's main-page header is a title and its subtitle with\n * no avatar, so pass this alongside a `breadcrumb`.\n *\n * Size it to match the design's two sub-page cards, since the node is yours to build: `size=\"40\"`\n * (8px radius) against a bare title, and `size=\"48\"` (10px radius) once `secondaryText` is\n * present — the same avatar the `app` and `user` content shapes use.\n */\n avatar?: ReactNode;\n /** Badge rendered next to the title. Hidden in the collapsed layout. */\n badge?: ReactNode;\n /**\n * Breadcrumb trail above the title — pass a `BitkitBreadcrumb`. Its presence is what switches the\n * header from the design's main-page layout to a sub-page one, so there is no separate page-type\n * prop: the recipe's own `hasBreadcrumb` variant is derived from it.\n *\n * The two layouts carry more than the trail. A main-page header is a title and its subtitle —\n * top-level pages and product overviews — with no avatar and no `app`/`user` content shape. Those\n * belong to sub-pages, so pass them together with a trail.\n */\n breadcrumb?: ReactNode;\n /**\n * Trailing action buttons. Stacked full-width below the title on mobile, primary action on top.\n *\n * The collapsed row is built for `size=\"sm\"` buttons and the expanded header for `size=\"md\"`.\n * Actions arrive as children, so the component cannot set that for you — pass a flat `size=\"sm\"`\n * whenever `minimal` or `isSticky` is in play.\n *\n * Flat, not responsive: Bitkit icons render a different SVG asset per size, so `BitkitButton`\n * resolves `size` in JS and a `{ base, tablet }` object matches no key, falling through to a 24px\n * icon in a 32px button. Since the header collapses in CSS from `tablet` up, a `sm` action is a\n * little small in the expanded mobile layout — but the design draws no mobile frame for the\n * collapsed variants, so `sm` is the closer of the two.\n */\n children?: ReactNode;\n /**\n * Where the content block sits when `contentWidth` caps it: `center` over a centred content\n * column, `start` over one that starts against a sidebar beside the header. Pass the same\n * alignment as that column, so the title and the content share their left edge at every width.\n *\n * No effect with `contentWidth=\"full\"` or without `contentWidth`, where nothing is capped.\n */\n contentAlign?: 'center' | 'start';\n /**\n * Shape of the title block, mirroring the design's `content` property. `title` is a page title\n * with an optional subtitle; `app` and `user` are the tighter entity layouts — a larger avatar\n * centred against a smaller title and 14px secondary text. They differ only in the gap between\n * the avatar and the text, 16 and 12 respectively.\n */\n contentVariant?: 'title' | 'app' | 'user';\n /**\n * Lines the header's content up with the page content below it. The root stays full-width, so\n * the background and the bottom border still run edge to edge — which a wrapper around the whole\n * header could not do.\n *\n * Pass the same value as that column's `max-width`, as rems — `rem()` builds one from a pixel\n * figure — and the content is capped with it, centred unless `contentAlign` says otherwise. Box-sizing is border-box throughout\n * Bitkit, so the value includes the header's own inline padding, which this layout puts at 32 on\n * both sides rather than the default 24 and 32.\n *\n * `'full'` is the same layout without the cap, for a content column that runs the full width: the\n * 32 on both sides, nothing to centre. The default 24 is for a header that runs over a sidebar and\n * lines up with its items instead.\n *\n * Its presence is the toggle: without it the header keeps the default inset.\n *\n * The cap applies at every breakpoint, deliberately — a content column's own `max-width` is not\n * breakpoint-scoped either, so scoping this one would misalign the two wherever the column is\n * narrow enough to bite on mobile. What does change with the breakpoint is the inline padding,\n * 16 below `tablet` and 32 above, which is the same pair such a column uses.\n */\n contentWidth?: `${number}rem` | 'full';\n /**\n * Inline controls between the trail and the actions — a view switcher, filters, a settings menu.\n * Their presence spreads the row into three groups instead of the usual trail-and-actions pair,\n * which is the shape the design file calls `PageHeader-WFE`.\n *\n * Belongs to the collapsed row, so it is honoured by `variant=\"minimal\"` *and* by `isSticky` —\n * the two produce the same row — and ignored in the expanded layouts, which the design draws no\n * controls in.\n */\n controls?: ReactNode;\n /**\n * Renders the compact single-row layout used while the header is stuck to the top of the viewport.\n * Appearance only — the consumer owns scroll detection and any portalling, since the sticky offset\n * and header container are application concerns.\n *\n * Ignored below the `tablet` breakpoint, where the header always stays expanded.\n *\n * Pair it with `size=\"sm\"` actions — see `children`.\n */\n isSticky?: boolean;\n /**\n * Opens the page's sidebar on mobile. Its presence switches the header to the design's\n * `hasSidebar` layout below `tablet`: the title shrinks beside a menu button, and the\n * `sectionTitle` takes over as the large heading under a rule, with the actions below it. Wire it\n * to a `BitkitPageSidebar`'s `open` / `onOpenChange`. From `tablet` the button is hidden and the\n * header is the ordinary one — the sidebar is beside the page there.\n */\n onMenuClick?: () => void;\n /**\n * Descriptive text below the title. Hidden in the collapsed layout.\n *\n * A string, like `title`, so it can be truncated to one line and carried in full by the tooltip\n * that appears when it does not fit.\n */\n secondaryText?: string;\n /**\n * Draws the line along the header's bottom edge. Set it to `false` for a header that flows\n * straight into the page, and the bottom padding goes with the line: the header's background is\n * the page's, so without the line that padding would only be a margin the page cannot shrink. The\n * page spaces its content from the header itself.\n *\n * The collapsed row keeps its padding, which centres the row rather than spacing the page, and from\n * `tablet` a header with `isSticky` keeps its line as well, since content scrolls under it. Below\n * `tablet` `isSticky` is ignored, so there the line goes too.\n *\n * Ignored with `tabs`, whose list sits on that edge. That includes `variant=\"minimal\"`, which hides\n * the tab list from `tablet`: the line stays although no tabs show above it.\n */\n showDivider?: boolean;\n /**\n * The sidebar section the page is showing, for the `hasSidebar` layout — see `onMenuClick`. It\n * is the page's visible `h2` on mobile, where the sidebar that would name it is behind the menu\n * button. Not rendered from `tablet`, and never without `onMenuClick`.\n */\n sectionTitle?: string;\n /**\n * Tab bar along the bottom edge — pass a `BitkitTabs.List`. Hidden in the collapsed layout.\n *\n * The node is rendered inside the header, so tab panels cannot be siblings of the row. To drive\n * page content from it, put `BitkitTabs.Root` around the header *and* the content, and pass only\n * the list here — `BitkitTabs.List` reads its Root from context and throws without an ancestor:\n *\n * ```tsx\n * <BitkitTabs.Root defaultValue=\"overview\">\n * <BitkitPageHeader tabs={<BitkitTabs.List>…</BitkitTabs.List>} title=\"Build Hub\" />\n * <BitkitTabs.ContentGroup>\n * <BitkitTabs.Content padding=\"32\" value=\"overview\">…</BitkitTabs.Content>\n * </BitkitTabs.ContentGroup>\n * </BitkitTabs.Root>\n * ```\n *\n * Only the row is hidden when the header collapses — the panels live outside it and keep\n * rendering, so the page never blanks.\n */\n tabs?: ReactNode;\n /**\n * The page title, as a string. Everything passed here lands inside the `h1`, so it is the\n * heading's accessible name and nothing else belongs in it — the avatar, badge and secondary\n * text each have their own slot. Required: it is what gives the header a page heading at every\n * breakpoint.\n *\n * Truncated to a single line with an ellipsis. When it does not fit, hovering or focusing it\n * shows the full string in a tooltip — so a long title never grows the header.\n *\n * In the collapsed layout (`minimal` or `isSticky`) *with* a `breadcrumb`, the design shows the\n * title in the trail rather than as a heading, so end the breadcrumb with a\n * `BitkitBreadcrumb.CurrentItem` carrying it — the title is then rendered as a visually hidden\n * `h1`, since `Breadcrumb.CurrentLink` is a link and not a heading. Below `tablet` the header\n * never collapses and the visible heading is used instead (`BitkitBreadcrumb` drops its\n * `CurrentItem` on mobile).\n */\n title: string;\n /**\n * `minimal` collapses the header to the single breadcrumb row used by sub-pages that don't repeat\n * their title as a heading. Like `isSticky`, it only collapses from `tablet` up, and expects\n * `size=\"sm\"` actions — see `children`.\n */\n variant?: 'default' | 'minimal';\n}\n\nconst BitkitPageHeader = forwardRef<HTMLElement, BitkitPageHeaderProps>((props, ref) => {\n const {\n avatar,\n badge,\n breadcrumb,\n children,\n contentAlign,\n contentVariant,\n contentWidth,\n controls,\n css,\n isSticky,\n onMenuClick,\n secondaryText,\n sectionTitle,\n showDivider = true,\n tabs,\n title,\n variant,\n ...rest\n } = props;\n\n // Which slots the collapsed row drops, and whether it collapses at all, is decided entirely by\n // the recipe's `tablet`-scoped styles. Nothing here branches on the viewport: a breakpoint hook\n // would render the wrong layout on first paint, since Chakra's `useMediaQuery` returns its\n // fallback until after mount.\n\n // A slot's presence is its own toggle, so all of them share one emptiness test rather than plain\n // truthiness: an array whose entries all render nothing — `items.map()` where every branch is\n // `null` — is still truthy, and would otherwise render a wrapper that only contributes gap. For\n // `controls` it would also spread the row into three groups around an empty middle one.\n const hasAvatar = !isEmptyNode(avatar);\n const hasBadge = !isEmptyNode(badge);\n const hasBreadcrumb = !isEmptyNode(breadcrumb);\n const hasActions = !isEmptyNode(children);\n const hasSecondaryText = !isEmptyNode(secondaryText);\n const hasSidebar = onMenuClick !== undefined;\n const hasSectionTitle = hasSidebar && !isEmptyNode(sectionTitle);\n const hasTabs = !isEmptyNode(tabs);\n\n // `BitkitOverflowTooltip` does no measuring of its own — it shows the tooltip unless told not to,\n // so each truncating text reports whether it is actually clipped and suppresses its own tooltip\n // when it is not. Keyed on the text so a change re-measures without waiting for a resize.\n const { isTruncated: isTitleTruncated, ref: titleRef } = useIsTruncated<HTMLHeadingElement>(title);\n const { isTruncated: isSecondaryTruncated, ref: secondaryRef } = useIsTruncated<HTMLParagraphElement>(secondaryText);\n\n // The design only draws inline controls in the collapsed row, so they are ignored in the\n // expanded layouts rather than left to land somewhere undesigned. `minimal` and `isSticky`\n // produce that row through the same `collapsedSlots`, so both have to honour them — gating on\n // `minimal` alone left `isSticky` rendering the identical row with the middle group missing.\n const isCollapsed = variant === 'minimal' || Boolean(isSticky);\n const showControls = !isEmptyNode(controls) && isCollapsed;\n\n // The collapsed row with a trail makes the title block `srOnly`, which is a 1px box — so the\n // measurement reports \"truncated\" for a heading nobody can see. Left alone that puts a tab stop\n // on an invisible element and opens a tooltip against it. The trail already carries the title as\n // its current item in exactly that layout, so there is nothing to surface anyway.\n const canShowTitleTooltip = isTitleTruncated && !(isCollapsed && hasBreadcrumb);\n\n const recipe = useSlotRecipe({ key: 'pageHeader' });\n const styles = recipe({\n contentVariant,\n hasBreadcrumb,\n hasControls: showControls,\n hasDivider: showDivider || hasTabs,\n hasSidebar,\n // The cap is a length the consumer chooses, so it cannot be a recipe variant -- the variant\n // carries the layout switch and the `maxWidth` below carries the value.\n alignsToContent: Boolean(contentWidth),\n contentAlign,\n isSticky,\n variant,\n });\n\n // The controls are the middle of three groups, so they follow whichever element holds the left\n // slot: the trail when there is one, otherwise the title — which lives inside `contentBlock`, so\n // they have to go in there with it rather than beside it.\n const controlsNode = showControls ? <Box css={styles.controls}>{controls}</Box> : null;\n\n // With a sidebar the menu button shares the title's row, so the block gets a wrapper the content\n // row treats as the title block. Other headers keep their DOM.\n const titleBlock = (\n <Box css={styles.titleBlock}>\n {hasAvatar && <Box css={styles.avatar}>{avatar}</Box>}\n\n <Box css={styles.textBlock}>\n <Box css={styles.titleRow}>\n {/*\n `bottom-start` rather than the tooltip's default `right`: these texts are\n full-width rows, so a right-hand tooltip lands over the actions. Opening below and\n aligned to the text's start keeps it clear of them and lines it up with the text\n it repeats.\n */}\n <BitkitOverflowTooltip disabled={!canShowTitleTooltip} placement=\"bottom-start\" text={title}>\n {/*\n `tabIndex` only while truncated: the tooltip trigger is rendered `asChild`, so an\n `h1` stays unfocusable and the full title would be mouse-only. Adding the tab\n stop unconditionally would put one in every header for no benefit.\n */}\n <Text as=\"h1\" css={styles.title} ref={titleRef} tabIndex={canShowTitleTooltip ? 0 : undefined}>\n {title}\n </Text>\n </BitkitOverflowTooltip>\n {hasBadge && <Box css={styles.badge}>{badge}</Box>}\n </Box>\n\n {hasSecondaryText && secondaryText !== undefined && (\n <BitkitOverflowTooltip disabled={!isSecondaryTruncated} placement=\"bottom-start\" text={secondaryText}>\n <Text css={styles.secondaryText} ref={secondaryRef} tabIndex={isSecondaryTruncated ? 0 : undefined}>\n {secondaryText}\n </Text>\n </BitkitOverflowTooltip>\n )}\n </Box>\n </Box>\n );\n\n return (\n <Box as=\"header\" css={[styles.root, css]} ref={ref} {...rest}>\n <Box css={styles.headerBlock} maxWidth={contentWidth === 'full' ? undefined : contentWidth}>\n {hasBreadcrumb && <Box css={styles.breadcrumb}>{breadcrumb}</Box>}\n\n {hasBreadcrumb && controlsNode}\n\n <Box css={styles.contentBlock}>\n {hasSidebar ? (\n <Box css={styles.titleBar}>\n {titleBlock}\n <Box css={styles.menuButton}>\n <BitkitIconButton\n icon={IconMenuHamburger}\n label=\"Open navigation\"\n onClick={onMenuClick}\n size=\"sm\"\n variant=\"secondary\"\n />\n </Box>\n </Box>\n ) : (\n titleBlock\n )}\n\n {hasSectionTitle && (\n <Box css={styles.sectionBlock}>\n <Text as=\"h2\" css={styles.sectionTitle}>\n {sectionTitle}\n </Text>\n </Box>\n )}\n\n {!hasBreadcrumb && controlsNode}\n\n {hasActions && <Box css={styles.actionBlock}>{children}</Box>}\n </Box>\n </Box>\n\n {hasTabs && <Box css={styles.tabs}>{tabs}</Box>}\n </Box>\n );\n});\n\nBitkitPageHeader.displayName = 'BitkitPageHeader';\n\nexport default BitkitPageHeader;\n"],"mappings":"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"}
|
|
@@ -7,6 +7,21 @@ import { BitkitColorToken } from '../../theme/themeUtils';
|
|
|
7
7
|
import { BitkitGroupHeadingProps } from '../BitkitGroupHeading/BitkitGroupHeading';
|
|
8
8
|
export interface BitkitPageSidebarProps extends BoxProps {
|
|
9
9
|
children: ReactNode;
|
|
10
|
+
/**
|
|
11
|
+
* Header of the mobile drawer panel, naming what the navigation belongs to — pass a
|
|
12
|
+
* `BitkitPageSidebar.DrawerHeader`. Only rendered in drawer mode.
|
|
13
|
+
*/
|
|
14
|
+
drawerHeader?: ReactNode;
|
|
15
|
+
/**
|
|
16
|
+
* Mobile drawer visibility. Controlled, because the page header's menu button is what opens it.
|
|
17
|
+
* Passing `onOpenChange` is what opts the sidebar into the drawer below `tablet`; without it the
|
|
18
|
+
* sidebar stays in the flow at full width there. Both are ignored from `tablet` up.
|
|
19
|
+
*
|
|
20
|
+
* Style props and `css` apply to the sidebar beside the page only — the drawer panel sizes
|
|
21
|
+
* itself, so a desktop `width` or padding never reaches the navigation inside it.
|
|
22
|
+
*/
|
|
23
|
+
onOpenChange?: (open: boolean) => void;
|
|
24
|
+
open?: boolean;
|
|
10
25
|
}
|
|
11
26
|
interface BitkitPageSidebarItemCommonProps {
|
|
12
27
|
badge?: ReactNode;
|
|
@@ -105,10 +120,19 @@ export interface BitkitPageSidebarProjectProps extends BoxProps {
|
|
|
105
120
|
value: string;
|
|
106
121
|
}
|
|
107
122
|
export type BitkitPageSidebarGroupHeadingProps = BitkitGroupHeadingProps;
|
|
123
|
+
export interface BitkitPageSidebarDrawerHeaderProps extends Omit<BoxProps, 'title'> {
|
|
124
|
+
/** App icon or user avatar before the title — a 32px `BitkitAvatar`. */
|
|
125
|
+
avatar?: ReactNode;
|
|
126
|
+
/** The entity's secondary line, an app's bundle id or a user's email. Its presence selects the entity layout. */
|
|
127
|
+
secondaryText?: string;
|
|
128
|
+
/** What the navigation belongs to: the page, app or user. */
|
|
129
|
+
title: string;
|
|
130
|
+
}
|
|
108
131
|
export type BitkitPageSidebarDividerProps = SeparatorProps;
|
|
109
132
|
declare const _default: import('react').ForwardRefExoticComponent<BitkitPageSidebarProps & import('react').RefAttributes<HTMLElement>> & {
|
|
110
133
|
Body: import('react').ForwardRefExoticComponent<BitkitPageSidebarBodyProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
111
134
|
Divider: import('react').ForwardRefExoticComponent<SeparatorProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
135
|
+
DrawerHeader: import('react').ForwardRefExoticComponent<BitkitPageSidebarDrawerHeaderProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
112
136
|
Footer: import('react').ForwardRefExoticComponent<BitkitPageSidebarFooterProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
113
137
|
GroupHeading: import('react').ForwardRefExoticComponent<BitkitGroupHeadingProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
114
138
|
Item: import('react').ForwardRefExoticComponent<BitkitPageSidebarItemProps & import('react').RefAttributes<HTMLAnchorElement | HTMLButtonElement>>;
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { withSubComponents } from "../../utilities/withSubComponents.js";
|
|
2
2
|
import IconChevronLeft from "../../icons/IconChevronLeft.js";
|
|
3
|
+
import BitkitCloseButton from "../BitkitCloseButton/BitkitCloseButton.js";
|
|
3
4
|
import BitkitControlButton from "../BitkitControlButton/BitkitControlButton.js";
|
|
4
5
|
import BitkitGroupHeading from "../BitkitGroupHeading/BitkitGroupHeading.js";
|
|
5
6
|
import BitkitOverflowTooltip from "../BitkitOverflowTooltip/BitkitOverflowTooltip.js";
|
|
6
7
|
import useIsTruncated from "../../utilities/useIsTruncated.js";
|
|
8
|
+
import useResponsive from "../../hooks/useResponsive.js";
|
|
7
9
|
import { Box } from "@chakra-ui/react/box";
|
|
8
|
-
import { chakra, useSlotRecipe } from "@chakra-ui/react/styled-system";
|
|
10
|
+
import { chakra, useChakraContext, useSlotRecipe } from "@chakra-ui/react/styled-system";
|
|
9
11
|
import { forwardRef } from "react";
|
|
10
12
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
import { Portal } from "@chakra-ui/react/portal";
|
|
11
14
|
import { Separator } from "@chakra-ui/react/separator";
|
|
12
15
|
import { Text } from "@chakra-ui/react/text";
|
|
16
|
+
import { Drawer } from "@chakra-ui/react/drawer";
|
|
13
17
|
//#region lib/components/BitkitPageSidebar/BitkitPageSidebar.tsx
|
|
14
18
|
var showAtDesktop = { display: {
|
|
15
19
|
base: "none",
|
|
@@ -44,13 +48,20 @@ var showAtMobile = { display: {
|
|
|
44
48
|
* opts out with `minHeight="auto"`.
|
|
45
49
|
*
|
|
46
50
|
* Below the `tablet` breakpoint it switches to the mobile design on its own — full width, 48px
|
|
47
|
-
* rows, larger icons and labels, a squared-off selection edge.
|
|
48
|
-
* that
|
|
51
|
+
* rows, larger icons and labels, a squared-off selection edge. A sidebar that sits beside page
|
|
52
|
+
* content has nowhere to go at that width, so it can become a drawer instead: pass `open` and
|
|
53
|
+
* `onOpenChange` (opened by `BitkitPageHeader`'s menu button) and below `tablet` it slides in
|
|
54
|
+
* from the trailing edge over a backdrop, with its close button on the backdrop beside the panel
|
|
55
|
+
* and a `drawerHeader` naming the page. Both props are ignored from `tablet` up.
|
|
49
56
|
*/
|
|
50
57
|
var BitkitPageSidebar = forwardRef((props, ref) => {
|
|
51
|
-
const { children, css, ...rest } = props;
|
|
52
|
-
const
|
|
53
|
-
|
|
58
|
+
const { children, css, drawerHeader, onOpenChange, open = false, ...rest } = props;
|
|
59
|
+
const { isMobile } = useResponsive();
|
|
60
|
+
const { splitCssProps } = useChakraContext();
|
|
61
|
+
const hasDrawer = onOpenChange !== void 0;
|
|
62
|
+
const isDrawer = isMobile && hasDrawer;
|
|
63
|
+
const styles = useSlotRecipe({ key: "pageSidebar" })({ layout: isDrawer ? "drawer" : hasDrawer ? "hidden" : "static" });
|
|
64
|
+
if (!isDrawer) return /* @__PURE__ */ jsx(Box, {
|
|
54
65
|
ref,
|
|
55
66
|
"aria-label": "Page navigation",
|
|
56
67
|
asChild: true,
|
|
@@ -58,6 +69,35 @@ var BitkitPageSidebar = forwardRef((props, ref) => {
|
|
|
58
69
|
...rest,
|
|
59
70
|
children: /* @__PURE__ */ jsx("nav", { children })
|
|
60
71
|
});
|
|
72
|
+
const [, navProps] = splitCssProps(rest);
|
|
73
|
+
return /* @__PURE__ */ jsx(Drawer.Root, {
|
|
74
|
+
onOpenChange: (details) => onOpenChange(details.open),
|
|
75
|
+
open,
|
|
76
|
+
children: /* @__PURE__ */ jsxs(Portal, { children: [/* @__PURE__ */ jsx(Drawer.Backdrop, { css: styles.drawerBackdrop }), /* @__PURE__ */ jsx(Drawer.Positioner, { children: /* @__PURE__ */ jsxs(Drawer.Content, {
|
|
77
|
+
"aria-label": "Navigation",
|
|
78
|
+
css: styles.drawerContent,
|
|
79
|
+
children: [
|
|
80
|
+
drawerHeader,
|
|
81
|
+
/* @__PURE__ */ jsx(Box, {
|
|
82
|
+
ref,
|
|
83
|
+
"aria-label": "Page navigation",
|
|
84
|
+
asChild: true,
|
|
85
|
+
css: styles.root,
|
|
86
|
+
...navProps,
|
|
87
|
+
children: /* @__PURE__ */ jsx("nav", { children })
|
|
88
|
+
}),
|
|
89
|
+
/* @__PURE__ */ jsx(Box, {
|
|
90
|
+
css: styles.drawerCloseButton,
|
|
91
|
+
children: /* @__PURE__ */ jsx(BitkitCloseButton, {
|
|
92
|
+
"aria-label": "Close navigation",
|
|
93
|
+
colorVariant: "white",
|
|
94
|
+
onClick: () => onOpenChange(false),
|
|
95
|
+
size: "md"
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
]
|
|
99
|
+
}) })] })
|
|
100
|
+
});
|
|
61
101
|
});
|
|
62
102
|
BitkitPageSidebar.displayName = "BitkitPageSidebar";
|
|
63
103
|
var BitkitPageSidebarItem = forwardRef((props, ref) => {
|
|
@@ -318,6 +358,33 @@ var BitkitPageSidebarGroupHeading = forwardRef((props, ref) => {
|
|
|
318
358
|
});
|
|
319
359
|
});
|
|
320
360
|
BitkitPageSidebarGroupHeading.displayName = "BitkitPageSidebarGroupHeading";
|
|
361
|
+
/**
|
|
362
|
+
* Header of the sidebar's mobile drawer (Figma `mobilePageSidebarHeader`). Pass it as the
|
|
363
|
+
* sidebar's `drawerHeader`; it is what the panel opens with, above the navigation.
|
|
364
|
+
*/
|
|
365
|
+
var BitkitPageSidebarDrawerHeader = forwardRef((props, ref) => {
|
|
366
|
+
const { avatar, css, secondaryText, title, ...rest } = props;
|
|
367
|
+
const styles = useSlotRecipe({ key: "pageSidebar" })({ hasSecondaryText: secondaryText !== void 0 });
|
|
368
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
369
|
+
ref,
|
|
370
|
+
css: [styles.drawerHeader, css],
|
|
371
|
+
...rest,
|
|
372
|
+
children: [avatar !== void 0 && /* @__PURE__ */ jsx(Box, {
|
|
373
|
+
css: styles.drawerHeaderAvatar,
|
|
374
|
+
children: avatar
|
|
375
|
+
}), /* @__PURE__ */ jsxs(Box, {
|
|
376
|
+
css: styles.drawerHeaderTextBlock,
|
|
377
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
378
|
+
css: styles.drawerHeaderTitle,
|
|
379
|
+
children: title
|
|
380
|
+
}), secondaryText !== void 0 && /* @__PURE__ */ jsx(Text, {
|
|
381
|
+
css: styles.drawerHeaderSecondaryText,
|
|
382
|
+
children: secondaryText
|
|
383
|
+
})]
|
|
384
|
+
})]
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
BitkitPageSidebarDrawerHeader.displayName = "BitkitPageSidebarDrawerHeader";
|
|
321
388
|
var BitkitPageSidebarDivider = forwardRef((props, ref) => {
|
|
322
389
|
const { css, ...rest } = props;
|
|
323
390
|
const styles = useSlotRecipe({ key: "pageSidebar" })();
|
|
@@ -331,6 +398,7 @@ BitkitPageSidebarDivider.displayName = "BitkitPageSidebarDivider";
|
|
|
331
398
|
var BitkitPageSidebar_default = withSubComponents(BitkitPageSidebar, {
|
|
332
399
|
Body: BitkitPageSidebarBody,
|
|
333
400
|
Divider: BitkitPageSidebarDivider,
|
|
401
|
+
DrawerHeader: BitkitPageSidebarDrawerHeader,
|
|
334
402
|
Footer: BitkitPageSidebarFooter,
|
|
335
403
|
GroupHeading: BitkitPageSidebarGroupHeading,
|
|
336
404
|
Item: BitkitPageSidebarItem,
|