@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
|
@@ -35,7 +35,7 @@ Migrate in this order to minimize blockers:
|
|
|
35
35
|
2. **Atomic components**: `Button`, `Badge`, `Tag`, `Spinner`, `Toast`
|
|
36
36
|
3. **Form components**: `Input`, `Textarea`, `Select`, `Checkbox`, `Switch`
|
|
37
37
|
4. **Layout & navigation**: `Sidebar`, `Breadcrumb`
|
|
38
|
-
5. **Composite components**: `Dialog`, `Tabs`, `Table`, `Card`, `Alert
|
|
38
|
+
5. **Composite components**: `Dialog`, `Tabs`, `Table`, `Card`, `Alert`
|
|
39
39
|
6. **Icons** (if migrating from `lucide-react` or similar)
|
|
40
40
|
7. **Dependency cleanup**: remove v1 and all now-unused peer deps
|
|
41
41
|
|
|
@@ -270,53 +270,6 @@ import { Table } from '@chakra-ui/react/table'
|
|
|
270
270
|
|
|
271
271
|
For password fields with an eye-toggle: use `BitkitField` + Chakra v3 `InputGroup` with `endElement`.
|
|
272
272
|
|
|
273
|
-
### Filter
|
|
274
|
-
|
|
275
|
-
v1's single `data` object becomes children: each filter is a controlled sub-component with its own `value` and `onValueChange`.
|
|
276
|
-
|
|
277
|
-
```tsx
|
|
278
|
-
// v1
|
|
279
|
-
<Filter data={{ project: {...}, date_range: { type: 'dateRange' }, branch: { type: 'tag', isPatternEnabled: true } }} state={state} onChange={setState} showAdd showSearch />
|
|
280
|
-
|
|
281
|
-
// v2
|
|
282
|
-
<BitkitFilter>
|
|
283
|
-
<BitkitFilter.Select label="Project" pluralLabel="Projects" items={projects} value={project} onValueChange={setProject} />
|
|
284
|
-
<BitkitFilter.DateRange value={dates} onValueChange={setDates} />
|
|
285
|
-
<BitkitFilter.Add>
|
|
286
|
-
<BitkitFilter.Select label="Branch" pluralLabel="Branches" items={branches} multiple pattern value={branch} onValueChange={setBranch} />
|
|
287
|
-
</BitkitFilter.Add>
|
|
288
|
-
<BitkitFilter.Search value={search} onValueChange={setSearch} />
|
|
289
|
-
</BitkitFilter>
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
| v1 | v2 |
|
|
293
|
-
|----|----|
|
|
294
|
-
| `state` / `onChange` | per-filter `value` / `onValueChange` |
|
|
295
|
-
| `defaultState` | per-filter `resetValue`; root `onClear` to reset everything in one write |
|
|
296
|
-
| `type: 'select'`, `isMultiple` | `BitkitFilter.Select`, `multiple` |
|
|
297
|
-
| `type: 'switch'`, `allOptionLabel` | `BitkitFilter.Switch`, `allLabel` |
|
|
298
|
-
| `type: 'dateRange'`, `selectable`, `dayTooltip` | `BitkitFilter.DateRange`, `min` / `max`, `dayTooltip` (now `(date: Date) => string \| undefined`; v1 passed `{ date, selectable }` with a luxon `DateTime`) |
|
|
299
|
-
| `type: 'tag'`, `showAdd` | `BitkitFilter.Select` inside `BitkitFilter.Add` |
|
|
300
|
-
| `showSearch`, `searchTooltip`, `state.search = [text]` | `BitkitFilter.Search`, `tooltip`, `value: string` |
|
|
301
|
-
| `options`, `optionsMap`, `iconsMap`, `iconColorsMap` | `items: { value, label, icon?, iconColor? }[]` |
|
|
302
|
-
| `categoryName`, `categoryNamePlural`, `unfilteredLabel` | `label`, `pluralLabel` (required), `emptyLabel` |
|
|
303
|
-
| `onAsyncSearch` | `onSearchChange` + `items` + `loading` |
|
|
304
|
-
| `isInitialLoading`, `loadingText` | `loading`, `loadingLabel` |
|
|
305
|
-
| `isPatternEnabled` | `pattern` |
|
|
306
|
-
| `hasNotFilteredOption: false` | `required`, with the default as `resetValue` |
|
|
307
|
-
| `preserveOptionOrder` | `preserveOrder` |
|
|
308
|
-
| `dependsOn`, `filtersDependOn` | `disabledReason`; clear the dependent filter in the parent's `onValueChange` |
|
|
309
|
-
| `isLoading` | `state="loading"` |
|
|
310
|
-
| `isMobile` | `device` |
|
|
311
|
-
| `showFilterIcon`, `showClearFilters` | `showIcon`, `showClear` |
|
|
312
|
-
|
|
313
|
-
Things that behave differently:
|
|
314
|
-
- **Mobile is automatic** below the tablet breakpoint. v1 was opt-in; pass `device="desktop"` where a page relied on that.
|
|
315
|
-
- **`onSearchChange` returns nothing.** Fetch yourself and pass the results as `items`, with `loading` meanwhile. It is debounced (300ms), but dropping out-of-order responses is up to you.
|
|
316
|
-
- **Values are typed per filter**: `string | null` for a switch (`null` = "All"), `[start, end] | null` of `Date`s for a date range, `string[]` for a select, a plain `string` for the search.
|
|
317
|
-
- **`iconColor` takes an `icon/*` token**, e.g. `icon/positive` instead of `status/success/icon`.
|
|
318
|
-
- **"Clear filters" only resets the filters that changed**, each through its own `onValueChange`. A URL-backed page should pass `onClear`, or it gets one history entry per filter.
|
|
319
|
-
|
|
320
273
|
### Cards
|
|
321
274
|
|
|
322
275
|
| v1 | v2 |
|
package/package.json
CHANGED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { SystemStyleObject } from '@chakra-ui/react/styled-system';
|
|
2
|
-
import { FilterValue } from './BitkitFilter.utils';
|
|
3
|
-
export interface FilterStatus {
|
|
4
|
-
/** Holds a selection — counted on the mobile "Filters" button. */
|
|
5
|
-
isActive: boolean;
|
|
6
|
-
/** Differs from its `resetValue` — shows "Clear filters". */
|
|
7
|
-
isDirty: boolean;
|
|
8
|
-
}
|
|
9
|
-
interface FilterContext {
|
|
10
|
-
/** The page is loading: the filters can't be opened or cleared. */
|
|
11
|
-
isDisabled: boolean;
|
|
12
|
-
/** Below the tablet breakpoint the filters render as fields in a full-screen dialog. */
|
|
13
|
-
isMobile: boolean;
|
|
14
|
-
register: (id: string, reset: () => void) => void;
|
|
15
|
-
setStatus: (id: string, status: FilterStatus) => void;
|
|
16
|
-
styles: Record<string, SystemStyleObject>;
|
|
17
|
-
unregister: (id: string) => void;
|
|
18
|
-
}
|
|
19
|
-
export declare const FilterContextProvider: import('react').Provider<FilterContext>, useFilterContext: () => FilterContext;
|
|
20
|
-
/** What a filter inside `BitkitFilter.Add` tells it, to list itself in the "Add filter" menu. */
|
|
21
|
-
export interface FilterAddEntry {
|
|
22
|
-
disabledReason?: string;
|
|
23
|
-
/** Has options to pick, or a search to find them. */
|
|
24
|
-
hasOptions: boolean;
|
|
25
|
-
/** Holds no value, so it is offered in the menu rather than shown as a chip. */
|
|
26
|
-
isEmpty: boolean;
|
|
27
|
-
label: string;
|
|
28
|
-
}
|
|
29
|
-
interface FilterAddContext {
|
|
30
|
-
/** The filter picked from the menu, whose panel is open. */
|
|
31
|
-
addingId?: string;
|
|
32
|
-
/** Moves focus to the "Add filter" button once it is enabled, e.g. after a chip is removed. */
|
|
33
|
-
focusTrigger: () => void;
|
|
34
|
-
/** The "Add filter" button: the panels anchor to it and hand focus back to it. */
|
|
35
|
-
getTrigger: () => HTMLElement | null;
|
|
36
|
-
register: (id: string, entry: FilterAddEntry) => void;
|
|
37
|
-
stopAdding: () => void;
|
|
38
|
-
unregister: (id: string) => void;
|
|
39
|
-
}
|
|
40
|
-
/** Set by `BitkitFilter.Add` around its filters: they render as removable chips, only while they hold a value. */
|
|
41
|
-
export declare const FilterAddContextProvider: import('react').Provider<FilterAddContext | undefined>, useFilterAddContext: () => FilterAddContext | undefined;
|
|
42
|
-
/**
|
|
43
|
-
* Lists a filter in its `BitkitFilter.Add` menu, wherever it sits in the tree, so a filter wrapped in
|
|
44
|
-
* a consumer component is listed too. Returns nothing outside `Add`.
|
|
45
|
-
*/
|
|
46
|
-
export declare const useFilterAddItem: ({ disabledReason, hasOptions, isEmpty, label }: FilterAddEntry) => {
|
|
47
|
-
isAdding: boolean;
|
|
48
|
-
/** The filter picked from the menu, whose panel is open. */
|
|
49
|
-
addingId?: string;
|
|
50
|
-
/** Moves focus to the "Add filter" button once it is enabled, e.g. after a chip is removed. */
|
|
51
|
-
focusTrigger: () => void;
|
|
52
|
-
/** The "Add filter" button: the panels anchor to it and hand focus back to it. */
|
|
53
|
-
getTrigger: () => HTMLElement | null;
|
|
54
|
-
register: (id: string, entry: FilterAddEntry) => void;
|
|
55
|
-
stopAdding: () => void;
|
|
56
|
-
unregister: (id: string) => void;
|
|
57
|
-
} | undefined;
|
|
58
|
-
/**
|
|
59
|
-
* Registers a filter with the root, which shows "Clear filters" while any filter differs from its
|
|
60
|
-
* `resetValue` and hands each one its `resetValue` back when clicked, and counts the active ones.
|
|
61
|
-
*/
|
|
62
|
-
export declare const useFilterRegistration: <T extends FilterValue>(value: T, resetValue: T, onValueChange: (value: T) => void,
|
|
63
|
-
/** Off for the search, which sits outside the mobile dialog the count describes. */
|
|
64
|
-
isCounted?: boolean) => void;
|
|
65
|
-
export {};
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { isEmptyFilterValue, isEqualFilterValue } from "./BitkitFilter.utils.js";
|
|
2
|
-
import { useEffect, useId, useRef } from "react";
|
|
3
|
-
import { createContext as createContext$1 } from "@chakra-ui/react";
|
|
4
|
-
//#region lib/components/BitkitFilter/BitkitFilter.context.ts
|
|
5
|
-
var [FilterContextProvider, useFilterContext] = createContext$1({
|
|
6
|
-
hookName: "useFilterContext",
|
|
7
|
-
name: "FilterContext",
|
|
8
|
-
providerName: "<BitkitFilter />"
|
|
9
|
-
});
|
|
10
|
-
/** Set by `BitkitFilter.Add` around its filters: they render as removable chips, only while they hold a value. */
|
|
11
|
-
var [FilterAddContextProvider, useFilterAddContext] = createContext$1({
|
|
12
|
-
name: "FilterAddContext",
|
|
13
|
-
strict: false
|
|
14
|
-
});
|
|
15
|
-
/**
|
|
16
|
-
* Lists a filter in its `BitkitFilter.Add` menu, wherever it sits in the tree, so a filter wrapped in
|
|
17
|
-
* a consumer component is listed too. Returns nothing outside `Add`.
|
|
18
|
-
*/
|
|
19
|
-
var useFilterAddItem = ({ disabledReason, hasOptions, isEmpty, label }) => {
|
|
20
|
-
const add = useFilterAddContext();
|
|
21
|
-
const id = useId();
|
|
22
|
-
const register = add?.register;
|
|
23
|
-
const unregister = add?.unregister;
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
register?.(id, {
|
|
26
|
-
disabledReason,
|
|
27
|
-
hasOptions,
|
|
28
|
-
isEmpty,
|
|
29
|
-
label
|
|
30
|
-
});
|
|
31
|
-
}, [
|
|
32
|
-
disabledReason,
|
|
33
|
-
hasOptions,
|
|
34
|
-
id,
|
|
35
|
-
isEmpty,
|
|
36
|
-
label,
|
|
37
|
-
register
|
|
38
|
-
]);
|
|
39
|
-
useEffect(() => () => unregister?.(id), [id, unregister]);
|
|
40
|
-
if (!add) return void 0;
|
|
41
|
-
return {
|
|
42
|
-
...add,
|
|
43
|
-
isAdding: add.addingId === id
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
/**
|
|
47
|
-
* Registers a filter with the root, which shows "Clear filters" while any filter differs from its
|
|
48
|
-
* `resetValue` and hands each one its `resetValue` back when clicked, and counts the active ones.
|
|
49
|
-
*/
|
|
50
|
-
var useFilterRegistration = (value, resetValue, onValueChange, isCounted = true) => {
|
|
51
|
-
const { register, setStatus, unregister } = useFilterContext();
|
|
52
|
-
const id = useId();
|
|
53
|
-
const resetRef = useRef(() => onValueChange(resetValue));
|
|
54
|
-
useEffect(() => {
|
|
55
|
-
resetRef.current = () => onValueChange(resetValue);
|
|
56
|
-
});
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
register(id, () => resetRef.current());
|
|
59
|
-
return () => unregister(id);
|
|
60
|
-
}, [
|
|
61
|
-
id,
|
|
62
|
-
register,
|
|
63
|
-
unregister
|
|
64
|
-
]);
|
|
65
|
-
const isDirty = !isEqualFilterValue(value, resetValue);
|
|
66
|
-
const isActive = isCounted && !isEmptyFilterValue(value);
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
setStatus(id, {
|
|
69
|
-
isActive,
|
|
70
|
-
isDirty
|
|
71
|
-
});
|
|
72
|
-
}, [
|
|
73
|
-
id,
|
|
74
|
-
isActive,
|
|
75
|
-
isDirty,
|
|
76
|
-
setStatus
|
|
77
|
-
]);
|
|
78
|
-
};
|
|
79
|
-
//#endregion
|
|
80
|
-
export { FilterAddContextProvider, FilterContextProvider, useFilterAddContext, useFilterAddItem, useFilterContext, useFilterRegistration };
|
|
81
|
-
|
|
82
|
-
//# sourceMappingURL=BitkitFilter.context.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitFilter.context.js","names":["createContext"],"sources":["../../../lib/components/BitkitFilter/BitkitFilter.context.ts"],"sourcesContent":["// eslint-disable-next-line no-restricted-imports\nimport { createContext } from '@chakra-ui/react';\nimport { type SystemStyleObject } from '@chakra-ui/react/styled-system';\nimport { useEffect, useId, useRef } from 'react';\n\nimport { type FilterValue, isEmptyFilterValue, isEqualFilterValue } from './BitkitFilter.utils';\n\nexport interface FilterStatus {\n /** Holds a selection — counted on the mobile \"Filters\" button. */\n isActive: boolean;\n /** Differs from its `resetValue` — shows \"Clear filters\". */\n isDirty: boolean;\n}\n\ninterface FilterContext {\n /** The page is loading: the filters can't be opened or cleared. */\n isDisabled: boolean;\n /** Below the tablet breakpoint the filters render as fields in a full-screen dialog. */\n isMobile: boolean;\n register: (id: string, reset: () => void) => void;\n setStatus: (id: string, status: FilterStatus) => void;\n styles: Record<string, SystemStyleObject>;\n unregister: (id: string) => void;\n}\n\nexport const [FilterContextProvider, useFilterContext] = createContext<FilterContext>({\n hookName: 'useFilterContext',\n name: 'FilterContext',\n providerName: '<BitkitFilter />',\n});\n\n/** What a filter inside `BitkitFilter.Add` tells it, to list itself in the \"Add filter\" menu. */\nexport interface FilterAddEntry {\n disabledReason?: string;\n /** Has options to pick, or a search to find them. */\n hasOptions: boolean;\n /** Holds no value, so it is offered in the menu rather than shown as a chip. */\n isEmpty: boolean;\n label: string;\n}\n\ninterface FilterAddContext {\n /** The filter picked from the menu, whose panel is open. */\n addingId?: string;\n /** Moves focus to the \"Add filter\" button once it is enabled, e.g. after a chip is removed. */\n focusTrigger: () => void;\n /** The \"Add filter\" button: the panels anchor to it and hand focus back to it. */\n getTrigger: () => HTMLElement | null;\n register: (id: string, entry: FilterAddEntry) => void;\n stopAdding: () => void;\n unregister: (id: string) => void;\n}\n\n/** Set by `BitkitFilter.Add` around its filters: they render as removable chips, only while they hold a value. */\nexport const [FilterAddContextProvider, useFilterAddContext] = createContext<FilterAddContext | undefined>({\n name: 'FilterAddContext',\n strict: false,\n});\n\n/**\n * Lists a filter in its `BitkitFilter.Add` menu, wherever it sits in the tree, so a filter wrapped in\n * a consumer component is listed too. Returns nothing outside `Add`.\n */\nexport const useFilterAddItem = ({ disabledReason, hasOptions, isEmpty, label }: FilterAddEntry) => {\n const add = useFilterAddContext();\n const id = useId();\n const register = add?.register;\n const unregister = add?.unregister;\n\n useEffect(() => {\n register?.(id, { disabledReason, hasOptions, isEmpty, label });\n }, [disabledReason, hasOptions, id, isEmpty, label, register]);\n\n useEffect(() => () => unregister?.(id), [id, unregister]);\n\n if (!add) return undefined;\n return { ...add, isAdding: add.addingId === id };\n};\n\n/**\n * Registers a filter with the root, which shows \"Clear filters\" while any filter differs from its\n * `resetValue` and hands each one its `resetValue` back when clicked, and counts the active ones.\n */\nexport const useFilterRegistration = <T extends FilterValue>(\n value: T,\n resetValue: T,\n onValueChange: (value: T) => void,\n /** Off for the search, which sits outside the mobile dialog the count describes. */\n isCounted = true,\n) => {\n const { register, setStatus, unregister } = useFilterContext();\n const id = useId();\n const resetRef = useRef(() => onValueChange(resetValue));\n useEffect(() => {\n resetRef.current = () => onValueChange(resetValue);\n });\n\n useEffect(() => {\n register(id, () => resetRef.current());\n return () => unregister(id);\n }, [id, register, unregister]);\n\n const isDirty = !isEqualFilterValue(value, resetValue);\n const isActive = isCounted && !isEmptyFilterValue(value);\n useEffect(() => {\n setStatus(id, { isActive, isDirty });\n }, [id, isActive, isDirty, setStatus]);\n};\n"],"mappings":";;;;AAyBA,IAAa,CAAC,uBAAuB,oBAAoBA,gBAA6B;CACpF,UAAU;CACV,MAAM;CACN,cAAc;AAChB,CAAC;;AAyBD,IAAa,CAAC,0BAA0B,uBAAuBA,gBAA4C;CACzG,MAAM;CACN,QAAQ;AACV,CAAC;;;;;AAMD,IAAa,oBAAoB,EAAE,gBAAgB,YAAY,SAAS,YAA4B;CAClG,MAAM,MAAM,oBAAoB;CAChC,MAAM,KAAK,MAAM;CACjB,MAAM,WAAW,KAAK;CACtB,MAAM,aAAa,KAAK;CAExB,gBAAgB;EACd,WAAW,IAAI;GAAE;GAAgB;GAAY;GAAS;EAAM,CAAC;CAC/D,GAAG;EAAC;EAAgB;EAAY;EAAI;EAAS;EAAO;CAAQ,CAAC;CAE7D,sBAAsB,aAAa,EAAE,GAAG,CAAC,IAAI,UAAU,CAAC;CAExD,IAAI,CAAC,KAAK,OAAO,KAAA;CACjB,OAAO;EAAE,GAAG;EAAK,UAAU,IAAI,aAAa;CAAG;AACjD;;;;;AAMA,IAAa,yBACX,OACA,YACA,eAEA,YAAY,SACT;CACH,MAAM,EAAE,UAAU,WAAW,eAAe,iBAAiB;CAC7D,MAAM,KAAK,MAAM;CACjB,MAAM,WAAW,aAAa,cAAc,UAAU,CAAC;CACvD,gBAAgB;EACd,SAAS,gBAAgB,cAAc,UAAU;CACnD,CAAC;CAED,gBAAgB;EACd,SAAS,UAAU,SAAS,QAAQ,CAAC;EACrC,aAAa,WAAW,EAAE;CAC5B,GAAG;EAAC;EAAI;EAAU;CAAU,CAAC;CAE7B,MAAM,UAAU,CAAC,mBAAmB,OAAO,UAAU;CACrD,MAAM,WAAW,aAAa,CAAC,mBAAmB,KAAK;CACvD,gBAAgB;EACd,UAAU,IAAI;GAAE;GAAU;EAAQ,CAAC;CACrC,GAAG;EAAC;EAAI;EAAU;EAAS;CAAS,CAAC;AACvC"}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { BoxProps } from '@chakra-ui/react/box';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
3
|
-
export interface BitkitFilterProps extends Omit<BoxProps, 'children'> {
|
|
4
|
-
/**
|
|
5
|
-
* The filters: `BitkitFilter.Switch`, `.Select`, `.DateRange`, `.Add` and `.Search`, in the
|
|
6
|
-
* order they appear. `.Search` always sits at the end of the bar.
|
|
7
|
-
*/
|
|
8
|
-
children: ReactNode;
|
|
9
|
-
/**
|
|
10
|
-
* Where a capped content block sits: `center` over a centred page column, `start` over one that
|
|
11
|
-
* starts against a sidebar. Pass the same alignment as the page header and content below it.
|
|
12
|
-
* Only applies with a `contentWidth` in rems. Desktop only.
|
|
13
|
-
*/
|
|
14
|
-
contentAlign?: 'center' | 'start';
|
|
15
|
-
/**
|
|
16
|
-
* Lines the bar up with the page content below it, for a bar under a page header: it gains a
|
|
17
|
-
* bottom border and the page's inline padding. Pass the content column's `max-width` in rems to
|
|
18
|
-
* cap the bar's content with it (padded 32 on both sides), or `'full'` for a full-width column
|
|
19
|
-
* (24 at the start, like the default `BitkitPageHeader`, and 32 at the end).
|
|
20
|
-
*
|
|
21
|
-
* Without it the bar has the tighter inset of a filter above a table. Desktop only.
|
|
22
|
-
*/
|
|
23
|
-
contentWidth?: `${number}rem` | 'full';
|
|
24
|
-
/**
|
|
25
|
-
* Forces the desktop bar or the mobile "Filters" dialog. Defaults to the viewport: mobile below the
|
|
26
|
-
* tablet breakpoint. The mobile dialog is made for narrow screens; from tablet up it is a 320px drawer.
|
|
27
|
-
*/
|
|
28
|
-
device?: 'desktop' | 'mobile';
|
|
29
|
-
/**
|
|
30
|
-
* Called once on "Clear filters" in place of handing each changed filter its `resetValue` — for a
|
|
31
|
-
* page that resets them all in one write, e.g. one URL update. The `resetValue`s still decide when
|
|
32
|
-
* the button shows.
|
|
33
|
-
*/
|
|
34
|
-
onClear?: () => void;
|
|
35
|
-
/**
|
|
36
|
-
* Shows "Clear filters" while any filter differs from its `resetValue`. It hands each of those
|
|
37
|
-
* filters its `resetValue` back through its own `onValueChange`, or calls `onClear`.
|
|
38
|
-
*/
|
|
39
|
-
showClear?: boolean;
|
|
40
|
-
/** Shows the filter icon at the start of the bar. Desktop only. */
|
|
41
|
-
showIcon?: boolean;
|
|
42
|
-
/** `loading`: while the page's data loads, the filters can't be opened, removed or cleared. The search stays usable. */
|
|
43
|
-
state?: 'loading';
|
|
44
|
-
}
|
|
45
|
-
declare const _default: import('react').ForwardRefExoticComponent<BitkitFilterProps & import('react').RefAttributes<HTMLDivElement>> & {
|
|
46
|
-
Add: {
|
|
47
|
-
({ children }: import('./BitkitFilterAdd').BitkitFilterAddProps): import('react').JSX.Element;
|
|
48
|
-
displayName: string;
|
|
49
|
-
};
|
|
50
|
-
DateRange: import('react').ForwardRefExoticComponent<import('./BitkitFilterDateRange').BitkitFilterDateRangeProps & import('react').RefAttributes<HTMLButtonElement>>;
|
|
51
|
-
Search: import('react').ForwardRefExoticComponent<import('./BitkitFilterSearch').BitkitFilterSearchProps & import('react').RefAttributes<HTMLInputElement>>;
|
|
52
|
-
Select: import('react').ForwardRefExoticComponent<import('./BitkitFilterSelect').BitkitFilterSelectProps & import('react').RefAttributes<HTMLButtonElement>>;
|
|
53
|
-
Switch: import('react').ForwardRefExoticComponent<import('./BitkitFilterSwitch').BitkitFilterSwitchProps & import('react').RefAttributes<HTMLElement>>;
|
|
54
|
-
};
|
|
55
|
-
export default _default;
|
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
import { withSubComponents } from "../../utilities/withSubComponents.js";
|
|
2
|
-
import IconCross from "../../icons/IconCross.js";
|
|
3
|
-
import IconFilter from "../../icons/IconFilter.js";
|
|
4
|
-
import { usePortalContext } from "../../utilities/BitkitPortalContext.js";
|
|
5
|
-
import BitkitCloseButton from "../BitkitCloseButton/BitkitCloseButton.js";
|
|
6
|
-
import BitkitBadge from "../BitkitBadge/BitkitBadge.js";
|
|
7
|
-
import { flattenChildren } from "../../utilities/flattenChildren.js";
|
|
8
|
-
import BitkitButton from "../BitkitButton/BitkitButton.js";
|
|
9
|
-
import useResponsive from "../../hooks/useResponsive.js";
|
|
10
|
-
import { FilterContextProvider } from "./BitkitFilter.context.js";
|
|
11
|
-
import BitkitFilterAdd from "./BitkitFilterAdd.js";
|
|
12
|
-
import BitkitFilterDateRange from "./BitkitFilterDateRange.js";
|
|
13
|
-
import BitkitFilterSearch from "./BitkitFilterSearch.js";
|
|
14
|
-
import BitkitFilterSelect from "./BitkitFilterSelect.js";
|
|
15
|
-
import BitkitFilterSwitch from "./BitkitFilterSwitch.js";
|
|
16
|
-
import { Box } from "@chakra-ui/react/box";
|
|
17
|
-
import { chakra, useSlotRecipe } from "@chakra-ui/react/styled-system";
|
|
18
|
-
import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
19
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
20
|
-
import { Portal } from "@chakra-ui/react/portal";
|
|
21
|
-
import { Drawer } from "@chakra-ui/react/drawer";
|
|
22
|
-
//#region lib/components/BitkitFilter/BitkitFilter.tsx
|
|
23
|
-
var toggle = (set, id, isIn) => {
|
|
24
|
-
if (set.has(id) === isIn) return set;
|
|
25
|
-
const next = new Set(set);
|
|
26
|
-
if (isIn) next.add(id);
|
|
27
|
-
else next.delete(id);
|
|
28
|
-
return next;
|
|
29
|
-
};
|
|
30
|
-
/**
|
|
31
|
-
* A bar of filters above a list. Each filter is a controlled sub-component with its own `value` and
|
|
32
|
-
* `onValueChange`; the bar lays them out and offers "Clear filters" across all of them.
|
|
33
|
-
*
|
|
34
|
-
* Below the tablet breakpoint the bar collapses into a "Filters" button opening a full-screen dialog,
|
|
35
|
-
* where every filter is a field opening its own view. A filter still applies on its view's "Apply".
|
|
36
|
-
*/
|
|
37
|
-
var BitkitFilter = forwardRef((props, ref) => {
|
|
38
|
-
const { children, contentAlign, contentWidth, css, device, onClear, showClear = true, showIcon = true, state, ...rest } = props;
|
|
39
|
-
const { isMobile: isMobileViewport } = useResponsive();
|
|
40
|
-
const isMobile = device ? device === "mobile" : isMobileViewport;
|
|
41
|
-
const isDisabled = state === "loading";
|
|
42
|
-
const { disablePortal } = usePortalContext();
|
|
43
|
-
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
44
|
-
const recipe = useSlotRecipe({ key: "filter" });
|
|
45
|
-
let pageLayout;
|
|
46
|
-
if (contentWidth) pageLayout = contentWidth === "full" ? "full" : "capped";
|
|
47
|
-
const styles = recipe({
|
|
48
|
-
contentAlign: pageLayout === "capped" ? contentAlign : void 0,
|
|
49
|
-
pageLayout
|
|
50
|
-
});
|
|
51
|
-
const resets = useRef(/* @__PURE__ */ new Map());
|
|
52
|
-
const [dirtyIds, setDirtyIds] = useState(/* @__PURE__ */ new Set());
|
|
53
|
-
const [activeIds, setActiveIds] = useState(/* @__PURE__ */ new Set());
|
|
54
|
-
const register = useCallback((id, reset) => {
|
|
55
|
-
resets.current.set(id, reset);
|
|
56
|
-
}, []);
|
|
57
|
-
const setStatus = useCallback((id, { isActive, isDirty }) => {
|
|
58
|
-
setDirtyIds((prev) => toggle(prev, id, isDirty));
|
|
59
|
-
setActiveIds((prev) => toggle(prev, id, isActive));
|
|
60
|
-
}, []);
|
|
61
|
-
const unregister = useCallback((id) => {
|
|
62
|
-
resets.current.delete(id);
|
|
63
|
-
setStatus(id, {
|
|
64
|
-
isActive: false,
|
|
65
|
-
isDirty: false
|
|
66
|
-
});
|
|
67
|
-
}, [setStatus]);
|
|
68
|
-
const context = useMemo(() => ({
|
|
69
|
-
isDisabled,
|
|
70
|
-
isMobile,
|
|
71
|
-
register,
|
|
72
|
-
setStatus,
|
|
73
|
-
styles,
|
|
74
|
-
unregister
|
|
75
|
-
}), [
|
|
76
|
-
isDisabled,
|
|
77
|
-
isMobile,
|
|
78
|
-
register,
|
|
79
|
-
setStatus,
|
|
80
|
-
styles,
|
|
81
|
-
unregister
|
|
82
|
-
]);
|
|
83
|
-
const filters = [];
|
|
84
|
-
const searches = [];
|
|
85
|
-
flattenChildren(children).forEach((child) => {
|
|
86
|
-
if (child.type === BitkitFilterSearch) searches.push(child);
|
|
87
|
-
else filters.push(child);
|
|
88
|
-
});
|
|
89
|
-
const isDirty = dirtyIds.size > 0;
|
|
90
|
-
const activeCount = activeIds.size;
|
|
91
|
-
const focusTarget = useRef(null);
|
|
92
|
-
const isFocusPending = useRef(false);
|
|
93
|
-
const clearAll = () => {
|
|
94
|
-
if (isDisabled) return;
|
|
95
|
-
isFocusPending.current = true;
|
|
96
|
-
document.addEventListener("focusin", () => {
|
|
97
|
-
isFocusPending.current = false;
|
|
98
|
-
}, { once: true });
|
|
99
|
-
if (onClear) onClear();
|
|
100
|
-
else dirtyIds.forEach((id) => resets.current.get(id)?.());
|
|
101
|
-
};
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
if (!isFocusPending.current || isDirty) return;
|
|
104
|
-
isFocusPending.current = false;
|
|
105
|
-
const active = document.activeElement;
|
|
106
|
-
if (active && active !== document.body && !active.matches("[data-filter-clear]")) return;
|
|
107
|
-
focusTarget.current?.querySelector("button:not(:disabled), input:not(:disabled)")?.focus();
|
|
108
|
-
}, [isDirty]);
|
|
109
|
-
const disabledProps = {
|
|
110
|
-
"aria-disabled": isDisabled || void 0,
|
|
111
|
-
"data-disabled": isDisabled ? "" : void 0
|
|
112
|
-
};
|
|
113
|
-
const clearProps = {
|
|
114
|
-
...disabledProps,
|
|
115
|
-
"data-filter-clear": "",
|
|
116
|
-
onClick: clearAll
|
|
117
|
-
};
|
|
118
|
-
if (isMobile) return /* @__PURE__ */ jsx(FilterContextProvider, {
|
|
119
|
-
value: context,
|
|
120
|
-
children: /* @__PURE__ */ jsxs(Box, {
|
|
121
|
-
css,
|
|
122
|
-
ref,
|
|
123
|
-
...rest,
|
|
124
|
-
children: [
|
|
125
|
-
/* @__PURE__ */ jsxs(Box, {
|
|
126
|
-
css: styles.mobileBar,
|
|
127
|
-
ref: isDialogOpen ? void 0 : focusTarget,
|
|
128
|
-
children: [/* @__PURE__ */ jsxs(chakra.button, {
|
|
129
|
-
"aria-expanded": isDialogOpen,
|
|
130
|
-
"aria-haspopup": "dialog",
|
|
131
|
-
"aria-label": `Filters, ${activeCount} applied`,
|
|
132
|
-
...disabledProps,
|
|
133
|
-
css: styles.mobileTrigger,
|
|
134
|
-
onClick: () => !isDisabled && setIsDialogOpen(true),
|
|
135
|
-
type: "button",
|
|
136
|
-
children: [
|
|
137
|
-
/* @__PURE__ */ jsx(IconFilter, { size: "16" }),
|
|
138
|
-
"Filters",
|
|
139
|
-
/* @__PURE__ */ jsx(BitkitBadge, {
|
|
140
|
-
colorVariant: activeCount ? "purple" : "neutral",
|
|
141
|
-
children: activeCount
|
|
142
|
-
})
|
|
143
|
-
]
|
|
144
|
-
}), showClear && isDirty && /* @__PURE__ */ jsx(BitkitButton, {
|
|
145
|
-
...clearProps,
|
|
146
|
-
icon: IconCross,
|
|
147
|
-
size: "sm",
|
|
148
|
-
variant: "tertiary",
|
|
149
|
-
children: "Clear filters"
|
|
150
|
-
})]
|
|
151
|
-
}),
|
|
152
|
-
searches,
|
|
153
|
-
/* @__PURE__ */ jsx(Drawer.Root, {
|
|
154
|
-
lazyMount: false,
|
|
155
|
-
onOpenChange: ({ open }) => setIsDialogOpen(open),
|
|
156
|
-
open: isDialogOpen,
|
|
157
|
-
unmountOnExit: false,
|
|
158
|
-
children: /* @__PURE__ */ jsx(Portal, {
|
|
159
|
-
disabled: disablePortal,
|
|
160
|
-
children: /* @__PURE__ */ jsx(Drawer.Positioner, { children: /* @__PURE__ */ jsxs(Drawer.Content, { children: [
|
|
161
|
-
/* @__PURE__ */ jsxs(Box, {
|
|
162
|
-
css: styles.mobileHeader,
|
|
163
|
-
children: [/* @__PURE__ */ jsx(Drawer.Title, {
|
|
164
|
-
css: styles.mobileTitle,
|
|
165
|
-
unstyled: true,
|
|
166
|
-
children: "Filters"
|
|
167
|
-
}), /* @__PURE__ */ jsx(Drawer.CloseTrigger, {
|
|
168
|
-
asChild: true,
|
|
169
|
-
unstyled: true,
|
|
170
|
-
children: /* @__PURE__ */ jsx(BitkitCloseButton, { size: "sm" })
|
|
171
|
-
})]
|
|
172
|
-
}),
|
|
173
|
-
/* @__PURE__ */ jsx(Drawer.Body, {
|
|
174
|
-
css: styles.mobileBody,
|
|
175
|
-
ref: isDialogOpen ? focusTarget : void 0,
|
|
176
|
-
children: filters
|
|
177
|
-
}),
|
|
178
|
-
/* @__PURE__ */ jsx(Drawer.Footer, { children: /* @__PURE__ */ jsxs(Box, {
|
|
179
|
-
css: styles.mobileFooter,
|
|
180
|
-
children: [showClear && /* @__PURE__ */ jsx(BitkitButton, {
|
|
181
|
-
...clearProps,
|
|
182
|
-
size: "md",
|
|
183
|
-
state: isDirty ? void 0 : "disabled",
|
|
184
|
-
variant: "secondary",
|
|
185
|
-
children: "Clear filters"
|
|
186
|
-
}), /* @__PURE__ */ jsx(BitkitButton, {
|
|
187
|
-
onClick: () => setIsDialogOpen(false),
|
|
188
|
-
size: "md",
|
|
189
|
-
children: "Show results"
|
|
190
|
-
})]
|
|
191
|
-
}) })
|
|
192
|
-
] }) })
|
|
193
|
-
})
|
|
194
|
-
})
|
|
195
|
-
]
|
|
196
|
-
})
|
|
197
|
-
});
|
|
198
|
-
return /* @__PURE__ */ jsx(FilterContextProvider, {
|
|
199
|
-
value: context,
|
|
200
|
-
children: /* @__PURE__ */ jsx(Box, {
|
|
201
|
-
css: [styles.root, css],
|
|
202
|
-
ref,
|
|
203
|
-
...rest,
|
|
204
|
-
children: /* @__PURE__ */ jsxs(Box, {
|
|
205
|
-
css: styles.content,
|
|
206
|
-
maxWidth: contentWidth === "full" ? void 0 : contentWidth,
|
|
207
|
-
ref: focusTarget,
|
|
208
|
-
children: [/* @__PURE__ */ jsxs(Box, {
|
|
209
|
-
css: styles.filters,
|
|
210
|
-
children: [
|
|
211
|
-
showIcon && /* @__PURE__ */ jsx(Box, {
|
|
212
|
-
css: styles.icon,
|
|
213
|
-
children: /* @__PURE__ */ jsx(IconFilter, {})
|
|
214
|
-
}),
|
|
215
|
-
/* @__PURE__ */ jsx(Box, {
|
|
216
|
-
css: styles.items,
|
|
217
|
-
children: filters
|
|
218
|
-
}),
|
|
219
|
-
showClear && isDirty && /* @__PURE__ */ jsx(BitkitButton, {
|
|
220
|
-
...clearProps,
|
|
221
|
-
flexShrink: "0",
|
|
222
|
-
icon: IconCross,
|
|
223
|
-
size: "sm",
|
|
224
|
-
variant: "tertiary",
|
|
225
|
-
children: "Clear filters"
|
|
226
|
-
})
|
|
227
|
-
]
|
|
228
|
-
}), searches.length > 0 && /* @__PURE__ */ jsx(Box, {
|
|
229
|
-
css: styles.searchBlock,
|
|
230
|
-
children: searches
|
|
231
|
-
})]
|
|
232
|
-
})
|
|
233
|
-
})
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
BitkitFilter.displayName = "BitkitFilter";
|
|
237
|
-
var BitkitFilter_default = withSubComponents(BitkitFilter, {
|
|
238
|
-
Add: BitkitFilterAdd,
|
|
239
|
-
DateRange: BitkitFilterDateRange,
|
|
240
|
-
Search: BitkitFilterSearch,
|
|
241
|
-
Select: BitkitFilterSelect,
|
|
242
|
-
Switch: BitkitFilterSwitch
|
|
243
|
-
});
|
|
244
|
-
//#endregion
|
|
245
|
-
export { BitkitFilter_default as default };
|
|
246
|
-
|
|
247
|
-
//# sourceMappingURL=BitkitFilter.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitFilter.js","names":[],"sources":["../../../lib/components/BitkitFilter/BitkitFilter.tsx"],"sourcesContent":["import { Box, type BoxProps } from '@chakra-ui/react/box';\nimport { Drawer } from '@chakra-ui/react/drawer';\nimport { Portal } from '@chakra-ui/react/portal';\n// `chakra.button`: the mobile \"Filters\" button carries a count badge, which BitkitButton's string-only label can't.\nimport { chakra, useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { forwardRef, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport useResponsive from '../../hooks/useResponsive';\nimport { IconCross, IconFilter } from '../../icons';\nimport { usePortalContext } from '../../utilities/BitkitPortalContext';\nimport { flattenChildren } from '../../utilities/flattenChildren';\nimport { withSubComponents } from '../../utilities/withSubComponents';\nimport BitkitBadge from '../BitkitBadge/BitkitBadge';\nimport BitkitButton from '../BitkitButton/BitkitButton';\nimport BitkitCloseButton from '../BitkitCloseButton/BitkitCloseButton';\nimport { FilterContextProvider, type FilterStatus } from './BitkitFilter.context';\nimport BitkitFilterAdd from './BitkitFilterAdd';\nimport BitkitFilterDateRange from './BitkitFilterDateRange';\nimport BitkitFilterSearch from './BitkitFilterSearch';\nimport BitkitFilterSelect from './BitkitFilterSelect';\nimport BitkitFilterSwitch from './BitkitFilterSwitch';\n\nexport interface BitkitFilterProps extends Omit<BoxProps, 'children'> {\n /**\n * The filters: `BitkitFilter.Switch`, `.Select`, `.DateRange`, `.Add` and `.Search`, in the\n * order they appear. `.Search` always sits at the end of the bar.\n */\n children: ReactNode;\n /**\n * Where a capped content block sits: `center` over a centred page column, `start` over one that\n * starts against a sidebar. Pass the same alignment as the page header and content below it.\n * Only applies with a `contentWidth` in rems. Desktop only.\n */\n contentAlign?: 'center' | 'start';\n /**\n * Lines the bar up with the page content below it, for a bar under a page header: it gains a\n * bottom border and the page's inline padding. Pass the content column's `max-width` in rems to\n * cap the bar's content with it (padded 32 on both sides), or `'full'` for a full-width column\n * (24 at the start, like the default `BitkitPageHeader`, and 32 at the end).\n *\n * Without it the bar has the tighter inset of a filter above a table. Desktop only.\n */\n contentWidth?: `${number}rem` | 'full';\n /**\n * Forces the desktop bar or the mobile \"Filters\" dialog. Defaults to the viewport: mobile below the\n * tablet breakpoint. The mobile dialog is made for narrow screens; from tablet up it is a 320px drawer.\n */\n device?: 'desktop' | 'mobile';\n /**\n * Called once on \"Clear filters\" in place of handing each changed filter its `resetValue` — for a\n * page that resets them all in one write, e.g. one URL update. The `resetValue`s still decide when\n * the button shows.\n */\n onClear?: () => void;\n /**\n * Shows \"Clear filters\" while any filter differs from its `resetValue`. It hands each of those\n * filters its `resetValue` back through its own `onValueChange`, or calls `onClear`.\n */\n showClear?: boolean;\n /** Shows the filter icon at the start of the bar. Desktop only. */\n showIcon?: boolean;\n /** `loading`: while the page's data loads, the filters can't be opened, removed or cleared. The search stays usable. */\n state?: 'loading';\n}\n\nconst toggle = (set: ReadonlySet<string>, id: string, isIn: boolean) => {\n if (set.has(id) === isIn) return set;\n const next = new Set(set);\n if (isIn) next.add(id);\n else next.delete(id);\n return next;\n};\n\n/**\n * A bar of filters above a list. Each filter is a controlled sub-component with its own `value` and\n * `onValueChange`; the bar lays them out and offers \"Clear filters\" across all of them.\n *\n * Below the tablet breakpoint the bar collapses into a \"Filters\" button opening a full-screen dialog,\n * where every filter is a field opening its own view. A filter still applies on its view's \"Apply\".\n */\nconst BitkitFilter = forwardRef<HTMLDivElement, BitkitFilterProps>((props, ref) => {\n const {\n children,\n contentAlign,\n contentWidth,\n css,\n device,\n onClear,\n showClear = true,\n showIcon = true,\n state,\n ...rest\n } = props;\n const { isMobile: isMobileViewport } = useResponsive();\n const isMobile = device ? device === 'mobile' : isMobileViewport;\n const isDisabled = state === 'loading';\n const { disablePortal } = usePortalContext();\n const [isDialogOpen, setIsDialogOpen] = useState(false);\n\n const recipe = useSlotRecipe({ key: 'filter' });\n let pageLayout: 'capped' | 'full' | undefined;\n if (contentWidth) pageLayout = contentWidth === 'full' ? 'full' : 'capped';\n const styles = recipe({ contentAlign: pageLayout === 'capped' ? contentAlign : undefined, pageLayout });\n\n const resets = useRef(new Map<string, () => void>());\n const [dirtyIds, setDirtyIds] = useState<ReadonlySet<string>>(new Set());\n const [activeIds, setActiveIds] = useState<ReadonlySet<string>>(new Set());\n\n const register = useCallback((id: string, reset: () => void) => {\n resets.current.set(id, reset);\n }, []);\n\n const setStatus = useCallback((id: string, { isActive, isDirty }: FilterStatus) => {\n setDirtyIds((prev) => toggle(prev, id, isDirty));\n setActiveIds((prev) => toggle(prev, id, isActive));\n }, []);\n\n const unregister = useCallback(\n (id: string) => {\n resets.current.delete(id);\n setStatus(id, { isActive: false, isDirty: false });\n },\n [setStatus],\n );\n\n const context = useMemo(\n () => ({ isDisabled, isMobile, register, setStatus, styles, unregister }),\n [isDisabled, isMobile, register, setStatus, styles, unregister],\n );\n\n const filters: ReactNode[] = [];\n const searches: ReactNode[] = [];\n // Flattened, so a filter grouped in a fragment is still sorted to the right place.\n flattenChildren(children).forEach((child) => {\n if (child.type === BitkitFilterSearch) searches.push(child);\n else filters.push(child);\n });\n\n const isDirty = dirtyIds.size > 0;\n const activeCount = activeIds.size;\n // The \"Clear filters\" button goes away (or is disabled) once nothing differs, so focus then moves\n // to the first control of the cleared filters rather than dropping to the page. Only if it hasn't\n // gone anywhere else meanwhile, e.g. while a slow reset was under way.\n const focusTarget = useRef<HTMLDivElement>(null);\n const isFocusPending = useRef(false);\n const clearAll = () => {\n if (isDisabled) return;\n isFocusPending.current = true;\n // Called off once focus lands anywhere, so a clear that never fully lands can't pull it in later.\n document.addEventListener(\n 'focusin',\n () => {\n isFocusPending.current = false;\n },\n { once: true },\n );\n if (onClear) onClear();\n else dirtyIds.forEach((id) => resets.current.get(id)?.());\n };\n useEffect(() => {\n if (!isFocusPending.current || isDirty) return;\n isFocusPending.current = false;\n const active = document.activeElement;\n if (active && active !== document.body && !active.matches('[data-filter-clear]')) return;\n focusTarget.current?.querySelector<HTMLElement>('button:not(:disabled), input:not(:disabled)')?.focus();\n }, [isDirty]);\n\n // While the page loads, the controls are only `aria-disabled`, so they keep a focus handed to them.\n // `data-disabled` takes them out of the hover and active styles too.\n const disabledProps = { 'aria-disabled': isDisabled || undefined, 'data-disabled': isDisabled ? '' : undefined };\n const clearProps = { ...disabledProps, 'data-filter-clear': '', onClick: clearAll };\n\n if (isMobile) {\n return (\n <FilterContextProvider value={context}>\n <Box css={css} ref={ref} {...rest}>\n <Box css={styles.mobileBar} ref={isDialogOpen ? undefined : focusTarget}>\n <chakra.button\n aria-expanded={isDialogOpen}\n aria-haspopup=\"dialog\"\n aria-label={`Filters, ${activeCount} applied`}\n {...disabledProps}\n css={styles.mobileTrigger}\n onClick={() => !isDisabled && setIsDialogOpen(true)}\n type=\"button\"\n >\n <IconFilter size=\"16\" />\n Filters\n <BitkitBadge colorVariant={activeCount ? 'purple' : 'neutral'}>{activeCount}</BitkitBadge>\n </chakra.button>\n {showClear && isDirty && (\n <BitkitButton {...clearProps} icon={IconCross} size=\"sm\" variant=\"tertiary\">\n Clear filters\n </BitkitButton>\n )}\n </Box>\n {searches}\n {/* Kept mounted while closed: the filters inside register their values with the bar. */}\n <Drawer.Root\n lazyMount={false}\n onOpenChange={({ open }) => setIsDialogOpen(open)}\n open={isDialogOpen}\n unmountOnExit={false}\n >\n <Portal disabled={disablePortal}>\n <Drawer.Positioner>\n <Drawer.Content>\n <Box css={styles.mobileHeader}>\n <Drawer.Title css={styles.mobileTitle} unstyled>\n Filters\n </Drawer.Title>\n <Drawer.CloseTrigger asChild unstyled>\n <BitkitCloseButton size=\"sm\" />\n </Drawer.CloseTrigger>\n </Box>\n <Drawer.Body css={styles.mobileBody} ref={isDialogOpen ? focusTarget : undefined}>\n {filters}\n </Drawer.Body>\n <Drawer.Footer>\n <Box css={styles.mobileFooter}>\n {showClear && (\n <BitkitButton\n {...clearProps}\n size=\"md\"\n state={isDirty ? undefined : 'disabled'}\n variant=\"secondary\"\n >\n Clear filters\n </BitkitButton>\n )}\n <BitkitButton onClick={() => setIsDialogOpen(false)} size=\"md\">\n Show results\n </BitkitButton>\n </Box>\n </Drawer.Footer>\n </Drawer.Content>\n </Drawer.Positioner>\n </Portal>\n </Drawer.Root>\n </Box>\n </FilterContextProvider>\n );\n }\n\n return (\n <FilterContextProvider value={context}>\n <Box css={[styles.root, css]} ref={ref} {...rest}>\n <Box css={styles.content} maxWidth={contentWidth === 'full' ? undefined : contentWidth} ref={focusTarget}>\n <Box css={styles.filters}>\n {showIcon && (\n <Box css={styles.icon}>\n <IconFilter />\n </Box>\n )}\n <Box css={styles.items}>{filters}</Box>\n {showClear && isDirty && (\n <BitkitButton {...clearProps} flexShrink=\"0\" icon={IconCross} size=\"sm\" variant=\"tertiary\">\n Clear filters\n </BitkitButton>\n )}\n </Box>\n {searches.length > 0 && <Box css={styles.searchBlock}>{searches}</Box>}\n </Box>\n </Box>\n </FilterContextProvider>\n );\n});\n\nBitkitFilter.displayName = 'BitkitFilter';\n\nexport default withSubComponents(BitkitFilter, {\n Add: BitkitFilterAdd,\n DateRange: BitkitFilterDateRange,\n Search: BitkitFilterSearch,\n Select: BitkitFilterSelect,\n Switch: BitkitFilterSwitch,\n});\n"],"mappings":"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"}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export type FilterValue = string | string[] | Date[] | null;
|
|
2
|
-
/** Typing in a search settles for this long before its callback fires. */
|
|
3
|
-
export declare const SEARCH_DEBOUNCE_MS = 300;
|
|
4
|
-
/** Compares option lists as sets, since checking options in another order picks the same ones; dates by position. */
|
|
5
|
-
export declare const isEqualFilterValue: (a: FilterValue, b: FilterValue) => boolean;
|
|
6
|
-
/** A filter value that holds no selection. */
|
|
7
|
-
export declare const isEmptyFilterValue: (value: FilterValue) => boolean;
|
|
8
|
-
export declare const isPattern: (value: string) => boolean;
|
|
9
|
-
/** Compiles a wildcard `pattern`, where `*` stands for any run of characters, to match whole texts. */
|
|
10
|
-
export declare const compilePattern: (pattern: string) => RegExp;
|
|
11
|
-
/**
|
|
12
|
-
* Lowercases a noun for running text, keeping any word with a capital after its first letter as an
|
|
13
|
-
* acronym or name: "CI Providers" reads "CI providers", "PRs" and "iOS apps" stay as they are.
|
|
14
|
-
*/
|
|
15
|
-
export declare const toRunningText: (noun: string) => string;
|
|
16
|
-
export declare const formatDateRange: (range: Date[]) => string;
|
|
17
|
-
/** The last moment of the day `date` falls on, so a range ending that day includes all of it. */
|
|
18
|
-
export declare const endOfDay: (date: Date) => Date;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
//#region lib/components/BitkitFilter/BitkitFilter.utils.ts
|
|
2
|
-
/** Compares option lists as sets, since checking options in another order picks the same ones; dates by position. */
|
|
3
|
-
var isEqualFilterValue = (a, b) => {
|
|
4
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
5
|
-
if (a.length !== b.length) return false;
|
|
6
|
-
if (a.every((item) => typeof item === "string")) {
|
|
7
|
-
const others = new Set(b);
|
|
8
|
-
return a.every((item) => others.has(item));
|
|
9
|
-
}
|
|
10
|
-
return a.every((item, index) => {
|
|
11
|
-
const other = b[index];
|
|
12
|
-
return item instanceof Date && other instanceof Date ? item.getTime() === other.getTime() : item === other;
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
return a === b;
|
|
16
|
-
};
|
|
17
|
-
/** A filter value that holds no selection. */
|
|
18
|
-
var isEmptyFilterValue = (value) => value === null || value === "" || Array.isArray(value) && value.length === 0;
|
|
19
|
-
var regExpEscape = (text) => text.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
20
|
-
var isPattern = (value) => value.includes("*");
|
|
21
|
-
/** Compiles a wildcard `pattern`, where `*` stands for any run of characters, to match whole texts. */
|
|
22
|
-
var compilePattern = (pattern) => new RegExp(`^${pattern.split(/\*+/).map(regExpEscape).join(".*")}$`);
|
|
23
|
-
/**
|
|
24
|
-
* Lowercases a noun for running text, keeping any word with a capital after its first letter as an
|
|
25
|
-
* acronym or name: "CI Providers" reads "CI providers", "PRs" and "iOS apps" stay as they are.
|
|
26
|
-
*/
|
|
27
|
-
var toRunningText = (noun) => noun.split(" ").map((word) => /[A-Z]/.test(word.slice(1)) ? word : word.toLowerCase()).join(" ");
|
|
28
|
-
var dateFormat = new Intl.DateTimeFormat("en-US", {
|
|
29
|
-
day: "numeric",
|
|
30
|
-
month: "short"
|
|
31
|
-
});
|
|
32
|
-
var formatDateRange = (range) => range.map((date) => dateFormat.format(date)).join(" – ");
|
|
33
|
-
/** The last moment of the day `date` falls on, so a range ending that day includes all of it. */
|
|
34
|
-
var endOfDay = (date) => {
|
|
35
|
-
const end = new Date(date);
|
|
36
|
-
end.setHours(23, 59, 59, 999);
|
|
37
|
-
return end;
|
|
38
|
-
};
|
|
39
|
-
//#endregion
|
|
40
|
-
export { compilePattern, endOfDay, formatDateRange, isEmptyFilterValue, isEqualFilterValue, isPattern, toRunningText };
|
|
41
|
-
|
|
42
|
-
//# sourceMappingURL=BitkitFilter.utils.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitFilter.utils.js","names":[],"sources":["../../../lib/components/BitkitFilter/BitkitFilter.utils.ts"],"sourcesContent":["export type FilterValue = string | string[] | Date[] | null;\n\n/** Typing in a search settles for this long before its callback fires. */\nexport const SEARCH_DEBOUNCE_MS = 300;\n\n/** Compares option lists as sets, since checking options in another order picks the same ones; dates by position. */\nexport const isEqualFilterValue = (a: FilterValue, b: FilterValue): boolean => {\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n if (a.every((item) => typeof item === 'string')) {\n const others = new Set<unknown>(b);\n return a.every((item) => others.has(item));\n }\n return a.every((item, index) => {\n const other = b[index];\n return item instanceof Date && other instanceof Date ? item.getTime() === other.getTime() : item === other;\n });\n }\n return a === b;\n};\n\n/** A filter value that holds no selection. */\nexport const isEmptyFilterValue = (value: FilterValue): boolean =>\n value === null || value === '' || (Array.isArray(value) && value.length === 0);\n\nconst regExpEscape = (text: string) => text.replace(/[|\\\\{}()[\\]^$+?.]/g, '\\\\$&');\n\nexport const isPattern = (value: string) => value.includes('*');\n\n/** Compiles a wildcard `pattern`, where `*` stands for any run of characters, to match whole texts. */\nexport const compilePattern = (pattern: string) => new RegExp(`^${pattern.split(/\\*+/).map(regExpEscape).join('.*')}$`);\n\n/**\n * Lowercases a noun for running text, keeping any word with a capital after its first letter as an\n * acronym or name: \"CI Providers\" reads \"CI providers\", \"PRs\" and \"iOS apps\" stay as they are.\n */\nexport const toRunningText = (noun: string) =>\n noun\n .split(' ')\n .map((word) => (/[A-Z]/.test(word.slice(1)) ? word : word.toLowerCase()))\n .join(' ');\n\nconst dateFormat = new Intl.DateTimeFormat('en-US', { day: 'numeric', month: 'short' });\n\nexport const formatDateRange = (range: Date[]) => range.map((date) => dateFormat.format(date)).join(' – ');\n\n/** The last moment of the day `date` falls on, so a range ending that day includes all of it. */\nexport const endOfDay = (date: Date) => {\n const end = new Date(date);\n end.setHours(23, 59, 59, 999);\n return end;\n};\n"],"mappings":";;AAMA,IAAa,sBAAsB,GAAgB,MAA4B;CAC7E,IAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GAAG;EACxC,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;EAClC,IAAI,EAAE,OAAO,SAAS,OAAO,SAAS,QAAQ,GAAG;GAC/C,MAAM,SAAS,IAAI,IAAa,CAAC;GACjC,OAAO,EAAE,OAAO,SAAS,OAAO,IAAI,IAAI,CAAC;EAC3C;EACA,OAAO,EAAE,OAAO,MAAM,UAAU;GAC9B,MAAM,QAAQ,EAAE;GAChB,OAAO,gBAAgB,QAAQ,iBAAiB,OAAO,KAAK,QAAQ,MAAM,MAAM,QAAQ,IAAI,SAAS;EACvG,CAAC;CACH;CACA,OAAO,MAAM;AACf;;AAGA,IAAa,sBAAsB,UACjC,UAAU,QAAQ,UAAU,MAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW;AAE9E,IAAM,gBAAgB,SAAiB,KAAK,QAAQ,sBAAsB,MAAM;AAEhF,IAAa,aAAa,UAAkB,MAAM,SAAS,GAAG;;AAG9D,IAAa,kBAAkB,YAAoB,IAAI,OAAO,IAAI,QAAQ,MAAM,KAAK,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,KAAK,IAAI,EAAE,EAAE;;;;;AAMtH,IAAa,iBAAiB,SAC5B,KACG,MAAM,GAAG,CAAC,CACV,KAAK,SAAU,QAAQ,KAAK,KAAK,MAAM,CAAC,CAAC,IAAI,OAAO,KAAK,YAAY,CAAE,CAAC,CACxE,KAAK,GAAG;AAEb,IAAM,aAAa,IAAI,KAAK,eAAe,SAAS;CAAE,KAAK;CAAW,OAAO;AAAQ,CAAC;AAEtF,IAAa,mBAAmB,UAAkB,MAAM,KAAK,SAAS,WAAW,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK;;AAGzG,IAAa,YAAY,SAAe;CACtC,MAAM,MAAM,IAAI,KAAK,IAAI;CACzB,IAAI,SAAS,IAAI,IAAI,IAAI,GAAG;CAC5B,OAAO;AACT"}
|