@umodeapporg/ui 0.27.0 → 0.29.0
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/assets/umode-ui.css +0 -5
- package/dist/components/DateTime/DatePicker.d.ts +2 -0
- package/dist/components/DateTime/DatePicker.js +5 -2
- package/dist/components/DateTime/DatePicker.js.map +1 -1
- package/dist/components/DateTime/DateRangePicker.d.ts +2 -0
- package/dist/components/DateTime/DateRangePicker.js +3 -0
- package/dist/components/DateTime/DateRangePicker.js.map +1 -1
- package/dist/components/Form/ControlledButton.d.ts +1 -1
- package/dist/components/Form/ControlledButton.js +2 -2
- package/dist/components/Form/ControlledButton.js.map +1 -1
- package/dist/components/Form/ControlledCheckbox.d.ts +5 -1
- package/dist/components/Form/ControlledCheckbox.js +53 -39
- package/dist/components/Form/ControlledCheckbox.js.map +1 -1
- package/dist/components/Form/ControlledDialogPrimitives.js +17 -5
- package/dist/components/Form/ControlledDialogPrimitives.js.map +1 -1
- package/dist/components/Form/ControlledDrawer.js +1 -0
- package/dist/components/Form/ControlledDrawer.js.map +1 -1
- package/dist/components/Form/ControlledImage/index.js +2 -0
- package/dist/components/Form/ControlledImage/index.js.map +1 -1
- package/dist/components/Form/ControlledInput.d.ts +1 -1
- package/dist/components/Form/ControlledInput.js +4 -4
- package/dist/components/Form/ControlledInput.js.map +1 -1
- package/dist/components/Form/ControlledSelect.js +1 -1
- package/dist/components/Form/ControlledSelect.js.map +1 -1
- package/dist/components/Form/ControlledTabs.js +17 -3
- package/dist/components/Form/ControlledTabs.js.map +1 -1
- package/dist/components/Form/ControlledTabsPrimitives.js +6 -6
- package/dist/components/Form/ControlledTabsPrimitives.js.map +1 -1
- package/dist/components/Form/ControlledTextarea.js +2 -2
- package/dist/components/Form/ControlledTextarea.js.map +1 -1
- package/dist/components/Form/ControlledTimeline.js +11 -8
- package/dist/components/Form/ControlledTimeline.js.map +1 -1
- package/dist/components/Form/ControlledVoiceTextarea.d.ts +2 -0
- package/dist/components/Form/ControlledVoiceTextarea.js +3 -0
- package/dist/components/Form/ControlledVoiceTextarea.js.map +1 -1
- package/dist/components/Form/FieldLabel.d.ts +16 -0
- package/dist/components/Form/FieldLabel.js +7 -0
- package/dist/components/Form/FieldLabel.js.map +1 -0
- package/dist/components/Form/RichTextEditor.js +15 -1
- package/dist/components/Form/RichTextEditor.js.map +1 -1
- package/dist/components/Form/attachments/AttachmentPreviewList.js +3 -0
- package/dist/components/Form/attachments/AttachmentPreviewList.js.map +1 -1
- package/dist/components/Form/attachments/FileDropzone.js +3 -3
- package/dist/components/Form/attachments/FileDropzone.js.map +1 -1
- package/dist/components/Navigation/PageTitle.d.ts +6 -0
- package/dist/components/Navigation/PageTitle.js +6 -4
- package/dist/components/Navigation/PageTitle.js.map +1 -1
- package/dist/components/Search/filters/FiltersPanel.js +1 -0
- package/dist/components/Search/filters/FiltersPanel.js.map +1 -1
- package/dist/components/Search/filters/FiltersSummaryTooltip.js +2 -2
- package/dist/components/Search/filters/FiltersSummaryTooltip.js.map +1 -1
- package/dist/components/Shell/LanguageToggle.js +1 -1
- package/dist/components/Shell/LanguageToggle.js.map +1 -1
- package/dist/components/Shell/SidebarMenu.js +9 -1
- package/dist/components/Shell/SidebarMenu.js.map +1 -1
- package/dist/components/Shell/ThemeToggle.js +2 -1
- package/dist/components/Shell/ThemeToggle.js.map +1 -1
- package/dist/components/UI/BaseDataCard.d.ts +2 -0
- package/dist/components/UI/BaseDataCard.js +6 -0
- package/dist/components/UI/BaseDataCard.js.map +1 -1
- package/dist/components/UI/BulkActionBar.js +2 -0
- package/dist/components/UI/BulkActionBar.js.map +1 -1
- package/dist/components/UI/Chips/OutlinedChip.js +13 -6
- package/dist/components/UI/Chips/OutlinedChip.js.map +1 -1
- package/dist/components/UI/ColorSwatch.js +2 -2
- package/dist/components/UI/ColorSwatch.js.map +1 -1
- package/dist/components/UI/ControlledAccordion.js +10 -4
- package/dist/components/UI/ControlledAccordion.js.map +1 -1
- package/dist/components/UI/ControlledCollapse.js +2 -2
- package/dist/components/UI/ControlledCollapse.js.map +1 -1
- package/dist/components/UI/ControlledMenu.js +3 -3
- package/dist/components/UI/ControlledMenu.js.map +1 -1
- package/dist/components/UI/ControlledPopover.d.ts +12 -0
- package/dist/components/UI/ControlledPopover.js +35 -5
- package/dist/components/UI/ControlledPopover.js.map +1 -1
- package/dist/components/UI/ControlledProgress.js +3 -3
- package/dist/components/UI/ControlledProgress.js.map +1 -1
- package/dist/components/UI/ControlledSlider.d.ts +2 -0
- package/dist/components/UI/ControlledSlider.js +8 -3
- package/dist/components/UI/ControlledSlider.js.map +1 -1
- package/dist/components/UI/ControlledTooltip.js +7 -3
- package/dist/components/UI/ControlledTooltip.js.map +1 -1
- package/dist/components/UI/DataTable/DataTable.js +1 -1
- package/dist/components/UI/DataTable/DataTableBody.js +4 -0
- package/dist/components/UI/DataTable/DataTableBody.js.map +1 -1
- package/dist/components/UI/DataTable/DataTableBulkEditDialog.js +4 -2
- package/dist/components/UI/DataTable/DataTableBulkEditDialog.js.map +1 -1
- package/dist/components/UI/DataTable/DataTableBulkEditRowsPicker.js +1 -1
- package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.d.ts +5 -1
- package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.js +19 -4
- package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.js.map +1 -1
- package/dist/components/UI/DataTable/DataTableColumnToggle.js +3 -1
- package/dist/components/UI/DataTable/DataTableColumnToggle.js.map +1 -1
- package/dist/components/UI/DataTable/DataTableFilters.js +1 -1
- package/dist/components/UI/DataTable/DataTablePagination.js +4 -0
- package/dist/components/UI/DataTable/DataTablePagination.js.map +1 -1
- package/dist/components/UI/DataTable/DataTableStates.js +2 -2
- package/dist/components/UI/DataTable/DataTableStates.js.map +1 -1
- package/dist/components/UI/DataTable/DataTableToolbar.js +1 -1
- package/dist/components/UI/DataTable/applyDataTableFilters.js +1 -1
- package/dist/components/UI/DataTable/cells/DataTableEditableCell.js +2 -1
- package/dist/components/UI/DataTable/cells/DataTableEditableCell.js.map +1 -1
- package/dist/components/UI/DataTable/types.d.ts +4 -0
- package/dist/components/UI/DataTable/types.js.map +1 -1
- package/dist/components/UI/Flow/FlowBlockNode.js +1 -0
- package/dist/components/UI/Flow/FlowBlockNode.js.map +1 -1
- package/dist/components/UI/Flow/FlowCanvas.js +2 -2
- package/dist/components/UI/Flow/FlowCanvas.js.map +1 -1
- package/dist/components/UI/Flow/FlowConnectionEdge.js +1 -0
- package/dist/components/UI/Flow/FlowConnectionEdge.js.map +1 -1
- package/dist/components/UI/Flow/FlowInspector.js +3 -2
- package/dist/components/UI/Flow/FlowInspector.js.map +1 -1
- package/dist/components/UI/Flow/FlowIssueList.js +2 -2
- package/dist/components/UI/Flow/FlowIssueList.js.map +1 -1
- package/dist/components/UI/Flow/FlowLaneMap.js +2 -2
- package/dist/components/UI/Flow/FlowLaneMap.js.map +1 -1
- package/dist/components/UI/Flow/FlowResizablePanel.js +3 -2
- package/dist/components/UI/Flow/FlowResizablePanel.js.map +1 -1
- package/dist/components/UI/Flow/FlowZoomControls.js +1 -0
- package/dist/components/UI/Flow/FlowZoomControls.js.map +1 -1
- package/dist/components/UI/HorizontalScrollArea.d.ts +1 -1
- package/dist/components/UI/HorizontalScrollArea.js +1 -0
- package/dist/components/UI/HorizontalScrollArea.js.map +1 -1
- package/dist/components/UI/ImageAnnotator/components/AnnotationColorPopover.js +1 -0
- package/dist/components/UI/ImageAnnotator/components/AnnotationColorPopover.js.map +1 -1
- package/dist/components/UI/ImagePlaceholder.js +5 -2
- package/dist/components/UI/ImagePlaceholder.js.map +1 -1
- package/dist/components/UI/ImageZoomOverlay.js +1 -0
- package/dist/components/UI/ImageZoomOverlay.js.map +1 -1
- package/dist/components/UI/MaterialSymbol/index.js +5 -0
- package/dist/components/UI/MaterialSymbol/index.js.map +1 -1
- package/dist/components/UI/NoDataFound/index.js +3 -3
- package/dist/components/UI/NoDataFound/index.js.map +1 -1
- package/dist/components/UI/RichTextContent.js +3 -3
- package/dist/components/UI/RichTextContent.js.map +1 -1
- package/dist/components/UI/ThreeDots.js +15 -11
- package/dist/components/UI/ThreeDots.js.map +1 -1
- package/dist/helpers/mergeClasses.d.ts +7 -3
- package/dist/helpers/mergeClasses.js +23 -1
- package/dist/helpers/mergeClasses.js.map +1 -1
- package/dist/hooks/useVoiceTranscription.js +5 -2
- package/dist/hooks/useVoiceTranscription.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +58 -54
- package/dist/index.js.map +1 -1
- package/dist/locales/en/ui.json.d.ts +8 -3
- package/dist/locales/en/ui.json.js +3 -3
- package/dist/locales/index.d.ts +10 -0
- package/dist/locales/pt/ui.json.d.ts +8 -3
- package/dist/locales/pt/ui.json.js +3 -3
- package/package.json +1 -1
- package/src/assets/styles/base.css +7 -1
- package/tailwind-plugins/glass.js +23 -28
package/dist/assets/umode-ui.css
CHANGED
|
@@ -93,7 +93,6 @@
|
|
|
93
93
|
.sidebar-aside.is-mobile {
|
|
94
94
|
background-color: rgb(var(--glass-bg) / var(--glass-alpha));
|
|
95
95
|
backdrop-filter: blur(20px) saturate(var(--glass-saturate-strong));
|
|
96
|
-
-webkit-backdrop-filter: blur(20px) saturate(var(--glass-saturate-strong));
|
|
97
96
|
box-shadow:
|
|
98
97
|
var(--shadow-lg),
|
|
99
98
|
inset 0 1px 0 0 rgb(var(--glass-highlight) / var(--glass-highlight-alpha));
|
|
@@ -102,7 +101,6 @@
|
|
|
102
101
|
@media (max-width: 639px) {
|
|
103
102
|
.sidebar-aside.is-mobile {
|
|
104
103
|
backdrop-filter: blur(12px) saturate(var(--glass-saturate-strong));
|
|
105
|
-
-webkit-backdrop-filter: blur(12px) saturate(var(--glass-saturate-strong));
|
|
106
104
|
}
|
|
107
105
|
}
|
|
108
106
|
|
|
@@ -118,7 +116,6 @@
|
|
|
118
116
|
.sidebar-aside.is-mobile {
|
|
119
117
|
background-color: rgb(var(--color-surface-overlay));
|
|
120
118
|
backdrop-filter: none;
|
|
121
|
-
-webkit-backdrop-filter: none;
|
|
122
119
|
}
|
|
123
120
|
}
|
|
124
121
|
|
|
@@ -241,7 +238,6 @@
|
|
|
241
238
|
The per-type tinted backgrounds below override the base fill, so the material
|
|
242
239
|
here is the blur + the edge + the inner highlight. */
|
|
243
240
|
backdrop-filter: blur(20px) saturate(var(--glass-saturate-strong));
|
|
244
|
-
-webkit-backdrop-filter: blur(20px) saturate(var(--glass-saturate-strong));
|
|
245
241
|
box-shadow:
|
|
246
242
|
var(--shadow-lg),
|
|
247
243
|
inset 0 1px 0 0 rgb(var(--glass-highlight) / var(--glass-highlight-alpha)) !important;
|
|
@@ -250,7 +246,6 @@
|
|
|
250
246
|
@media (prefers-reduced-transparency: reduce) {
|
|
251
247
|
.Toastify__toast {
|
|
252
248
|
backdrop-filter: none;
|
|
253
|
-
-webkit-backdrop-filter: none;
|
|
254
249
|
}
|
|
255
250
|
}
|
|
256
251
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
export interface IDatePickerProps {
|
|
3
|
+
/** Reaches the field itself, so a `FieldLabel` can point at it. */
|
|
4
|
+
id?: string;
|
|
3
5
|
label?: string;
|
|
4
6
|
/** Text shown inside the empty input. Falls back to the label for the historical behavior. */
|
|
5
7
|
placeholder?: string;
|
|
@@ -3,10 +3,10 @@ import { useState, useRef, useEffect } from "react";
|
|
|
3
3
|
import { ptBR } from "date-fns/locale";
|
|
4
4
|
import { DayPicker } from "react-day-picker";
|
|
5
5
|
import { useTranslation } from "react-i18next";
|
|
6
|
-
import { twMerge } from "tailwind-merge";
|
|
7
6
|
import { ControlledPopoverRoot, ControlledPopoverHandler, ControlledPopoverContent } from "../UI/ControlledPopover.js";
|
|
8
7
|
import ControlledTooltip from "../UI/ControlledTooltip.js";
|
|
9
8
|
import { formatDate } from "../../helpers/dateHelpers.js";
|
|
9
|
+
import { mergeClasses } from "../../helpers/mergeClasses.js";
|
|
10
10
|
import ControlledInput from "../Form/ControlledInput.js";
|
|
11
11
|
import { MaterialSymbol } from "../UI/MaterialSymbol/index.js";
|
|
12
12
|
import "react-day-picker/style.css";
|
|
@@ -26,6 +26,7 @@ function parseDate(dateString) {
|
|
|
26
26
|
return formattedDate;
|
|
27
27
|
}
|
|
28
28
|
const DatePicker = ({
|
|
29
|
+
id,
|
|
29
30
|
label,
|
|
30
31
|
placeholder,
|
|
31
32
|
disabled,
|
|
@@ -77,9 +78,11 @@ const DatePicker = ({
|
|
|
77
78
|
/* @__PURE__ */ jsx(
|
|
78
79
|
ControlledInput,
|
|
79
80
|
{
|
|
81
|
+
id,
|
|
80
82
|
readOnly: true,
|
|
83
|
+
"aria-label": label,
|
|
81
84
|
placeholder: placeholder ?? label,
|
|
82
|
-
className:
|
|
85
|
+
className: mergeClasses("focus:border-border-strong", inputClassName),
|
|
83
86
|
disabled,
|
|
84
87
|
value: date ? formatDate(date) : "",
|
|
85
88
|
onClick: togglePopover
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatePicker.js","sources":["../../../src/components/DateTime/DatePicker.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport * as React from 'react';\nimport { ptBR } from 'date-fns/locale';\nimport { DateAfter, DateBefore, DayPicker } from 'react-day-picker';\nimport { useTranslation } from 'react-i18next';\
|
|
1
|
+
{"version":3,"file":"DatePicker.js","sources":["../../../src/components/DateTime/DatePicker.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport * as React from 'react';\nimport { ptBR } from 'date-fns/locale';\nimport { DateAfter, DateBefore, DayPicker } from 'react-day-picker';\nimport { useTranslation } from 'react-i18next';\n\nimport {\n ControlledPopoverContent,\n ControlledPopoverHandler,\n ControlledPopoverRoot,\n} from '@/components/UI/ControlledPopover';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { formatDate } from '@/helpers/dateHelpers';\nimport { mergeClasses } from '@/helpers/mergeClasses';\n\nimport ControlledInput from '../Form/ControlledInput';\nimport { MaterialSymbol } from '../UI/MaterialSymbol';\n\nimport 'react-day-picker/style.css';\n\nconst DATE_PICKER_TIMEZONE = 'UTC';\n\nexport interface IDatePickerProps {\n /** Reaches the field itself, so a `FieldLabel` can point at it. */\n id?: string;\n label?: string;\n /** Text shown inside the empty input. Falls back to the label for the historical behavior. */\n placeholder?: string;\n onUpdate: (value: string | undefined) => void;\n disabled?: boolean;\n minDate?: string;\n maxDate?: string;\n useIcon?: boolean;\n valueRender?: () => React.ReactNode;\n startValue?: Date;\n onFocus?: () => void;\n onBlur?: () => void;\n /** Extra classes for the inner input, which is a `ControlledInput` like any other field. */\n inputClassName?: string;\n}\n\nfunction formatDateToYYYYMMDD(date: Date) {\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n const day = String(date.getUTCDate()).padStart(2, '0');\n const year = date.getUTCFullYear();\n return `${year}-${month}-${day}`;\n}\n\nfunction parseDate(dateString: string): Date {\n const dateParts = dateString.split('T')[0].split('-');\n const year = parseInt(dateParts[0], 10);\n const month = parseInt(dateParts[1], 10) - 1;\n const day = parseInt(dateParts[2], 10);\n\n const formattedDate = new Date(year, month, day);\n return formattedDate;\n}\n\nconst DatePicker: React.FC<IDatePickerProps> = ({\n id,\n label,\n placeholder,\n disabled,\n onUpdate,\n minDate,\n maxDate,\n useIcon,\n valueRender,\n startValue,\n onFocus,\n inputClassName,\n}) => {\n const { t } = useTranslation('ui');\n const [date, setDate] = useState<Date | undefined>(startValue || undefined);\n const [popoverOpen, setPopoverOpen] = useState(false);\n\n // Hosts pass a freshly parsed Date on every render, so the sync compares\n // timestamps. Without this the picker ignored external changes (clearing\n // filters, applying a preset) and callers had to force a remount via `key`.\n const startTime = startValue ? startValue.getTime() : null;\n const syncedStartTimeRef = useRef(startTime);\n\n useEffect(() => {\n if (syncedStartTimeRef.current === startTime) return;\n syncedStartTimeRef.current = startTime;\n setDate(startTime === null ? undefined : new Date(startTime));\n }, [startTime]);\n\n const handleSelectedDate = (date: Date | undefined) => {\n setDate(date);\n if (date) {\n const formattedDate = formatDateToYYYYMMDD(date);\n onUpdate(formattedDate);\n } else {\n onUpdate(undefined);\n }\n setPopoverOpen(false);\n };\n\n const togglePopover = () => {\n setPopoverOpen(!popoverOpen);\n };\n\n const clear = () => {\n handleSelectedDate(undefined);\n };\n\n const beforeMatcher: DateBefore = {\n before: minDate ? parseDate(String(minDate)) : new Date(2024, 1, 1),\n };\n const afterMatcher: DateAfter = {\n after: maxDate ? parseDate(String(maxDate)) : new Date(2030, 1, 1),\n };\n\n return (\n <ControlledPopoverRoot placement=\"bottom\" open={popoverOpen} handler={setPopoverOpen}>\n <ControlledPopoverHandler>\n <div className=\"zIndexMax flex w-full flex-row items-center gap-x-1\" onFocus={onFocus}>\n {useIcon && <MaterialSymbol icon=\"calendar_month\" size={20} />}\n {valueRender ? (\n valueRender()\n ) : (\n <div className=\"relative w-full\">\n <ControlledInput\n id={id}\n readOnly\n aria-label={label}\n placeholder={placeholder ?? label}\n className={mergeClasses('focus:border-border-strong', inputClassName)}\n disabled={disabled}\n value={date ? formatDate(date) : ''}\n onClick={togglePopover}\n />\n\n {date && (\n <ControlledTooltip content={t('dates.clearSelection')}>\n <div onClick={clear} className=\"absolute inset-y-0 right-4 flex cursor-pointer items-center\">\n <MaterialSymbol icon=\"close_small\" size={20} color=\"rgb(var(--color-foreground-subtle))\" />\n </div>\n </ControlledTooltip>\n )}\n </div>\n )}\n </div>\n </ControlledPopoverHandler>\n <ControlledPopoverContent className=\"zIndexMax m-0 p-0\">\n <DayPicker\n ISOWeek\n showOutsideDays\n captionLayout=\"label\"\n dir=\"ltr\"\n firstWeekContainsDate={1}\n mode=\"single\"\n numberOfMonths={1}\n timeZone={DATE_PICKER_TIMEZONE}\n weekStartsOn={1}\n locale={ptBR}\n selected={date}\n defaultMonth={date}\n disabled={[beforeMatcher, afterMatcher]}\n onSelect={handleSelectedDate}\n className=\"p-3\"\n classNames={{\n today: `rounded-md bg-muted-active text-foreground`,\n selected: `rounded-md bg-ink text-white hover:bg-ink hover:text-white focus:bg-ink focus:text-white`,\n chevron: `fill-foreground`,\n }}\n />\n </ControlledPopoverContent>\n </ControlledPopoverRoot>\n );\n};\n\nexport default DatePicker;\n"],"names":["date"],"mappings":";;;;;;;;;;;;AAoBA,MAAM,uBAAuB;AAqB7B,SAAS,qBAAqB,MAAY;AACxC,QAAM,QAAQ,OAAO,KAAK,YAAA,IAAgB,CAAC,EAAE,SAAS,GAAG,GAAG;AAC5D,QAAM,MAAM,OAAO,KAAK,WAAA,CAAY,EAAE,SAAS,GAAG,GAAG;AACrD,QAAM,OAAO,KAAK,eAAA;AAClB,SAAO,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG;AAChC;AAEA,SAAS,UAAU,YAA0B;AAC3C,QAAM,YAAY,WAAW,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG;AACpD,QAAM,OAAO,SAAS,UAAU,CAAC,GAAG,EAAE;AACtC,QAAM,QAAQ,SAAS,UAAU,CAAC,GAAG,EAAE,IAAI;AAC3C,QAAM,MAAM,SAAS,UAAU,CAAC,GAAG,EAAE;AAErC,QAAM,gBAAgB,IAAI,KAAK,MAAM,OAAO,GAAG;AAC/C,SAAO;AACT;AAEA,MAAM,aAAyC,CAAC;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,CAAC,MAAM,OAAO,IAAI,SAA2B,cAAc,MAAS;AAC1E,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AAKpD,QAAM,YAAY,aAAa,WAAW,QAAA,IAAY;AACtD,QAAM,qBAAqB,OAAO,SAAS;AAE3C,YAAU,MAAM;AACd,QAAI,mBAAmB,YAAY,UAAW;AAC9C,uBAAmB,UAAU;AAC7B,YAAQ,cAAc,OAAO,SAAY,IAAI,KAAK,SAAS,CAAC;AAAA,EAC9D,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,qBAAqB,CAACA,UAA2B;AACrD,YAAQA,KAAI;AACZ,QAAIA,OAAM;AACR,YAAM,gBAAgB,qBAAqBA,KAAI;AAC/C,eAAS,aAAa;AAAA,IACxB,OAAO;AACL,eAAS,MAAS;AAAA,IACpB;AACA,mBAAe,KAAK;AAAA,EACtB;AAEA,QAAM,gBAAgB,MAAM;AAC1B,mBAAe,CAAC,WAAW;AAAA,EAC7B;AAEA,QAAM,QAAQ,MAAM;AAClB,uBAAmB,MAAS;AAAA,EAC9B;AAEA,QAAM,gBAA4B;AAAA,IAChC,QAAQ,UAAU,UAAU,OAAO,OAAO,CAAC,IAAI,IAAI,KAAK,MAAM,GAAG,CAAC;AAAA,EAAA;AAEpE,QAAM,eAA0B;AAAA,IAC9B,OAAO,UAAU,UAAU,OAAO,OAAO,CAAC,IAAI,IAAI,KAAK,MAAM,GAAG,CAAC;AAAA,EAAA;AAGnE,8BACG,uBAAA,EAAsB,WAAU,UAAS,MAAM,aAAa,SAAS,gBACpE,UAAA;AAAA,IAAA,oBAAC,0BAAA,EACC,UAAA,qBAAC,OAAA,EAAI,WAAU,uDAAsD,SAClE,UAAA;AAAA,MAAA,WAAW,oBAAC,gBAAA,EAAe,MAAK,kBAAiB,MAAM,IAAI;AAAA,MAC3D,cACC,YAAA,IAEA,qBAAC,OAAA,EAAI,WAAU,mBACb,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC;AAAA,YACA,UAAQ;AAAA,YACR,cAAY;AAAA,YACZ,aAAa,eAAe;AAAA,YAC5B,WAAW,aAAa,8BAA8B,cAAc;AAAA,YACpE;AAAA,YACA,OAAO,OAAO,WAAW,IAAI,IAAI;AAAA,YACjC,SAAS;AAAA,UAAA;AAAA,QAAA;AAAA,QAGV,4BACE,mBAAA,EAAkB,SAAS,EAAE,sBAAsB,GAClD,UAAA,oBAAC,OAAA,EAAI,SAAS,OAAO,WAAU,+DAC7B,UAAA,oBAAC,kBAAe,MAAK,eAAc,MAAM,IAAI,OAAM,sCAAA,CAAsC,EAAA,CAC3F,EAAA,CACF;AAAA,MAAA,EAAA,CAEJ;AAAA,IAAA,EAAA,CAEJ,EAAA,CACF;AAAA,IACA,oBAAC,0BAAA,EAAyB,WAAU,qBAClC,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,SAAO;AAAA,QACP,iBAAe;AAAA,QACf,eAAc;AAAA,QACd,KAAI;AAAA,QACJ,uBAAuB;AAAA,QACvB,MAAK;AAAA,QACL,gBAAgB;AAAA,QAChB,UAAU;AAAA,QACV,cAAc;AAAA,QACd,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,cAAc;AAAA,QACd,UAAU,CAAC,eAAe,YAAY;AAAA,QACtC,UAAU;AAAA,QACV,WAAU;AAAA,QACV,YAAY;AAAA,UACV,OAAO;AAAA,UACP,UAAU;AAAA,UACV,SAAS;AAAA,QAAA;AAAA,MACX;AAAA,IAAA,EACF,CACF;AAAA,EAAA,GACF;AAEJ;"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { DateRange } from 'react-day-picker';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
interface IDateRangePickerProps {
|
|
4
|
+
/** Reaches the field itself, so a `FieldLabel` can point at it. */
|
|
5
|
+
id?: string;
|
|
4
6
|
label?: string;
|
|
5
7
|
onUpdate: (value: {
|
|
6
8
|
from: string | undefined;
|
|
@@ -36,6 +36,7 @@ function parseDate(dateString) {
|
|
|
36
36
|
return formattedDate;
|
|
37
37
|
}
|
|
38
38
|
const DateRangePicker = ({
|
|
39
|
+
id,
|
|
39
40
|
label,
|
|
40
41
|
disabled,
|
|
41
42
|
onUpdate,
|
|
@@ -138,7 +139,9 @@ const DateRangePicker = ({
|
|
|
138
139
|
/* @__PURE__ */ jsx(
|
|
139
140
|
ControlledInput,
|
|
140
141
|
{
|
|
142
|
+
id,
|
|
141
143
|
readOnly: true,
|
|
144
|
+
"aria-label": label,
|
|
142
145
|
error: Boolean(range?.from && !range.to),
|
|
143
146
|
placeholder: label || t("dateRangePicker.selectPeriod"),
|
|
144
147
|
className: classNames("w-full cursor-pointer truncate text-left", range?.from && "!pr-9"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DateRangePicker.js","sources":["../../../src/components/DateTime/DateRangePicker.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport * as React from 'react';\nimport classNames from 'classnames';\nimport { ptBR } from 'date-fns/locale';\nimport { DateAfter, DateBefore, DateRange, DayPicker, getDefaultClassNames } from 'react-day-picker';\nimport { useTranslation } from 'react-i18next';\n\nimport {\n ControlledPopoverContent,\n ControlledPopoverHandler,\n ControlledPopoverRoot,\n} from '@/components/UI/ControlledPopover';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { formatDate } from '@/helpers/dateHelpers';\n\nimport ControlledInput from '../Form/ControlledInput';\nimport { MaterialSymbol } from '../UI/MaterialSymbol';\n\nimport 'react-day-picker/style.css';\n\nconst DATE_RANGE_PICKER_TIMEZONE = 'UTC';\n\n/**\n * react-day-picker paints the selection through its own CSS variables, so the brand\n * color has to be injected there: a Tailwind background class on the day cell loses\n * to the library's compound selectors. The important modifier is required because the\n * library stylesheet ships in a lazily injected chunk, so it wins any specificity tie\n * against these utilities.\n */\nconst CALENDAR_THEME_VARIABLES = [\n '![--rdp-accent-background-color:theme(colors.purple.50)]',\n '![--rdp-accent-color:theme(colors.purple.500)]',\n '![--rdp-day-height:2.5rem]',\n '![--rdp-day-width:2.5rem]',\n '![--rdp-day_button-height:2.25rem]',\n '![--rdp-day_button-width:2.25rem]',\n '![--rdp-range_middle-color:theme(colors.purple.700)]',\n].join(' ');\n\nconst calendarClassNames = getDefaultClassNames();\n\ninterface IDateRangePickerProps {\n label?: string;\n onUpdate: (value: { from: string | undefined; to: string | undefined }) => void;\n disabled?: boolean;\n minDate?: string;\n maxDate?: string;\n useIcon?: boolean;\n valueRender?: () => React.ReactNode;\n startValue?: DateRange;\n onFocus?: () => void;\n onBlur?: () => void;\n}\n\nfunction formatDateToYYYYMMDD(date: Date) {\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n const day = String(date.getUTCDate()).padStart(2, '0');\n const year = date.getUTCFullYear();\n return `${year}-${month}-${day}`;\n}\n\nfunction parseDate(dateString: string): Date {\n const dateParts = dateString.split('T')[0].split('-');\n const year = parseInt(dateParts[0], 10);\n const month = parseInt(dateParts[1], 10) - 1;\n const day = parseInt(dateParts[2], 10);\n\n const formattedDate = new Date(year, month, day);\n return formattedDate;\n}\n\nconst DateRangePicker: React.FC<IDateRangePickerProps> = ({\n label,\n disabled,\n onUpdate,\n minDate,\n maxDate,\n useIcon,\n valueRender,\n startValue,\n onFocus,\n}) => {\n const { t } = useTranslation('ui');\n const [range, setRange] = useState<DateRange | undefined>(startValue);\n const [popoverOpen, setPopoverOpen] = useState(false);\n\n useEffect(() => {\n setRange(startValue);\n }, [startValue]);\n\n const handleSelectedRange = (newRange: DateRange | undefined) => {\n if (!newRange) {\n setRange(undefined);\n onUpdate({ from: undefined, to: undefined });\n return;\n }\n\n if (!range?.from) {\n setRange({ from: newRange.from, to: undefined });\n if (newRange.from) {\n onUpdate({\n from: formatDateToYYYYMMDD(newRange.from),\n to: undefined,\n });\n }\n return;\n }\n\n if (range.from && !range.to && newRange.to) {\n const finalRange = {\n from: range.from,\n to: newRange.to,\n };\n setRange(finalRange);\n onUpdate({\n from: formatDateToYYYYMMDD(finalRange.from),\n to: formatDateToYYYYMMDD(finalRange.to),\n });\n setPopoverOpen(false);\n return;\n }\n\n setRange({ from: newRange.from, to: undefined });\n if (newRange.from) {\n onUpdate({\n from: formatDateToYYYYMMDD(newRange.from),\n to: undefined,\n });\n }\n };\n\n const togglePopover = () => {\n setPopoverOpen(!popoverOpen);\n };\n\n const clear = (e?: React.MouseEvent) => {\n if (e) {\n e.preventDefault();\n e.stopPropagation();\n }\n const emptyRange = undefined;\n setRange(emptyRange);\n onUpdate({ from: undefined, to: undefined });\n setPopoverOpen(false);\n };\n\n const beforeMatcher: DateBefore = {\n before: minDate ? parseDate(String(minDate)) : new Date(2024, 1, 1),\n };\n const afterMatcher: DateAfter = {\n after: maxDate ? parseDate(String(maxDate)) : new Date(2030, 1, 1),\n };\n\n const displayValue = () => {\n if (!range?.from) return '';\n if (!range.to) return `${formatDate(range.from)} - ${t('dates.selectEndDate')}`;\n return `${formatDate(range.from)} - ${formatDate(range.to)}`;\n };\n\n const getFooter = () => {\n if (!range?.from) {\n return (\n <div className=\"flex items-center justify-center py-2\">\n <div className=\"flex items-center gap-2 text-sm text-foreground-muted\">\n <MaterialSymbol icon=\"info\" size={16} color=\"rgb(var(--color-foreground-subtle))\" />\n <span>{t('dates.selectStartDate')}</span>\n </div>\n </div>\n );\n }\n if (!range.to) {\n return (\n <div className=\"flex items-center justify-center py-2\">\n <div className=\"flex items-center gap-2 text-sm text-brand-emphasis\">\n <MaterialSymbol icon=\"schedule\" size={16} color=\"rgb(var(--color-primary))\" />\n <span>{t('dateRangePicker.nowSelectEndDate')}</span>\n </div>\n </div>\n );\n }\n return (\n <div className=\"flex items-center justify-center py-2\">\n <div className=\"flex items-center gap-2 text-sm text-success-emphasis\">\n <MaterialSymbol icon=\"check_circle\" size={16} color=\"rgb(var(--color-success))\" />\n <span>{t('dates.periodSelectedSuccess')}</span>\n </div>\n </div>\n );\n };\n\n return (\n <ControlledPopoverRoot placement=\"bottom\" open={popoverOpen} handler={setPopoverOpen}>\n <ControlledPopoverHandler>\n <div className=\"zIndexMax flex w-full flex-row items-center gap-x-2\" onFocus={onFocus}>\n {useIcon && <MaterialSymbol icon=\"calendar_month\" size={20} />}\n\n {valueRender ? (\n valueRender()\n ) : (\n <div className=\"relative w-full\">\n <ControlledInput\n readOnly\n error={Boolean(range?.from && !range.to)}\n placeholder={label || t('dateRangePicker.selectPeriod')}\n className={classNames('w-full cursor-pointer truncate text-left', range?.from && '!pr-9')}\n disabled={disabled}\n value={displayValue()}\n onClick={togglePopover}\n />\n\n {range?.from && (\n <ControlledTooltip content={t('dates.clearPeriod')}>\n <div\n onClick={(e) => {\n e.preventDefault();\n e.stopPropagation();\n clear(e);\n }}\n onMouseDown={(e) => {\n e.preventDefault();\n e.stopPropagation();\n }}\n className=\"absolute inset-y-0 right-2 z-10 flex cursor-pointer items-center\"\n >\n <MaterialSymbol icon=\"close_small\" size={20} color=\"rgb(var(--color-foreground-subtle))\" />\n </div>\n </ControlledTooltip>\n )}\n </div>\n )}\n </div>\n </ControlledPopoverHandler>\n <ControlledPopoverContent className=\"zIndexMax overflow-hidden rounded-xl border-0 p-0 shadow-2xl\">\n <div className=\"bg-surface\">\n <DayPicker\n ISOWeek\n showOutsideDays\n captionLayout=\"label\"\n dir=\"ltr\"\n firstWeekContainsDate={1}\n mode=\"range\"\n numberOfMonths={2}\n timeZone={DATE_RANGE_PICKER_TIMEZONE}\n weekStartsOn={1}\n locale={ptBR}\n selected={range}\n defaultMonth={range?.from || new Date()}\n disabled={[beforeMatcher, afterMatcher]}\n onSelect={handleSelectedRange}\n className={classNames('p-4', CALENDAR_THEME_VARIABLES)}\n classNames={{\n month_caption: `${calendarClassNames.month_caption} items-center text-brand-emphasis`,\n caption_label: `${calendarClassNames.caption_label} text-base font-semibold`,\n weekday: `${calendarClassNames.weekday} text-foreground-subtle`,\n day: `${calendarClassNames.day} text-sm text-foreground transition-colors hover:bg-brand-soft hover:text-brand-emphasis`,\n day_button: `${calendarClassNames.day_button} focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-border`,\n selected: 'text-sm font-semibold',\n today: `${calendarClassNames.today} font-semibold`,\n outside: `${calendarClassNames.outside} text-foreground-faint`,\n disabled: `${calendarClassNames.disabled} text-foreground-faint`,\n }}\n footer={<div className=\"rounded-b-xl border-t border-border-subtle bg-muted\">{getFooter()}</div>}\n />\n </div>\n </ControlledPopoverContent>\n </ControlledPopoverRoot>\n );\n};\n\nexport default DateRangePicker;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DateRangePicker.js","sources":["../../../src/components/DateTime/DateRangePicker.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport * as React from 'react';\nimport classNames from 'classnames';\nimport { ptBR } from 'date-fns/locale';\nimport { DateAfter, DateBefore, DateRange, DayPicker, getDefaultClassNames } from 'react-day-picker';\nimport { useTranslation } from 'react-i18next';\n\nimport {\n ControlledPopoverContent,\n ControlledPopoverHandler,\n ControlledPopoverRoot,\n} from '@/components/UI/ControlledPopover';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { formatDate } from '@/helpers/dateHelpers';\n\nimport ControlledInput from '../Form/ControlledInput';\nimport { MaterialSymbol } from '../UI/MaterialSymbol';\n\nimport 'react-day-picker/style.css';\n\nconst DATE_RANGE_PICKER_TIMEZONE = 'UTC';\n\n/**\n * react-day-picker paints the selection through its own CSS variables, so the brand\n * color has to be injected there: a Tailwind background class on the day cell loses\n * to the library's compound selectors. The important modifier is required because the\n * library stylesheet ships in a lazily injected chunk, so it wins any specificity tie\n * against these utilities.\n */\nconst CALENDAR_THEME_VARIABLES = [\n '![--rdp-accent-background-color:theme(colors.purple.50)]',\n '![--rdp-accent-color:theme(colors.purple.500)]',\n '![--rdp-day-height:2.5rem]',\n '![--rdp-day-width:2.5rem]',\n '![--rdp-day_button-height:2.25rem]',\n '![--rdp-day_button-width:2.25rem]',\n '![--rdp-range_middle-color:theme(colors.purple.700)]',\n].join(' ');\n\nconst calendarClassNames = getDefaultClassNames();\n\ninterface IDateRangePickerProps {\n /** Reaches the field itself, so a `FieldLabel` can point at it. */\n id?: string;\n label?: string;\n onUpdate: (value: { from: string | undefined; to: string | undefined }) => void;\n disabled?: boolean;\n minDate?: string;\n maxDate?: string;\n useIcon?: boolean;\n valueRender?: () => React.ReactNode;\n startValue?: DateRange;\n onFocus?: () => void;\n onBlur?: () => void;\n}\n\nfunction formatDateToYYYYMMDD(date: Date) {\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n const day = String(date.getUTCDate()).padStart(2, '0');\n const year = date.getUTCFullYear();\n return `${year}-${month}-${day}`;\n}\n\nfunction parseDate(dateString: string): Date {\n const dateParts = dateString.split('T')[0].split('-');\n const year = parseInt(dateParts[0], 10);\n const month = parseInt(dateParts[1], 10) - 1;\n const day = parseInt(dateParts[2], 10);\n\n const formattedDate = new Date(year, month, day);\n return formattedDate;\n}\n\nconst DateRangePicker: React.FC<IDateRangePickerProps> = ({\n id,\n label,\n disabled,\n onUpdate,\n minDate,\n maxDate,\n useIcon,\n valueRender,\n startValue,\n onFocus,\n}) => {\n const { t } = useTranslation('ui');\n const [range, setRange] = useState<DateRange | undefined>(startValue);\n const [popoverOpen, setPopoverOpen] = useState(false);\n\n useEffect(() => {\n setRange(startValue);\n }, [startValue]);\n\n const handleSelectedRange = (newRange: DateRange | undefined) => {\n if (!newRange) {\n setRange(undefined);\n onUpdate({ from: undefined, to: undefined });\n return;\n }\n\n if (!range?.from) {\n setRange({ from: newRange.from, to: undefined });\n if (newRange.from) {\n onUpdate({\n from: formatDateToYYYYMMDD(newRange.from),\n to: undefined,\n });\n }\n return;\n }\n\n if (range.from && !range.to && newRange.to) {\n const finalRange = {\n from: range.from,\n to: newRange.to,\n };\n setRange(finalRange);\n onUpdate({\n from: formatDateToYYYYMMDD(finalRange.from),\n to: formatDateToYYYYMMDD(finalRange.to),\n });\n setPopoverOpen(false);\n return;\n }\n\n setRange({ from: newRange.from, to: undefined });\n if (newRange.from) {\n onUpdate({\n from: formatDateToYYYYMMDD(newRange.from),\n to: undefined,\n });\n }\n };\n\n const togglePopover = () => {\n setPopoverOpen(!popoverOpen);\n };\n\n const clear = (e?: React.MouseEvent) => {\n if (e) {\n e.preventDefault();\n e.stopPropagation();\n }\n const emptyRange = undefined;\n setRange(emptyRange);\n onUpdate({ from: undefined, to: undefined });\n setPopoverOpen(false);\n };\n\n const beforeMatcher: DateBefore = {\n before: minDate ? parseDate(String(minDate)) : new Date(2024, 1, 1),\n };\n const afterMatcher: DateAfter = {\n after: maxDate ? parseDate(String(maxDate)) : new Date(2030, 1, 1),\n };\n\n const displayValue = () => {\n if (!range?.from) return '';\n if (!range.to) return `${formatDate(range.from)} - ${t('dates.selectEndDate')}`;\n return `${formatDate(range.from)} - ${formatDate(range.to)}`;\n };\n\n const getFooter = () => {\n if (!range?.from) {\n return (\n <div className=\"flex items-center justify-center py-2\">\n <div className=\"flex items-center gap-2 text-sm text-foreground-muted\">\n <MaterialSymbol icon=\"info\" size={16} color=\"rgb(var(--color-foreground-subtle))\" />\n <span>{t('dates.selectStartDate')}</span>\n </div>\n </div>\n );\n }\n if (!range.to) {\n return (\n <div className=\"flex items-center justify-center py-2\">\n <div className=\"flex items-center gap-2 text-sm text-brand-emphasis\">\n <MaterialSymbol icon=\"schedule\" size={16} color=\"rgb(var(--color-primary))\" />\n <span>{t('dateRangePicker.nowSelectEndDate')}</span>\n </div>\n </div>\n );\n }\n return (\n <div className=\"flex items-center justify-center py-2\">\n <div className=\"flex items-center gap-2 text-sm text-success-emphasis\">\n <MaterialSymbol icon=\"check_circle\" size={16} color=\"rgb(var(--color-success))\" />\n <span>{t('dates.periodSelectedSuccess')}</span>\n </div>\n </div>\n );\n };\n\n return (\n <ControlledPopoverRoot placement=\"bottom\" open={popoverOpen} handler={setPopoverOpen}>\n <ControlledPopoverHandler>\n <div className=\"zIndexMax flex w-full flex-row items-center gap-x-2\" onFocus={onFocus}>\n {useIcon && <MaterialSymbol icon=\"calendar_month\" size={20} />}\n\n {valueRender ? (\n valueRender()\n ) : (\n <div className=\"relative w-full\">\n <ControlledInput\n id={id}\n readOnly\n aria-label={label}\n error={Boolean(range?.from && !range.to)}\n placeholder={label || t('dateRangePicker.selectPeriod')}\n className={classNames('w-full cursor-pointer truncate text-left', range?.from && '!pr-9')}\n disabled={disabled}\n value={displayValue()}\n onClick={togglePopover}\n />\n\n {range?.from && (\n <ControlledTooltip content={t('dates.clearPeriod')}>\n <div\n onClick={(e) => {\n e.preventDefault();\n e.stopPropagation();\n clear(e);\n }}\n onMouseDown={(e) => {\n e.preventDefault();\n e.stopPropagation();\n }}\n className=\"absolute inset-y-0 right-2 z-10 flex cursor-pointer items-center\"\n >\n <MaterialSymbol icon=\"close_small\" size={20} color=\"rgb(var(--color-foreground-subtle))\" />\n </div>\n </ControlledTooltip>\n )}\n </div>\n )}\n </div>\n </ControlledPopoverHandler>\n <ControlledPopoverContent className=\"zIndexMax overflow-hidden rounded-xl border-0 p-0 shadow-2xl\">\n <div className=\"bg-surface\">\n <DayPicker\n ISOWeek\n showOutsideDays\n captionLayout=\"label\"\n dir=\"ltr\"\n firstWeekContainsDate={1}\n mode=\"range\"\n numberOfMonths={2}\n timeZone={DATE_RANGE_PICKER_TIMEZONE}\n weekStartsOn={1}\n locale={ptBR}\n selected={range}\n defaultMonth={range?.from || new Date()}\n disabled={[beforeMatcher, afterMatcher]}\n onSelect={handleSelectedRange}\n className={classNames('p-4', CALENDAR_THEME_VARIABLES)}\n classNames={{\n month_caption: `${calendarClassNames.month_caption} items-center text-brand-emphasis`,\n caption_label: `${calendarClassNames.caption_label} text-base font-semibold`,\n weekday: `${calendarClassNames.weekday} text-foreground-subtle`,\n day: `${calendarClassNames.day} text-sm text-foreground transition-colors hover:bg-brand-soft hover:text-brand-emphasis`,\n day_button: `${calendarClassNames.day_button} focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-border`,\n selected: 'text-sm font-semibold',\n today: `${calendarClassNames.today} font-semibold`,\n outside: `${calendarClassNames.outside} text-foreground-faint`,\n disabled: `${calendarClassNames.disabled} text-foreground-faint`,\n }}\n footer={<div className=\"rounded-b-xl border-t border-border-subtle bg-muted\">{getFooter()}</div>}\n />\n </div>\n </ControlledPopoverContent>\n </ControlledPopoverRoot>\n );\n};\n\nexport default DateRangePicker;\n"],"names":[],"mappings":"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"}
|
|
@@ -18,7 +18,7 @@ export interface IButtonClassOptions {
|
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* The themed button recipe, for surfaces that must look like a button without being one (a
|
|
21
|
-
* link: see `LinkButton`). `className` is merged last with `
|
|
21
|
+
* link: see `LinkButton`). `className` is merged last with `mergeClasses`, like on the button.
|
|
22
22
|
*/
|
|
23
23
|
export declare const getButtonClassName: ({ variant, tone, size, fullWidth, className, }?: IButtonClassOptions) => string;
|
|
24
24
|
declare const ControlledButton: import('react').ForwardRefExoticComponent<IControlledButtonProps & import('react').RefAttributes<HTMLButtonElement>>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef } from "react";
|
|
3
3
|
import classNames from "classnames";
|
|
4
|
-
import {
|
|
4
|
+
import { mergeClasses } from "../../helpers/mergeClasses.js";
|
|
5
5
|
const BASE_CLASS = "inline-flex select-none items-center justify-center gap-x-2 rounded-xl font-bold capitalize align-middle transition-colors duration-fast focus:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50";
|
|
6
6
|
const SIZE_CLASS = {
|
|
7
7
|
sm: "px-4 py-2 text-xs",
|
|
@@ -37,7 +37,7 @@ const getButtonClassName = ({
|
|
|
37
37
|
size = "md",
|
|
38
38
|
fullWidth = false,
|
|
39
39
|
className
|
|
40
|
-
} = {}) =>
|
|
40
|
+
} = {}) => mergeClasses(classNames(BASE_CLASS, SIZE_CLASS[size], TONE_CLASS[variant][tone], { "w-full": fullWidth }), className);
|
|
41
41
|
const ControlledButton = forwardRef(
|
|
42
42
|
({
|
|
43
43
|
defaultHtmlButton = false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ControlledButton.js","sources":["../../../src/components/Form/ControlledButton.tsx"],"sourcesContent":["import { forwardRef } from 'react';\nimport classNames from 'classnames';\nimport {
|
|
1
|
+
{"version":3,"file":"ControlledButton.js","sources":["../../../src/components/Form/ControlledButton.tsx"],"sourcesContent":["import { forwardRef } from 'react';\nimport classNames from 'classnames';\n\nimport { mergeClasses } from '@/helpers/mergeClasses';\n\nexport type TButtonVariant = 'filled' | 'outlined' | 'text';\nexport type TButtonTone = 'brand' | 'neutral' | 'danger' | 'success' | 'info';\nexport type TButtonSize = 'sm' | 'md' | 'lg';\n\ninterface IControlledButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Renders a bare <button> with no theme, for clickable surfaces that are not buttons. */\n defaultHtmlButton?: boolean;\n variant?: TButtonVariant;\n tone?: TButtonTone;\n size?: TButtonSize;\n fullWidth?: boolean;\n}\n\nconst BASE_CLASS =\n 'inline-flex select-none items-center justify-center gap-x-2 rounded-xl font-bold capitalize align-middle ' +\n 'transition-colors duration-fast focus:outline-none focus-visible:ring-2 focus-visible:ring-ring ' +\n 'disabled:pointer-events-none disabled:opacity-50';\n\nconst SIZE_CLASS: Record<TButtonSize, string> = {\n sm: 'px-4 py-2 text-xs',\n md: 'px-6 py-3 text-xs',\n lg: 'px-7 py-3.5 text-sm',\n};\n\nconst TONE_CLASS: Record<TButtonVariant, Record<TButtonTone, string>> = {\n filled: {\n brand: 'bg-brand text-brand-fg shadow-sm hover:bg-brand-hover',\n neutral: 'bg-muted-active text-foreground shadow-sm hover:bg-muted-hover',\n danger: 'bg-danger text-danger-fg shadow-sm hover:bg-danger-hover',\n success: 'bg-success text-success-fg shadow-sm hover:bg-success-hover',\n info: 'bg-info text-info-fg shadow-sm hover:bg-info-hover',\n },\n outlined: {\n brand: 'border border-brand-border text-brand-emphasis hover:bg-brand-soft',\n neutral: 'border border-border text-foreground-muted hover:bg-muted',\n danger: 'border border-danger-border text-danger-emphasis hover:bg-danger-soft',\n success: 'border border-success-border text-success-emphasis hover:bg-success-soft',\n info: 'border border-info-border text-info-emphasis hover:bg-info-soft',\n },\n text: {\n brand: 'text-brand-emphasis hover:bg-brand-soft',\n neutral: 'text-foreground-muted hover:bg-muted',\n danger: 'text-danger-emphasis hover:bg-danger-soft',\n success: 'text-success-emphasis hover:bg-success-soft',\n info: 'text-info-emphasis hover:bg-info-soft',\n },\n};\n\nexport interface IButtonClassOptions {\n variant?: TButtonVariant;\n tone?: TButtonTone;\n size?: TButtonSize;\n fullWidth?: boolean;\n className?: string;\n}\n\n/**\n * The themed button recipe, for surfaces that must look like a button without being one (a\n * link: see `LinkButton`). `className` is merged last with `mergeClasses`, like on the button.\n */\nexport const getButtonClassName = ({\n variant = 'filled',\n tone = 'brand',\n size = 'md',\n fullWidth = false,\n className,\n}: IButtonClassOptions = {}): string =>\n mergeClasses(classNames(BASE_CLASS, SIZE_CLASS[size], TONE_CLASS[variant][tone], { 'w-full': fullWidth }), className);\n\nconst ControlledButton = forwardRef<HTMLButtonElement, IControlledButtonProps>(\n (\n {\n defaultHtmlButton = false,\n variant = 'filled',\n tone = 'brand',\n size = 'md',\n fullWidth = false,\n type = 'button',\n className,\n children,\n ...rest\n },\n ref,\n ) => {\n if (defaultHtmlButton) {\n return (\n <button type={type} {...rest} ref={ref} className={className}>\n {children}\n </button>\n );\n }\n\n return (\n <button\n type={type}\n {...rest}\n ref={ref}\n className={getButtonClassName({ variant, tone, size, fullWidth, className })}\n >\n {children}\n </button>\n );\n },\n);\n\nControlledButton.displayName = 'ControlledButton';\n\nexport default ControlledButton;\n"],"names":[],"mappings":";;;;AAkBA,MAAM,aACJ;AAIF,MAAM,aAA0C;AAAA,EAC9C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,MAAM,aAAkE;AAAA,EACtE,QAAQ;AAAA,IACN,OAAO;AAAA,IACP,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,EAAA;AAAA,EAER,UAAU;AAAA,IACR,OAAO;AAAA,IACP,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,EAAA;AAAA,EAER,MAAM;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,EAAA;AAEV;AAcO,MAAM,qBAAqB,CAAC;AAAA,EACjC,UAAU;AAAA,EACV,OAAO;AAAA,EACP,OAAO;AAAA,EACP,YAAY;AAAA,EACZ;AACF,IAAyB,CAAA,MACvB,aAAa,WAAW,YAAY,WAAW,IAAI,GAAG,WAAW,OAAO,EAAE,IAAI,GAAG,EAAE,UAAU,UAAA,CAAW,GAAG,SAAS;AAEtH,MAAM,mBAAmB;AAAA,EACvB,CACE;AAAA,IACE,oBAAoB;AAAA,IACpB,UAAU;AAAA,IACV,OAAO;AAAA,IACP,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,QAAI,mBAAmB;AACrB,iCACG,UAAA,EAAO,MAAa,GAAG,MAAM,KAAU,WACrC,UACH;AAAA,IAEJ;AAEA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACC,GAAG;AAAA,QACJ;AAAA,QACA,WAAW,mBAAmB,EAAE,SAAS,MAAM,MAAM,WAAW,WAAW;AAAA,QAE1E;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AACF;AAEA,iBAAiB,cAAc;"}
|
|
@@ -7,6 +7,10 @@ interface IControlledCheckboxProps {
|
|
|
7
7
|
className?: string;
|
|
8
8
|
labelClassName?: string;
|
|
9
9
|
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
10
|
+
/** The name when there is no visible `label`. */
|
|
11
|
+
'aria-label'?: string;
|
|
12
|
+
/** `-1` when the box only pictures the state of a surrounding control (a select option). */
|
|
13
|
+
tabIndex?: number;
|
|
10
14
|
}
|
|
11
|
-
declare const ControlledCheckbox: ({ checked, onChange, label, color, disabled, className, labelClassName, size, }: IControlledCheckboxProps) => import("react").JSX.Element;
|
|
15
|
+
declare const ControlledCheckbox: ({ checked, onChange, label, color, disabled, className, labelClassName, size, "aria-label": ariaLabel, tabIndex, }: IControlledCheckboxProps) => import("react").JSX.Element;
|
|
12
16
|
export default ControlledCheckbox;
|
|
@@ -9,7 +9,9 @@ const ControlledCheckbox = ({
|
|
|
9
9
|
disabled = false,
|
|
10
10
|
className = "",
|
|
11
11
|
labelClassName = "",
|
|
12
|
-
size = "md"
|
|
12
|
+
size = "md",
|
|
13
|
+
"aria-label": ariaLabel,
|
|
14
|
+
tabIndex
|
|
13
15
|
}) => {
|
|
14
16
|
const [colorClasses, setColorClasses] = useState("");
|
|
15
17
|
const [borderClasses, setBorderClasses] = useState("");
|
|
@@ -55,45 +57,57 @@ const ControlledCheckbox = ({
|
|
|
55
57
|
setColorClasses(colorMap[color] || "bg-brand");
|
|
56
58
|
setBorderClasses(borderMap[color] || "border-brand");
|
|
57
59
|
}, [color]);
|
|
58
|
-
return /* @__PURE__ */ jsxs(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
onChange: () => onChange(!checked),
|
|
66
|
-
disabled,
|
|
67
|
-
className: "peer sr-only"
|
|
68
|
-
}
|
|
60
|
+
return /* @__PURE__ */ jsxs(
|
|
61
|
+
"label",
|
|
62
|
+
{
|
|
63
|
+
className: classNames(
|
|
64
|
+
"relative flex items-center gap-2 px-[6px]",
|
|
65
|
+
disabled ? "cursor-not-allowed" : "cursor-pointer",
|
|
66
|
+
className
|
|
69
67
|
),
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
disabled
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
"
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
68
|
+
children: [
|
|
69
|
+
/* @__PURE__ */ jsx(
|
|
70
|
+
"input",
|
|
71
|
+
{
|
|
72
|
+
type: "checkbox",
|
|
73
|
+
checked,
|
|
74
|
+
onChange: () => onChange(!checked),
|
|
75
|
+
disabled,
|
|
76
|
+
"aria-label": ariaLabel,
|
|
77
|
+
tabIndex,
|
|
78
|
+
className: "peer sr-only"
|
|
79
|
+
}
|
|
80
|
+
),
|
|
81
|
+
/* @__PURE__ */ jsx(
|
|
82
|
+
"span",
|
|
83
|
+
{
|
|
84
|
+
className: classNames(
|
|
85
|
+
sizeConfig[size].container,
|
|
86
|
+
"flex shrink-0 items-center justify-center rounded-md border p-[2px] transition-colors duration-base",
|
|
87
|
+
// The input is visually hidden, so the box is where the keyboard sees its focus.
|
|
88
|
+
"peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2",
|
|
89
|
+
checked ? [colorClasses, "border-0"] : ["bg-surface", borderClasses],
|
|
90
|
+
disabled && "opacity-50"
|
|
91
|
+
),
|
|
92
|
+
children: checked && /* @__PURE__ */ jsx(
|
|
93
|
+
"svg",
|
|
94
|
+
{
|
|
95
|
+
className: "text-white",
|
|
96
|
+
viewBox: "0 0 24 24",
|
|
97
|
+
fill: "none",
|
|
98
|
+
stroke: "currentColor",
|
|
99
|
+
strokeWidth: "3",
|
|
100
|
+
strokeLinecap: "round",
|
|
101
|
+
strokeLinejoin: "round",
|
|
102
|
+
children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" })
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
),
|
|
107
|
+
label && /* @__PURE__ */ jsx("span", { className: classNames("text-sm font-medium text-foreground", labelClassName), children: label })
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
);
|
|
97
111
|
};
|
|
98
112
|
export {
|
|
99
113
|
ControlledCheckbox as default
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ControlledCheckbox.js","sources":["../../../src/components/Form/ControlledCheckbox.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport classNames from 'classnames';\n\ninterface IControlledCheckboxProps {\n checked: boolean;\n onChange: (checked: boolean) => void;\n label?: string;\n color?: 'blue' | 'red' | 'green' | 'amber' | 'purple' | 'indigo' | 'pink' | 'teal';\n disabled?: boolean;\n className?: string;\n labelClassName?: string;\n size?: 'sm' | 'md' | 'lg' | 'xl';\n}\n\nconst ControlledCheckbox = ({\n checked,\n onChange,\n label,\n color = 'purple',\n disabled = false,\n className = '',\n labelClassName = '',\n size = 'md',\n}: IControlledCheckboxProps) => {\n const [colorClasses, setColorClasses] = useState('');\n const [borderClasses, setBorderClasses] = useState('');\n\n const sizeConfig = {\n sm: {\n container: 'w-4 h-4',\n checkmark: 'h-2 w-2',\n },\n md: {\n container: 'w-5 h-5',\n checkmark: 'h-3 w-3',\n },\n lg: {\n container: 'w-6 h-6',\n checkmark: 'h-4 w-4',\n },\n xl: {\n container: 'w-7 h-7',\n checkmark: 'h-5 w-5',\n },\n };\n\n useEffect(() => {\n const colorMap = {\n blue: 'bg-info',\n red: 'bg-danger',\n green: 'bg-success',\n amber: 'bg-warning',\n purple: 'bg-brand',\n indigo: 'bg-brand',\n pink: 'bg-accent',\n teal: 'bg-secondary',\n };\n\n const borderMap = {\n blue: 'border-info',\n red: 'border-danger',\n green: 'border-success',\n amber: 'border-warning',\n purple: 'border-border-strong',\n indigo: 'border-brand',\n pink: 'border-accent',\n teal: 'border-secondary',\n };\n\n setColorClasses(colorMap[color] || 'bg-brand');\n setBorderClasses(borderMap[color] || 'border-brand');\n }, [color]);\n\n return (\n <
|
|
1
|
+
{"version":3,"file":"ControlledCheckbox.js","sources":["../../../src/components/Form/ControlledCheckbox.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport classNames from 'classnames';\n\ninterface IControlledCheckboxProps {\n checked: boolean;\n onChange: (checked: boolean) => void;\n label?: string;\n color?: 'blue' | 'red' | 'green' | 'amber' | 'purple' | 'indigo' | 'pink' | 'teal';\n disabled?: boolean;\n className?: string;\n labelClassName?: string;\n size?: 'sm' | 'md' | 'lg' | 'xl';\n /** The name when there is no visible `label`. */\n 'aria-label'?: string;\n /** `-1` when the box only pictures the state of a surrounding control (a select option). */\n tabIndex?: number;\n}\n\nconst ControlledCheckbox = ({\n checked,\n onChange,\n label,\n color = 'purple',\n disabled = false,\n className = '',\n labelClassName = '',\n size = 'md',\n 'aria-label': ariaLabel,\n tabIndex,\n}: IControlledCheckboxProps) => {\n const [colorClasses, setColorClasses] = useState('');\n const [borderClasses, setBorderClasses] = useState('');\n\n const sizeConfig = {\n sm: {\n container: 'w-4 h-4',\n checkmark: 'h-2 w-2',\n },\n md: {\n container: 'w-5 h-5',\n checkmark: 'h-3 w-3',\n },\n lg: {\n container: 'w-6 h-6',\n checkmark: 'h-4 w-4',\n },\n xl: {\n container: 'w-7 h-7',\n checkmark: 'h-5 w-5',\n },\n };\n\n useEffect(() => {\n const colorMap = {\n blue: 'bg-info',\n red: 'bg-danger',\n green: 'bg-success',\n amber: 'bg-warning',\n purple: 'bg-brand',\n indigo: 'bg-brand',\n pink: 'bg-accent',\n teal: 'bg-secondary',\n };\n\n const borderMap = {\n blue: 'border-info',\n red: 'border-danger',\n green: 'border-success',\n amber: 'border-warning',\n purple: 'border-border-strong',\n indigo: 'border-brand',\n pink: 'border-accent',\n teal: 'border-secondary',\n };\n\n setColorClasses(colorMap[color] || 'bg-brand');\n setBorderClasses(borderMap[color] || 'border-brand');\n }, [color]);\n\n return (\n <label\n className={classNames(\n 'relative flex items-center gap-2 px-[6px]',\n disabled ? 'cursor-not-allowed' : 'cursor-pointer',\n className,\n )}\n >\n <input\n type=\"checkbox\"\n checked={checked}\n onChange={() => onChange(!checked)}\n disabled={disabled}\n aria-label={ariaLabel}\n tabIndex={tabIndex}\n className=\"peer sr-only\"\n />\n <span\n className={classNames(\n sizeConfig[size].container,\n 'flex shrink-0 items-center justify-center rounded-md border p-[2px] transition-colors duration-base',\n // The input is visually hidden, so the box is where the keyboard sees its focus.\n 'peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2',\n checked ? [colorClasses, 'border-0'] : ['bg-surface', borderClasses],\n disabled && 'opacity-50',\n )}\n >\n {checked && (\n <svg\n className=\"text-white\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n {/* eslint-disable-next-line i18n-check/no-literal-jsx-strings */}\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n )}\n </span>\n {label && <span className={classNames('text-sm font-medium text-foreground', labelClassName)}>{label}</span>}\n </label>\n );\n};\n\nexport default ControlledCheckbox;\n"],"names":[],"mappings":";;;AAkBA,MAAM,qBAAqB,CAAC;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,OAAO;AAAA,EACP,cAAc;AAAA,EACd;AACF,MAAgC;AAC9B,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,EAAE;AACnD,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,EAAE;AAErD,QAAM,aAAa;AAAA,IACjB,IAAI;AAAA,MACF,WAAW;AAAA,MACX,WAAW;AAAA,IAAA;AAAA,IAEb,IAAI;AAAA,MACF,WAAW;AAAA,MACX,WAAW;AAAA,IAAA;AAAA,IAEb,IAAI;AAAA,MACF,WAAW;AAAA,MACX,WAAW;AAAA,IAAA;AAAA,IAEb,IAAI;AAAA,MACF,WAAW;AAAA,MACX,WAAW;AAAA,IAAA;AAAA,EACb;AAGF,YAAU,MAAM;AACd,UAAM,WAAW;AAAA,MACf,MAAM;AAAA,MACN,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,MAAM;AAAA,IAAA;AAGR,UAAM,YAAY;AAAA,MAChB,MAAM;AAAA,MACN,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,MAAM;AAAA,IAAA;AAGR,oBAAgB,SAAS,KAAK,KAAK,UAAU;AAC7C,qBAAiB,UAAU,KAAK,KAAK,cAAc;AAAA,EACrD,GAAG,CAAC,KAAK,CAAC;AAEV,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,WAAW,uBAAuB;AAAA,QAClC;AAAA,MAAA;AAAA,MAGF,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL;AAAA,YACA,UAAU,MAAM,SAAS,CAAC,OAAO;AAAA,YACjC;AAAA,YACA,cAAY;AAAA,YACZ;AAAA,YACA,WAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAEZ;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW;AAAA,cACT,WAAW,IAAI,EAAE;AAAA,cACjB;AAAA;AAAA,cAEA;AAAA,cACA,UAAU,CAAC,cAAc,UAAU,IAAI,CAAC,cAAc,aAAa;AAAA,cACnE,YAAY;AAAA,YAAA;AAAA,YAGb,UAAA,WACC;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,WAAU;AAAA,gBACV,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,QAAO;AAAA,gBACP,aAAY;AAAA,gBACZ,eAAc;AAAA,gBACd,gBAAe;AAAA,gBAGf,UAAA,oBAAC,YAAA,EAAS,QAAO,iBAAA,CAAiB;AAAA,cAAA;AAAA,YAAA;AAAA,UACpC;AAAA,QAAA;AAAA,QAGH,6BAAU,QAAA,EAAK,WAAW,WAAW,uCAAuC,cAAc,GAAI,UAAA,MAAA,CAAM;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAG3G;"}
|
|
@@ -3,7 +3,7 @@ import { createContext, useRef, useState, useContext, useId, useMemo, useEffect
|
|
|
3
3
|
import classNames from "classnames";
|
|
4
4
|
import { AnimatePresence, motion } from "motion/react";
|
|
5
5
|
import { createPortal } from "react-dom";
|
|
6
|
-
import {
|
|
6
|
+
import { mergeClasses } from "../../helpers/mergeClasses.js";
|
|
7
7
|
import { getPortalHost } from "../../helpers/portalHost.js";
|
|
8
8
|
import { scrimVariants, dialogVariants } from "../../motion/motionPresets.js";
|
|
9
9
|
import "../../motion/motionTokens.js";
|
|
@@ -184,7 +184,7 @@ const ControlledDialogRoot = ({
|
|
|
184
184
|
"aria-labelledby": hasTitle && !hasOwnName ? titleId : void 0,
|
|
185
185
|
tabIndex: -1,
|
|
186
186
|
...rest,
|
|
187
|
-
className:
|
|
187
|
+
className: mergeClasses(
|
|
188
188
|
classNames(
|
|
189
189
|
"relative m-4 flex max-h-[90vh] flex-col rounded-lg bg-surface text-base font-light leading-relaxed",
|
|
190
190
|
"text-foreground-muted shadow-xl outline-none",
|
|
@@ -202,15 +202,27 @@ const ControlledDialogRoot = ({
|
|
|
202
202
|
const ControlledDialogHeader = ({ children, className }) => /* @__PURE__ */ jsx(
|
|
203
203
|
"div",
|
|
204
204
|
{
|
|
205
|
-
className:
|
|
205
|
+
className: mergeClasses(
|
|
206
206
|
"flex shrink-0 items-center p-4 text-2xl font-semibold leading-snug text-foreground antialiased",
|
|
207
207
|
className
|
|
208
208
|
),
|
|
209
209
|
children
|
|
210
210
|
}
|
|
211
211
|
);
|
|
212
|
-
const ControlledDialogBody = ({ children, className }) => /* @__PURE__ */ jsx(
|
|
213
|
-
|
|
212
|
+
const ControlledDialogBody = ({ children, className }) => /* @__PURE__ */ jsx(
|
|
213
|
+
"div",
|
|
214
|
+
{
|
|
215
|
+
className: mergeClasses("relative min-h-0 flex-1 p-4 text-base font-light leading-relaxed antialiased", className),
|
|
216
|
+
children
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
const ControlledDialogFooter = ({ children, className }) => /* @__PURE__ */ jsx(
|
|
220
|
+
"div",
|
|
221
|
+
{
|
|
222
|
+
className: mergeClasses("flex shrink-0 flex-wrap items-center justify-end p-4 text-foreground-muted", className),
|
|
223
|
+
children
|
|
224
|
+
}
|
|
225
|
+
);
|
|
214
226
|
const ControlledDialogTitle = ({ children, className }) => {
|
|
215
227
|
const context = useContext(DialogTitleContext);
|
|
216
228
|
const registerTitle = context?.registerTitle;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ControlledDialogPrimitives.js","sources":["../../../src/components/Form/ControlledDialogPrimitives.tsx"],"sourcesContent":["import React, { createContext, useContext, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { AnimatePresence, motion } from 'motion/react';\nimport { createPortal } from 'react-dom';\nimport { twMerge } from 'tailwind-merge';\n\nimport { getPortalHost } from '@/helpers/portalHost';\nimport { dialogVariants, scrimVariants } from '@/motion';\n\nexport type TDialogSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';\n\n/**\n * Where focus lands when the dialog opens. `auto` (default): an element that asked for it\n * (`autoFocus` / `data-autofocus`), else the first tabbable element outside a\n * `data-dialog-initial-focus=\"skip\"` zone, else the panel itself. `panel`: the panel, the\n * pre-0.13 behaviour. A ref: that element.\n */\nexport type TDialogInitialFocus = 'auto' | 'panel' | React.RefObject<HTMLElement | null>;\n\nexport interface IDialogDismissConfig {\n enabled?: boolean;\n escapeKey?: boolean;\n outsidePress?: boolean;\n}\n\nconst SIZE_CLASS: Record<TDialogSize, string> = {\n xs: 'w-full md:w-3/5 lg:w-2/5 2xl:w-1/4 min-w-[80%] md:min-w-[60%] lg:min-w-[40%] 2xl:min-w-[25%] max-w-[80%] md:max-w-[60%] lg:max-w-[40%] 2xl:max-w-[25%]',\n sm: 'w-full md:w-2/3 lg:w-2/4 2xl:w-1/3 min-w-[80%] md:min-w-[66.666667%] lg:min-w-[50%] 2xl:min-w-[33.333333%] max-w-[80%] md:max-w-[66.666667%] lg:max-w-[50%] 2xl:max-w-[33.333333%]',\n md: 'w-full md:w-3/4 lg:w-3/5 2xl:w-2/5 min-w-[90%] md:min-w-[75%] lg:min-w-[60%] 2xl:min-w-[40%] max-w-[90%] md:max-w-[75%] lg:max-w-[60%] 2xl:max-w-[40%]',\n lg: 'w-full md:w-5/6 lg:w-3/4 2xl:w-3/5 min-w-[90%] md:min-w-[83.333333%] lg:min-w-[75%] 2xl:min-w-[60%] max-w-[90%] md:max-w-[83.333333%] lg:max-w-[75%] 2xl:max-w-[60%]',\n xl: 'w-full md:w-5/6 2xl:w-3/4 min-w-[95%] md:min-w-[83.333333%] 2xl:min-w-[75%] max-w-[95%] md:max-w-[83.333333%] 2xl:max-w-[75%]',\n xxl: 'w-screen min-w-[100vw] max-w-[100vw]',\n};\n\ntype TDivPropsWithoutMotionConflicts = Omit<\n React.HTMLAttributes<HTMLDivElement>,\n | 'className'\n | 'onScroll'\n | 'onDrag'\n | 'onDragStart'\n | 'onDragEnd'\n | 'onAnimationStart'\n | 'onAnimationEnd'\n | 'onAnimationIteration'\n>;\n\ninterface IControlledDialogRootProps extends TDivPropsWithoutMotionConflicts {\n open: boolean;\n handler?: () => void;\n size?: TDialogSize;\n dismiss?: IDialogDismissConfig;\n className?: string;\n overlayClassName?: string;\n initialFocus?: TDialogInitialFocus;\n children: React.ReactNode;\n}\n\nconst TABBABLE_SELECTOR = [\n 'a[href]',\n 'area[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n 'iframe',\n 'audio[controls]',\n 'video[controls]',\n '[contenteditable]:not([contenteditable=\"false\"])',\n '[tabindex]',\n].join(',');\n\nconst isTabbable = (element: HTMLElement): boolean => {\n if (element.tabIndex < 0) return false;\n if (element.closest('[hidden], [inert]')) return false;\n if (typeof element.checkVisibility === 'function' && !element.checkVisibility()) return false;\n return true;\n};\n\nexport const getTabbableElements = (container: HTMLElement): Array<HTMLElement> =>\n Array.from(container.querySelectorAll<HTMLElement>(TABBABLE_SELECTOR)).filter(isTabbable);\n\n/* Open panels, top last: only the top one traps Tab and answers ESC. Ordered by React nesting\n depth (a dialog rendered inside another sits above it), then by document order, which is\n opening order for siblings because each one portals to the end of the host. */\nconst openDialogStack: Array<{ panel: HTMLElement; depth: number }> = [];\n\nconst pushOpenDialog = (panel: HTMLElement, depth: number) => {\n openDialogStack.push({ panel, depth });\n openDialogStack.sort((a, b) => {\n if (a.depth !== b.depth) return a.depth - b.depth;\n return a.panel.compareDocumentPosition(b.panel) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;\n });\n};\n\nconst removeOpenDialog = (panel: HTMLElement) => {\n const index = openDialogStack.findIndex((entry) => entry.panel === panel);\n if (index >= 0) openDialogStack.splice(index, 1);\n};\n\nconst DialogDepthContext = createContext(0);\n\nconst isTopDialog = (panel: HTMLElement | null) =>\n panel !== null && openDialogStack[openDialogStack.length - 1]?.panel === panel;\n\nconst resolveInitialFocus = (panel: HTMLElement, initialFocus: TDialogInitialFocus): HTMLElement => {\n if (initialFocus === 'panel') return panel;\n if (initialFocus !== 'auto') return initialFocus.current ?? panel;\n\n const requested = panel.querySelector<HTMLElement>('[data-autofocus], [autofocus]');\n if (requested) return requested;\n\n const firstTabbable = getTabbableElements(panel).find(\n (element) => !element.closest('[data-dialog-initial-focus=\"skip\"]'),\n );\n return firstTabbable ?? panel;\n};\n\n/** Spread on a wrapper whose controls must not take the initial focus (the header close button). */\nexport const DIALOG_SKIP_INITIAL_FOCUS_PROPS = { 'data-dialog-initial-focus': 'skip' } as const;\n\ninterface IDialogTitleContext {\n titleId: string;\n registerTitle: (present: boolean) => void;\n}\n\nconst DialogTitleContext = createContext<IDialogTitleContext | null>(null);\n\n/**\n * Portals only while open (plus its exit transition), so a closed dialog inside a mapped\n * list renders nothing at all.\n */\nexport const ControlledDialogRoot: React.FC<IControlledDialogRootProps> = ({\n open,\n handler,\n size = 'md',\n dismiss,\n className,\n overlayClassName,\n initialFocus = 'auto',\n children,\n ...rest\n}) => {\n const panelRef = useRef<HTMLDivElement>(null);\n const [isLeaving, setIsLeaving] = useState(false);\n const previouslyFocusedRef = useRef<HTMLElement | null>(null);\n const initialFocusRef = useRef(initialFocus);\n initialFocusRef.current = initialFocus;\n\n const depth = useContext(DialogDepthContext) + 1;\n const titleId = useId();\n const [hasTitle, setHasTitle] = useState(false);\n const titleContext = useMemo(() => ({ titleId, registerTitle: setHasTitle }), [titleId]);\n const hasOwnName = rest['aria-label'] !== undefined || rest['aria-labelledby'] !== undefined;\n\n useEffect(() => {\n if (open) setIsLeaving(true);\n }, [open]);\n\n /* Held until the exit finishes, not until `open` flips: releasing it early brings the\n page scrollbar back while the panel is still leaving, and the content behind jumps. */\n useEffect(() => {\n if (!open && !isLeaving) return undefined;\n\n const { overflow } = document.body.style;\n document.body.style.overflow = 'hidden';\n return () => {\n document.body.style.overflow = overflow;\n };\n }, [open, isLeaving]);\n\n useEffect(() => {\n if (!open) return undefined;\n\n previouslyFocusedRef.current = document.activeElement as HTMLElement | null;\n const frame = requestAnimationFrame(() => {\n const panel = panelRef.current;\n if (!panel) return;\n // Something inside already took focus (a React `autoFocus` field): keep it.\n if (panel !== document.activeElement && panel.contains(document.activeElement)) return;\n resolveInitialFocus(panel, initialFocusRef.current).focus();\n });\n\n return () => {\n cancelAnimationFrame(frame);\n previouslyFocusedRef.current?.focus?.();\n };\n }, [open]);\n\n useEffect(() => {\n const panel = panelRef.current;\n if (!open || !panel) return undefined;\n\n pushOpenDialog(panel, depth);\n\n const handleTab = (event: KeyboardEvent) => {\n if (event.key !== 'Tab' || event.defaultPrevented || !isTopDialog(panel)) return;\n\n const active = document.activeElement as HTMLElement | null;\n const isInside = active !== null && panel.contains(active);\n // Focus in a layer opened from inside the dialog (a select list, a popover) belongs to\n // that layer; only focus that escaped back to the page behind is pulled in.\n const isInPageBehind =\n !active ||\n active === document.body ||\n Boolean(panel.compareDocumentPosition(active) & Node.DOCUMENT_POSITION_PRECEDING);\n if (!isInside && !isInPageBehind) return;\n\n const tabbables = getTabbableElements(panel);\n if (tabbables.length === 0) {\n event.preventDefault();\n panel.focus();\n return;\n }\n\n const first = tabbables[0];\n const last = tabbables[tabbables.length - 1];\n\n if (!isInside || active === panel) {\n event.preventDefault();\n (event.shiftKey ? last : first).focus();\n } else if (event.shiftKey && active === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\n }\n };\n\n document.addEventListener('keydown', handleTab);\n return () => {\n document.removeEventListener('keydown', handleTab);\n removeOpenDialog(panel);\n };\n }, [open]);\n\n useEffect(() => {\n if (!open || dismiss?.enabled === false || dismiss?.escapeKey === false) return undefined;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n // A layer inside the dialog (a select list) that consumed the ESC marks it handled.\n if (event.key !== 'Escape' || event.defaultPrevented) return;\n if (panelRef.current && openDialogStack.length > 0 && !isTopDialog(panelRef.current)) return;\n handler?.();\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [open, dismiss, handler]);\n\n const portalHost = getPortalHost();\n if (portalHost === null) return null;\n\n return createPortal(\n <AnimatePresence onExitComplete={() => setIsLeaving(false)}>\n {open && (\n <motion.div key=\"dialog-layer\" className=\"fixed inset-0 z-[9999] flex items-center justify-center\">\n <motion.div\n variants={scrimVariants}\n initial=\"hidden\"\n animate=\"visible\"\n exit=\"exit\"\n aria-hidden\n onClick={() => {\n if (dismiss?.enabled === false || !dismiss?.outsidePress) return;\n handler?.();\n }}\n className={classNames('absolute inset-0 glass-scrim', overlayClassName)}\n />\n\n <motion.div\n ref={panelRef}\n variants={dialogVariants}\n initial=\"hidden\"\n animate=\"visible\"\n exit=\"exit\"\n role=\"dialog\"\n aria-modal={true}\n aria-labelledby={hasTitle && !hasOwnName ? titleId : undefined}\n tabIndex={-1}\n {...rest}\n className={twMerge(\n classNames(\n 'relative m-4 flex max-h-[90vh] flex-col rounded-lg bg-surface text-base font-light leading-relaxed',\n 'text-foreground-muted shadow-xl outline-none',\n SIZE_CLASS[size],\n ),\n className,\n )}\n >\n <DialogDepthContext.Provider value={depth}>\n <DialogTitleContext.Provider value={titleContext}>{children}</DialogTitleContext.Provider>\n </DialogDepthContext.Provider>\n </motion.div>\n </motion.div>\n )}\n </AnimatePresence>,\n portalHost,\n );\n};\n\ninterface IDialogSectionProps {\n children: React.ReactNode;\n className?: string;\n}\n\nexport const ControlledDialogHeader: React.FC<IDialogSectionProps> = ({ children, className }) => (\n <div\n className={twMerge(\n 'flex shrink-0 items-center p-4 text-2xl font-semibold leading-snug text-foreground antialiased',\n className,\n )}\n >\n {children}\n </div>\n);\n\nexport const ControlledDialogBody: React.FC<IDialogSectionProps> = ({ children, className }) => (\n <div className={twMerge('relative min-h-0 flex-1 p-4 text-base font-light leading-relaxed antialiased', className)}>\n {children}\n </div>\n);\n\nexport const ControlledDialogFooter: React.FC<IDialogSectionProps> = ({ children, className }) => (\n <div className={twMerge('flex shrink-0 flex-wrap items-center justify-end p-4 text-foreground-muted', className)}>\n {children}\n </div>\n);\n\ninterface IControlledDialogTitleProps {\n children: React.ReactNode;\n className?: string;\n}\n\n/**\n * The dialog's name: a level-2 heading whose id the root wires into `aria-labelledby`, unless\n * the root received its own `aria-label` / `aria-labelledby`. A `div` with the heading role, so\n * any title content (blocks included) stays valid markup.\n */\nexport const ControlledDialogTitle: React.FC<IControlledDialogTitleProps> = ({ children, className }) => {\n const context = useContext(DialogTitleContext);\n const registerTitle = context?.registerTitle;\n\n useEffect(() => {\n if (!registerTitle) return undefined;\n registerTitle(true);\n return () => registerTitle(false);\n }, [registerTitle]);\n\n return (\n <div id={context?.titleId} role=\"heading\" aria-level={2} className={className}>\n {children}\n </div>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;AAyBA,MAAM,aAA0C;AAAA,EAC9C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,KAAK;AACP;AAyBA,MAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAEV,MAAM,aAAa,CAAC,YAAkC;AACpD,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,MAAI,QAAQ,QAAQ,mBAAmB,EAAG,QAAO;AACjD,MAAI,OAAO,QAAQ,oBAAoB,cAAc,CAAC,QAAQ,gBAAA,EAAmB,QAAO;AACxF,SAAO;AACT;AAEO,MAAM,sBAAsB,CAAC,cAClC,MAAM,KAAK,UAAU,iBAA8B,iBAAiB,CAAC,EAAE,OAAO,UAAU;AAK1F,MAAM,kBAAgE,CAAA;AAEtE,MAAM,iBAAiB,CAAC,OAAoB,UAAkB;AAC5D,kBAAgB,KAAK,EAAE,OAAO,MAAA,CAAO;AACrC,kBAAgB,KAAK,CAAC,GAAG,MAAM;AAC7B,QAAI,EAAE,UAAU,EAAE,MAAO,QAAO,EAAE,QAAQ,EAAE;AAC5C,WAAO,EAAE,MAAM,wBAAwB,EAAE,KAAK,IAAI,KAAK,8BAA8B,KAAK;AAAA,EAC5F,CAAC;AACH;AAEA,MAAM,mBAAmB,CAAC,UAAuB;AAC/C,QAAM,QAAQ,gBAAgB,UAAU,CAAC,UAAU,MAAM,UAAU,KAAK;AACxE,MAAI,SAAS,EAAG,iBAAgB,OAAO,OAAO,CAAC;AACjD;AAEA,MAAM,qBAAqB,cAAc,CAAC;AAE1C,MAAM,cAAc,CAAC,UACnB,UAAU,QAAQ,gBAAgB,gBAAgB,SAAS,CAAC,GAAG,UAAU;AAE3E,MAAM,sBAAsB,CAAC,OAAoB,iBAAmD;AAClG,MAAI,iBAAiB,QAAS,QAAO;AACrC,MAAI,iBAAiB,OAAQ,QAAO,aAAa,WAAW;AAE5D,QAAM,YAAY,MAAM,cAA2B,+BAA+B;AAClF,MAAI,UAAW,QAAO;AAEtB,QAAM,gBAAgB,oBAAoB,KAAK,EAAE;AAAA,IAC/C,CAAC,YAAY,CAAC,QAAQ,QAAQ,oCAAoC;AAAA,EAAA;AAEpE,SAAO,iBAAiB;AAC1B;AAGO,MAAM,kCAAkC,EAAE,6BAA6B,OAAA;AAO9E,MAAM,qBAAqB,cAA0C,IAAI;AAMlE,MAAM,uBAA6D,CAAC;AAAA,EACzE;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA,GAAG;AACL,MAAM;AACJ,QAAM,WAAW,OAAuB,IAAI;AAC5C,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,QAAM,uBAAuB,OAA2B,IAAI;AAC5D,QAAM,kBAAkB,OAAO,YAAY;AAC3C,kBAAgB,UAAU;AAE1B,QAAM,QAAQ,WAAW,kBAAkB,IAAI;AAC/C,QAAM,UAAU,MAAA;AAChB,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,eAAe,QAAQ,OAAO,EAAE,SAAS,eAAe,YAAA,IAAgB,CAAC,OAAO,CAAC;AACvF,QAAM,aAAa,KAAK,YAAY,MAAM,UAAa,KAAK,iBAAiB,MAAM;AAEnF,YAAU,MAAM;AACd,QAAI,mBAAmB,IAAI;AAAA,EAC7B,GAAG,CAAC,IAAI,CAAC;AAIT,YAAU,MAAM;AACd,QAAI,CAAC,QAAQ,CAAC,UAAW,QAAO;AAEhC,UAAM,EAAE,SAAA,IAAa,SAAS,KAAK;AACnC,aAAS,KAAK,MAAM,WAAW;AAC/B,WAAO,MAAM;AACX,eAAS,KAAK,MAAM,WAAW;AAAA,IACjC;AAAA,EACF,GAAG,CAAC,MAAM,SAAS,CAAC;AAEpB,YAAU,MAAM;AACd,QAAI,CAAC,KAAM,QAAO;AAElB,yBAAqB,UAAU,SAAS;AACxC,UAAM,QAAQ,sBAAsB,MAAM;AACxC,YAAM,QAAQ,SAAS;AACvB,UAAI,CAAC,MAAO;AAEZ,UAAI,UAAU,SAAS,iBAAiB,MAAM,SAAS,SAAS,aAAa,EAAG;AAChF,0BAAoB,OAAO,gBAAgB,OAAO,EAAE,MAAA;AAAA,IACtD,CAAC;AAED,WAAO,MAAM;AACX,2BAAqB,KAAK;AAC1B,2BAAqB,SAAS,QAAA;AAAA,IAChC;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,YAAU,MAAM;AACd,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,QAAQ,CAAC,MAAO,QAAO;AAE5B,mBAAe,OAAO,KAAK;AAE3B,UAAM,YAAY,CAAC,UAAyB;AAC1C,UAAI,MAAM,QAAQ,SAAS,MAAM,oBAAoB,CAAC,YAAY,KAAK,EAAG;AAE1E,YAAM,SAAS,SAAS;AACxB,YAAM,WAAW,WAAW,QAAQ,MAAM,SAAS,MAAM;AAGzD,YAAM,iBACJ,CAAC,UACD,WAAW,SAAS,QACpB,QAAQ,MAAM,wBAAwB,MAAM,IAAI,KAAK,2BAA2B;AAClF,UAAI,CAAC,YAAY,CAAC,eAAgB;AAElC,YAAM,YAAY,oBAAoB,KAAK;AAC3C,UAAI,UAAU,WAAW,GAAG;AAC1B,cAAM,eAAA;AACN,cAAM,MAAA;AACN;AAAA,MACF;AAEA,YAAM,QAAQ,UAAU,CAAC;AACzB,YAAM,OAAO,UAAU,UAAU,SAAS,CAAC;AAE3C,UAAI,CAAC,YAAY,WAAW,OAAO;AACjC,cAAM,eAAA;AACN,SAAC,MAAM,WAAW,OAAO,OAAO,MAAA;AAAA,MAClC,WAAW,MAAM,YAAY,WAAW,OAAO;AAC7C,cAAM,eAAA;AACN,aAAK,MAAA;AAAA,MACP,WAAW,CAAC,MAAM,YAAY,WAAW,MAAM;AAC7C,cAAM,eAAA;AACN,cAAM,MAAA;AAAA,MACR;AAAA,IACF;AAEA,aAAS,iBAAiB,WAAW,SAAS;AAC9C,WAAO,MAAM;AACX,eAAS,oBAAoB,WAAW,SAAS;AACjD,uBAAiB,KAAK;AAAA,IACxB;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,YAAU,MAAM;AACd,QAAI,CAAC,QAAQ,SAAS,YAAY,SAAS,SAAS,cAAc,MAAO,QAAO;AAEhF,UAAM,gBAAgB,CAAC,UAAyB;AAE9C,UAAI,MAAM,QAAQ,YAAY,MAAM,iBAAkB;AACtD,UAAI,SAAS,WAAW,gBAAgB,SAAS,KAAK,CAAC,YAAY,SAAS,OAAO,EAAG;AACtF,gBAAA;AAAA,IACF;AAEA,aAAS,iBAAiB,WAAW,aAAa;AAClD,WAAO,MAAM,SAAS,oBAAoB,WAAW,aAAa;AAAA,EACpE,GAAG,CAAC,MAAM,SAAS,OAAO,CAAC;AAE3B,QAAM,aAAa,cAAA;AACnB,MAAI,eAAe,KAAM,QAAO;AAEhC,SAAO;AAAA,IACL,oBAAC,iBAAA,EAAgB,gBAAgB,MAAM,aAAa,KAAK,GACtD,UAAA,QACC,qBAAC,OAAO,KAAP,EAA8B,WAAU,2DACvC,UAAA;AAAA,MAAA;AAAA,QAAC,OAAO;AAAA,QAAP;AAAA,UACC,UAAU;AAAA,UACV,SAAQ;AAAA,UACR,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,eAAW;AAAA,UACX,SAAS,MAAM;AACb,gBAAI,SAAS,YAAY,SAAS,CAAC,SAAS,aAAc;AAC1D,sBAAA;AAAA,UACF;AAAA,UACA,WAAW,WAAW,gCAAgC,gBAAgB;AAAA,QAAA;AAAA,MAAA;AAAA,MAGxE;AAAA,QAAC,OAAO;AAAA,QAAP;AAAA,UACC,KAAK;AAAA,UACL,UAAU;AAAA,UACV,SAAQ;AAAA,UACR,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,MAAK;AAAA,UACL,cAAY;AAAA,UACZ,mBAAiB,YAAY,CAAC,aAAa,UAAU;AAAA,UACrD,UAAU;AAAA,UACT,GAAG;AAAA,UACJ,WAAW;AAAA,YACT;AAAA,cACE;AAAA,cACA;AAAA,cACA,WAAW,IAAI;AAAA,YAAA;AAAA,YAEjB;AAAA,UAAA;AAAA,UAGF,UAAA,oBAAC,mBAAmB,UAAnB,EAA4B,OAAO,OAClC,UAAA,oBAAC,mBAAmB,UAAnB,EAA4B,OAAO,cAAe,UAAS,EAAA,CAC9D;AAAA,QAAA;AAAA,MAAA;AAAA,IACF,EAAA,GArCc,cAsChB,GAEJ;AAAA,IACA;AAAA,EAAA;AAEJ;AAOO,MAAM,yBAAwD,CAAC,EAAE,UAAU,gBAChF;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,WAAW;AAAA,MACT;AAAA,MACA;AAAA,IAAA;AAAA,IAGD;AAAA,EAAA;AACH;AAGK,MAAM,uBAAsD,CAAC,EAAE,UAAU,UAAA,MAC9E,oBAAC,OAAA,EAAI,WAAW,QAAQ,gFAAgF,SAAS,GAC9G,SAAA,CACH;AAGK,MAAM,yBAAwD,CAAC,EAAE,UAAU,UAAA,MAChF,oBAAC,OAAA,EAAI,WAAW,QAAQ,8EAA8E,SAAS,GAC5G,SAAA,CACH;AAaK,MAAM,wBAA+D,CAAC,EAAE,UAAU,gBAAgB;AACvG,QAAM,UAAU,WAAW,kBAAkB;AAC7C,QAAM,gBAAgB,SAAS;AAE/B,YAAU,MAAM;AACd,QAAI,CAAC,cAAe,QAAO;AAC3B,kBAAc,IAAI;AAClB,WAAO,MAAM,cAAc,KAAK;AAAA,EAClC,GAAG,CAAC,aAAa,CAAC;AAElB,SACE,oBAAC,OAAA,EAAI,IAAI,SAAS,SAAS,MAAK,WAAU,cAAY,GAAG,WACtD,SAAA,CACH;AAEJ;"}
|
|
1
|
+
{"version":3,"file":"ControlledDialogPrimitives.js","sources":["../../../src/components/Form/ControlledDialogPrimitives.tsx"],"sourcesContent":["import React, { createContext, useContext, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { AnimatePresence, motion } from 'motion/react';\nimport { createPortal } from 'react-dom';\n\nimport { mergeClasses } from '@/helpers/mergeClasses';\nimport { getPortalHost } from '@/helpers/portalHost';\nimport { dialogVariants, scrimVariants } from '@/motion';\n\nexport type TDialogSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';\n\n/**\n * Where focus lands when the dialog opens. `auto` (default): an element that asked for it\n * (`autoFocus` / `data-autofocus`), else the first tabbable element outside a\n * `data-dialog-initial-focus=\"skip\"` zone, else the panel itself. `panel`: the panel, the\n * pre-0.13 behaviour. A ref: that element.\n */\nexport type TDialogInitialFocus = 'auto' | 'panel' | React.RefObject<HTMLElement | null>;\n\nexport interface IDialogDismissConfig {\n enabled?: boolean;\n escapeKey?: boolean;\n outsidePress?: boolean;\n}\n\nconst SIZE_CLASS: Record<TDialogSize, string> = {\n xs: 'w-full md:w-3/5 lg:w-2/5 2xl:w-1/4 min-w-[80%] md:min-w-[60%] lg:min-w-[40%] 2xl:min-w-[25%] max-w-[80%] md:max-w-[60%] lg:max-w-[40%] 2xl:max-w-[25%]',\n sm: 'w-full md:w-2/3 lg:w-2/4 2xl:w-1/3 min-w-[80%] md:min-w-[66.666667%] lg:min-w-[50%] 2xl:min-w-[33.333333%] max-w-[80%] md:max-w-[66.666667%] lg:max-w-[50%] 2xl:max-w-[33.333333%]',\n md: 'w-full md:w-3/4 lg:w-3/5 2xl:w-2/5 min-w-[90%] md:min-w-[75%] lg:min-w-[60%] 2xl:min-w-[40%] max-w-[90%] md:max-w-[75%] lg:max-w-[60%] 2xl:max-w-[40%]',\n lg: 'w-full md:w-5/6 lg:w-3/4 2xl:w-3/5 min-w-[90%] md:min-w-[83.333333%] lg:min-w-[75%] 2xl:min-w-[60%] max-w-[90%] md:max-w-[83.333333%] lg:max-w-[75%] 2xl:max-w-[60%]',\n xl: 'w-full md:w-5/6 2xl:w-3/4 min-w-[95%] md:min-w-[83.333333%] 2xl:min-w-[75%] max-w-[95%] md:max-w-[83.333333%] 2xl:max-w-[75%]',\n xxl: 'w-screen min-w-[100vw] max-w-[100vw]',\n};\n\ntype TDivPropsWithoutMotionConflicts = Omit<\n React.HTMLAttributes<HTMLDivElement>,\n | 'className'\n | 'onScroll'\n | 'onDrag'\n | 'onDragStart'\n | 'onDragEnd'\n | 'onAnimationStart'\n | 'onAnimationEnd'\n | 'onAnimationIteration'\n>;\n\ninterface IControlledDialogRootProps extends TDivPropsWithoutMotionConflicts {\n open: boolean;\n handler?: () => void;\n size?: TDialogSize;\n dismiss?: IDialogDismissConfig;\n className?: string;\n overlayClassName?: string;\n initialFocus?: TDialogInitialFocus;\n children: React.ReactNode;\n}\n\nconst TABBABLE_SELECTOR = [\n 'a[href]',\n 'area[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n 'iframe',\n 'audio[controls]',\n 'video[controls]',\n '[contenteditable]:not([contenteditable=\"false\"])',\n '[tabindex]',\n].join(',');\n\nconst isTabbable = (element: HTMLElement): boolean => {\n if (element.tabIndex < 0) return false;\n if (element.closest('[hidden], [inert]')) return false;\n if (typeof element.checkVisibility === 'function' && !element.checkVisibility()) return false;\n return true;\n};\n\nexport const getTabbableElements = (container: HTMLElement): Array<HTMLElement> =>\n Array.from(container.querySelectorAll<HTMLElement>(TABBABLE_SELECTOR)).filter(isTabbable);\n\n/* Open panels, top last: only the top one traps Tab and answers ESC. Ordered by React nesting\n depth (a dialog rendered inside another sits above it), then by document order, which is\n opening order for siblings because each one portals to the end of the host. */\nconst openDialogStack: Array<{ panel: HTMLElement; depth: number }> = [];\n\nconst pushOpenDialog = (panel: HTMLElement, depth: number) => {\n openDialogStack.push({ panel, depth });\n openDialogStack.sort((a, b) => {\n if (a.depth !== b.depth) return a.depth - b.depth;\n return a.panel.compareDocumentPosition(b.panel) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;\n });\n};\n\nconst removeOpenDialog = (panel: HTMLElement) => {\n const index = openDialogStack.findIndex((entry) => entry.panel === panel);\n if (index >= 0) openDialogStack.splice(index, 1);\n};\n\nconst DialogDepthContext = createContext(0);\n\nconst isTopDialog = (panel: HTMLElement | null) =>\n panel !== null && openDialogStack[openDialogStack.length - 1]?.panel === panel;\n\nconst resolveInitialFocus = (panel: HTMLElement, initialFocus: TDialogInitialFocus): HTMLElement => {\n if (initialFocus === 'panel') return panel;\n if (initialFocus !== 'auto') return initialFocus.current ?? panel;\n\n const requested = panel.querySelector<HTMLElement>('[data-autofocus], [autofocus]');\n if (requested) return requested;\n\n const firstTabbable = getTabbableElements(panel).find(\n (element) => !element.closest('[data-dialog-initial-focus=\"skip\"]'),\n );\n return firstTabbable ?? panel;\n};\n\n/** Spread on a wrapper whose controls must not take the initial focus (the header close button). */\nexport const DIALOG_SKIP_INITIAL_FOCUS_PROPS = { 'data-dialog-initial-focus': 'skip' } as const;\n\ninterface IDialogTitleContext {\n titleId: string;\n registerTitle: (present: boolean) => void;\n}\n\nconst DialogTitleContext = createContext<IDialogTitleContext | null>(null);\n\n/**\n * Portals only while open (plus its exit transition), so a closed dialog inside a mapped\n * list renders nothing at all.\n */\nexport const ControlledDialogRoot: React.FC<IControlledDialogRootProps> = ({\n open,\n handler,\n size = 'md',\n dismiss,\n className,\n overlayClassName,\n initialFocus = 'auto',\n children,\n ...rest\n}) => {\n const panelRef = useRef<HTMLDivElement>(null);\n const [isLeaving, setIsLeaving] = useState(false);\n const previouslyFocusedRef = useRef<HTMLElement | null>(null);\n const initialFocusRef = useRef(initialFocus);\n initialFocusRef.current = initialFocus;\n\n const depth = useContext(DialogDepthContext) + 1;\n const titleId = useId();\n const [hasTitle, setHasTitle] = useState(false);\n const titleContext = useMemo(() => ({ titleId, registerTitle: setHasTitle }), [titleId]);\n const hasOwnName = rest['aria-label'] !== undefined || rest['aria-labelledby'] !== undefined;\n\n useEffect(() => {\n if (open) setIsLeaving(true);\n }, [open]);\n\n /* Held until the exit finishes, not until `open` flips: releasing it early brings the\n page scrollbar back while the panel is still leaving, and the content behind jumps. */\n useEffect(() => {\n if (!open && !isLeaving) return undefined;\n\n const { overflow } = document.body.style;\n document.body.style.overflow = 'hidden';\n return () => {\n document.body.style.overflow = overflow;\n };\n }, [open, isLeaving]);\n\n useEffect(() => {\n if (!open) return undefined;\n\n previouslyFocusedRef.current = document.activeElement as HTMLElement | null;\n const frame = requestAnimationFrame(() => {\n const panel = panelRef.current;\n if (!panel) return;\n // Something inside already took focus (a React `autoFocus` field): keep it.\n if (panel !== document.activeElement && panel.contains(document.activeElement)) return;\n resolveInitialFocus(panel, initialFocusRef.current).focus();\n });\n\n return () => {\n cancelAnimationFrame(frame);\n previouslyFocusedRef.current?.focus?.();\n };\n }, [open]);\n\n useEffect(() => {\n const panel = panelRef.current;\n if (!open || !panel) return undefined;\n\n pushOpenDialog(panel, depth);\n\n const handleTab = (event: KeyboardEvent) => {\n if (event.key !== 'Tab' || event.defaultPrevented || !isTopDialog(panel)) return;\n\n const active = document.activeElement as HTMLElement | null;\n const isInside = active !== null && panel.contains(active);\n // Focus in a layer opened from inside the dialog (a select list, a popover) belongs to\n // that layer; only focus that escaped back to the page behind is pulled in.\n const isInPageBehind =\n !active ||\n active === document.body ||\n Boolean(panel.compareDocumentPosition(active) & Node.DOCUMENT_POSITION_PRECEDING);\n if (!isInside && !isInPageBehind) return;\n\n const tabbables = getTabbableElements(panel);\n if (tabbables.length === 0) {\n event.preventDefault();\n panel.focus();\n return;\n }\n\n const first = tabbables[0];\n const last = tabbables[tabbables.length - 1];\n\n if (!isInside || active === panel) {\n event.preventDefault();\n (event.shiftKey ? last : first).focus();\n } else if (event.shiftKey && active === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\n }\n };\n\n document.addEventListener('keydown', handleTab);\n return () => {\n document.removeEventListener('keydown', handleTab);\n removeOpenDialog(panel);\n };\n }, [open]);\n\n useEffect(() => {\n if (!open || dismiss?.enabled === false || dismiss?.escapeKey === false) return undefined;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n // A layer inside the dialog (a select list) that consumed the ESC marks it handled.\n if (event.key !== 'Escape' || event.defaultPrevented) return;\n if (panelRef.current && openDialogStack.length > 0 && !isTopDialog(panelRef.current)) return;\n handler?.();\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [open, dismiss, handler]);\n\n const portalHost = getPortalHost();\n if (portalHost === null) return null;\n\n return createPortal(\n <AnimatePresence onExitComplete={() => setIsLeaving(false)}>\n {open && (\n <motion.div key=\"dialog-layer\" className=\"fixed inset-0 z-[9999] flex items-center justify-center\">\n <motion.div\n variants={scrimVariants}\n initial=\"hidden\"\n animate=\"visible\"\n exit=\"exit\"\n aria-hidden\n onClick={() => {\n if (dismiss?.enabled === false || !dismiss?.outsidePress) return;\n handler?.();\n }}\n className={classNames('absolute inset-0 glass-scrim', overlayClassName)}\n />\n\n <motion.div\n ref={panelRef}\n variants={dialogVariants}\n initial=\"hidden\"\n animate=\"visible\"\n exit=\"exit\"\n role=\"dialog\"\n aria-modal={true}\n aria-labelledby={hasTitle && !hasOwnName ? titleId : undefined}\n tabIndex={-1}\n {...rest}\n className={mergeClasses(\n classNames(\n 'relative m-4 flex max-h-[90vh] flex-col rounded-lg bg-surface text-base font-light leading-relaxed',\n 'text-foreground-muted shadow-xl outline-none',\n SIZE_CLASS[size],\n ),\n className,\n )}\n >\n <DialogDepthContext.Provider value={depth}>\n <DialogTitleContext.Provider value={titleContext}>{children}</DialogTitleContext.Provider>\n </DialogDepthContext.Provider>\n </motion.div>\n </motion.div>\n )}\n </AnimatePresence>,\n portalHost,\n );\n};\n\ninterface IDialogSectionProps {\n children: React.ReactNode;\n className?: string;\n}\n\nexport const ControlledDialogHeader: React.FC<IDialogSectionProps> = ({ children, className }) => (\n <div\n className={mergeClasses(\n 'flex shrink-0 items-center p-4 text-2xl font-semibold leading-snug text-foreground antialiased',\n className,\n )}\n >\n {children}\n </div>\n);\n\nexport const ControlledDialogBody: React.FC<IDialogSectionProps> = ({ children, className }) => (\n <div\n className={mergeClasses('relative min-h-0 flex-1 p-4 text-base font-light leading-relaxed antialiased', className)}\n >\n {children}\n </div>\n);\n\nexport const ControlledDialogFooter: React.FC<IDialogSectionProps> = ({ children, className }) => (\n <div\n className={mergeClasses('flex shrink-0 flex-wrap items-center justify-end p-4 text-foreground-muted', className)}\n >\n {children}\n </div>\n);\n\ninterface IControlledDialogTitleProps {\n children: React.ReactNode;\n className?: string;\n}\n\n/**\n * The dialog's name: a level-2 heading whose id the root wires into `aria-labelledby`, unless\n * the root received its own `aria-label` / `aria-labelledby`. A `div` with the heading role, so\n * any title content (blocks included) stays valid markup.\n */\nexport const ControlledDialogTitle: React.FC<IControlledDialogTitleProps> = ({ children, className }) => {\n const context = useContext(DialogTitleContext);\n const registerTitle = context?.registerTitle;\n\n useEffect(() => {\n if (!registerTitle) return undefined;\n registerTitle(true);\n return () => registerTitle(false);\n }, [registerTitle]);\n\n return (\n <div id={context?.titleId} role=\"heading\" aria-level={2} className={className}>\n {children}\n </div>\n );\n};\n"],"names":[],"mappings":"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"}
|