aplance-ui 1.2.2 → 1.4.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/package.json +1 -1
- package/registry/components/primitives/modal/modal.tsx +16 -10
- package/registry/components/primitives/popover/popover-context.ts +33 -28
- package/registry/components/primitives/popover/popover.tsx +145 -26
- package/registry/components/primitives/slider/slider.module.css +62 -5
- package/registry/components/primitives/slider/slider.tsx +192 -34
- package/registry/components/primitives/slider/types.ts +56 -3
- package/registry/components/primitives/slider/utils.ts +9 -0
- package/registry/components/ui/copy-button/copy-button.tsx +5 -0
- package/registry/components/ui/data-table/data-table.module.css +695 -613
- package/registry/components/ui/data-table/data-table.tsx +136 -112
- package/registry/components/ui/data-table/filter-modal.tsx +86 -0
- package/registry/components/ui/data-table/types.ts +8 -0
- package/registry/components/ui/data-table/useDataTable.tsx +1 -0
- package/registry/components/ui/data-table/utils.ts +26 -1
- package/registry/components/ui/input-fields/custom-select/custom-select.module.css +32 -1
- package/registry/components/ui/input-fields/custom-select/custom-select.tsx +500 -469
- package/registry/components/ui/input-fields/date-picker/date-picker.tsx +940 -933
- package/registry/components/ui/input-fields/date-picker/types.ts +2 -0
- package/registry/components/ui/input-fields/emoji-picker/emoji-picker.tsx +8 -0
- package/registry/components/ui/input-fields/input/input.tsx +80 -69
- package/registry/components/ui/tabs/tabs.module.css +22 -8
- package/registry/components/ui/tabs/tabs.tsx +29 -12
- package/registry/hooks/useHasMounted.ts +22 -0
- package/registry/hooks/useMediaQuery.ts +38 -0
- package/registry/registry.json +41 -4
|
@@ -51,6 +51,8 @@ export type CommonDatePickerProps = Omit<
|
|
|
51
51
|
timePickerPopover?: {
|
|
52
52
|
position?: Position;
|
|
53
53
|
align?: Align;
|
|
54
|
+
autoPosition?: boolean;
|
|
55
|
+
collisionPadding?: number;
|
|
54
56
|
};
|
|
55
57
|
/** Show popover footer actions (Clear/Done) in DatePicker. Default: true. */
|
|
56
58
|
showActionFooter?: boolean;
|
|
@@ -23,6 +23,10 @@ export type EmojiPickerProps = {
|
|
|
23
23
|
/** Popover placement. */
|
|
24
24
|
popoverPosition?: Position;
|
|
25
25
|
popoverAlign?: Align;
|
|
26
|
+
/** Auto-position the popover to stay within the viewport. Default: true. */
|
|
27
|
+
popoverAutoPosition?: boolean;
|
|
28
|
+
/** Viewport padding used by popover auto positioning. */
|
|
29
|
+
popoverCollisionPadding?: number;
|
|
26
30
|
/**
|
|
27
31
|
* Show the popover footer with a Done action so users can pick multiple
|
|
28
32
|
* emojis before dismissing. Default: true. Set to `false` to hide the
|
|
@@ -51,6 +55,8 @@ export const EmojiPicker = ({
|
|
|
51
55
|
columns = 9,
|
|
52
56
|
popoverPosition = "bottom",
|
|
53
57
|
popoverAlign = "end",
|
|
58
|
+
popoverAutoPosition,
|
|
59
|
+
popoverCollisionPadding,
|
|
54
60
|
showActionFooter = true,
|
|
55
61
|
disabled,
|
|
56
62
|
className,
|
|
@@ -62,6 +68,8 @@ export const EmojiPicker = ({
|
|
|
62
68
|
<Popover
|
|
63
69
|
position={popoverPosition}
|
|
64
70
|
align={popoverAlign}
|
|
71
|
+
autoPosition={popoverAutoPosition}
|
|
72
|
+
collisionPadding={popoverCollisionPadding}
|
|
65
73
|
isOpen={isOpen}
|
|
66
74
|
setIsOpen={setIsOpen}
|
|
67
75
|
closeOnClickOutside
|
|
@@ -22,67 +22,67 @@ import { FieldMessage } from "../common/field-message/field-message";
|
|
|
22
22
|
import { Icon } from "../../../primitives/icon/icon";
|
|
23
23
|
import type { BaseInputFieldProps } from "../common/types";
|
|
24
24
|
|
|
25
|
-
export type InputType =
|
|
26
|
-
| "text"
|
|
27
|
-
| "email"
|
|
28
|
-
| "tel"
|
|
29
|
-
| "url"
|
|
30
|
-
| "password"
|
|
31
|
-
| "search"
|
|
32
|
-
| "number"
|
|
33
|
-
| "file"
|
|
34
|
-
| "radio"
|
|
35
|
-
| "date"
|
|
36
|
-
| "month"
|
|
25
|
+
export type InputType =
|
|
26
|
+
| "text"
|
|
27
|
+
| "email"
|
|
28
|
+
| "tel"
|
|
29
|
+
| "url"
|
|
30
|
+
| "password"
|
|
31
|
+
| "search"
|
|
32
|
+
| "number"
|
|
33
|
+
| "file"
|
|
34
|
+
| "radio"
|
|
35
|
+
| "date"
|
|
36
|
+
| "month"
|
|
37
37
|
| "week"
|
|
38
38
|
| "time"
|
|
39
39
|
| "datetime-local"
|
|
40
40
|
| "checkbox"
|
|
41
|
-
| "color"
|
|
42
|
-
| "range";
|
|
43
|
-
|
|
44
|
-
const styledInputTypes: readonly InputType[] = [
|
|
45
|
-
"text",
|
|
46
|
-
"email",
|
|
47
|
-
"tel",
|
|
48
|
-
"url",
|
|
49
|
-
"password",
|
|
50
|
-
"search",
|
|
51
|
-
"number",
|
|
52
|
-
"file",
|
|
53
|
-
"date",
|
|
54
|
-
"month",
|
|
55
|
-
"week",
|
|
56
|
-
"time",
|
|
57
|
-
"datetime-local",
|
|
58
|
-
] as const;
|
|
59
|
-
|
|
60
|
-
const clearableInputTypes: readonly InputType[] = [
|
|
61
|
-
"text",
|
|
62
|
-
"email",
|
|
63
|
-
"tel",
|
|
64
|
-
"url",
|
|
65
|
-
"password",
|
|
66
|
-
"search",
|
|
67
|
-
"number",
|
|
68
|
-
"file",
|
|
69
|
-
] as const;
|
|
70
|
-
|
|
71
|
-
const fitWidthInputTypes: readonly InputType[] = [
|
|
72
|
-
"checkbox",
|
|
73
|
-
"radio",
|
|
74
|
-
"color",
|
|
75
|
-
] as const;
|
|
76
|
-
const dateInputTypes: readonly InputType[] = [
|
|
77
|
-
"date",
|
|
78
|
-
"month",
|
|
79
|
-
"week",
|
|
80
|
-
"datetime-local",
|
|
81
|
-
] as const;
|
|
82
|
-
|
|
83
|
-
type BaseProps = BaseInputFieldProps & {
|
|
84
|
-
type?: InputType;
|
|
85
|
-
onClear?: () => void;
|
|
41
|
+
| "color"
|
|
42
|
+
| "range";
|
|
43
|
+
|
|
44
|
+
const styledInputTypes: readonly InputType[] = [
|
|
45
|
+
"text",
|
|
46
|
+
"email",
|
|
47
|
+
"tel",
|
|
48
|
+
"url",
|
|
49
|
+
"password",
|
|
50
|
+
"search",
|
|
51
|
+
"number",
|
|
52
|
+
"file",
|
|
53
|
+
"date",
|
|
54
|
+
"month",
|
|
55
|
+
"week",
|
|
56
|
+
"time",
|
|
57
|
+
"datetime-local",
|
|
58
|
+
] as const;
|
|
59
|
+
|
|
60
|
+
const clearableInputTypes: readonly InputType[] = [
|
|
61
|
+
"text",
|
|
62
|
+
"email",
|
|
63
|
+
"tel",
|
|
64
|
+
"url",
|
|
65
|
+
"password",
|
|
66
|
+
"search",
|
|
67
|
+
"number",
|
|
68
|
+
"file",
|
|
69
|
+
] as const;
|
|
70
|
+
|
|
71
|
+
const fitWidthInputTypes: readonly InputType[] = [
|
|
72
|
+
"checkbox",
|
|
73
|
+
"radio",
|
|
74
|
+
"color",
|
|
75
|
+
] as const;
|
|
76
|
+
const dateInputTypes: readonly InputType[] = [
|
|
77
|
+
"date",
|
|
78
|
+
"month",
|
|
79
|
+
"week",
|
|
80
|
+
"datetime-local",
|
|
81
|
+
] as const;
|
|
82
|
+
|
|
83
|
+
type BaseProps = BaseInputFieldProps & {
|
|
84
|
+
type?: InputType;
|
|
85
|
+
onClear?: () => void;
|
|
86
86
|
showClear?: boolean;
|
|
87
87
|
};
|
|
88
88
|
|
|
@@ -139,18 +139,18 @@ export const Input = ({
|
|
|
139
139
|
const generatedId = useId();
|
|
140
140
|
const inputId = props.id ?? generatedId;
|
|
141
141
|
|
|
142
|
-
const showPasswordToggle = isPassword && hasValue;
|
|
143
|
-
const showClearButton =
|
|
144
|
-
showClear && hasValue && clearableInputTypes.includes(type);
|
|
145
|
-
|
|
146
|
-
const stylesToApplyFields = styledInputTypes.includes(type);
|
|
147
|
-
const stylesFitWidth = fitWidthInputTypes.includes(type);
|
|
148
|
-
const hasButtonStyle = buttonStyle !== "none";
|
|
149
|
-
const buttonToneClass = hasButtonStyle
|
|
150
|
-
? sButton[`tone_${buttonStyle}`]
|
|
151
|
-
: undefined;
|
|
152
|
-
|
|
153
|
-
const isDateFamily = dateInputTypes.includes(type);
|
|
142
|
+
const showPasswordToggle = isPassword && hasValue;
|
|
143
|
+
const showClearButton =
|
|
144
|
+
showClear && hasValue && clearableInputTypes.includes(type);
|
|
145
|
+
|
|
146
|
+
const stylesToApplyFields = styledInputTypes.includes(type);
|
|
147
|
+
const stylesFitWidth = fitWidthInputTypes.includes(type);
|
|
148
|
+
const hasButtonStyle = buttonStyle !== "none";
|
|
149
|
+
const buttonToneClass = hasButtonStyle
|
|
150
|
+
? sButton[`tone_${buttonStyle}`]
|
|
151
|
+
: undefined;
|
|
152
|
+
|
|
153
|
+
const isDateFamily = dateInputTypes.includes(type);
|
|
154
154
|
const isTimeFamily = type === "time";
|
|
155
155
|
|
|
156
156
|
const showDateCalendarIcon = isDateFamily && !iconEnd;
|
|
@@ -174,6 +174,16 @@ export const Input = ({
|
|
|
174
174
|
onChange?.(e);
|
|
175
175
|
};
|
|
176
176
|
|
|
177
|
+
// A programmatic value set (e.g. react-hook-form populating a field on reset)
|
|
178
|
+
// sets the DOM value via the ref WITHOUT firing onChange, so `hasValue` can lag
|
|
179
|
+
// the real value and the reveal/clear actions stay hidden on a pre-filled
|
|
180
|
+
// field. Re-sync from the actual value on focus so they appear as soon as the
|
|
181
|
+
// field is interacted with.
|
|
182
|
+
const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
|
|
183
|
+
setHasValue(e.target.value.length > 0);
|
|
184
|
+
props.onFocus?.(e);
|
|
185
|
+
};
|
|
186
|
+
|
|
177
187
|
const handleClear = () => {
|
|
178
188
|
const element = inputRef.current;
|
|
179
189
|
if (!element) return;
|
|
@@ -279,6 +289,7 @@ export const Input = ({
|
|
|
279
289
|
style={dynamicInputStyle}
|
|
280
290
|
{...props}
|
|
281
291
|
onChange={handleInputChange}
|
|
292
|
+
onFocus={handleFocus}
|
|
282
293
|
/>
|
|
283
294
|
|
|
284
295
|
<div className={clsx(sCommon.action_end_wrapper)}>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* ─────────────────────────────────────────────
|
|
2
|
-
Tabs
|
|
2
|
+
Tabs - root
|
|
3
3
|
───────────────────────────────────────────── */
|
|
4
4
|
.root {
|
|
5
5
|
display: flex;
|
|
@@ -15,8 +15,20 @@
|
|
|
15
15
|
position: relative;
|
|
16
16
|
display: inline-flex;
|
|
17
17
|
align-items: center;
|
|
18
|
+
/* Scroll horizontally when the tabs don't fit (e.g. many status tabs on a
|
|
19
|
+
phone) instead of clipping/overflowing the page. The active indicator is
|
|
20
|
+
absolutely positioned inside, so it scrolls with the tabs and stays aligned.
|
|
21
|
+
Scrollbar hidden for a clean strip - the partially-shown edge tab signals
|
|
22
|
+
scrollability. */
|
|
23
|
+
max-width: 100%;
|
|
24
|
+
overflow-x: auto;
|
|
25
|
+
scrollbar-width: none;
|
|
26
|
+
|
|
27
|
+
&::-webkit-scrollbar {
|
|
28
|
+
display: none;
|
|
29
|
+
}
|
|
18
30
|
|
|
19
|
-
/* style1
|
|
31
|
+
/* style1 - pill container */
|
|
20
32
|
&.list_style1 {
|
|
21
33
|
background: var(--color-muted);
|
|
22
34
|
border-radius: var(--radius-md);
|
|
@@ -24,7 +36,7 @@
|
|
|
24
36
|
gap: var(--spacing-1);
|
|
25
37
|
}
|
|
26
38
|
|
|
27
|
-
/* style2
|
|
39
|
+
/* style2 - underline container */
|
|
28
40
|
&.list_style2 {
|
|
29
41
|
border-bottom: var(--border-1) solid var(--color-border);
|
|
30
42
|
gap: var(--spacing-6);
|
|
@@ -45,7 +57,7 @@
|
|
|
45
57
|
width var(--transition-base);
|
|
46
58
|
will-change: left, width;
|
|
47
59
|
|
|
48
|
-
/* style1
|
|
60
|
+
/* style1 - filled pill */
|
|
49
61
|
&.indicator_style1 {
|
|
50
62
|
top: var(--spacing-1);
|
|
51
63
|
height: calc(100% - var(--spacing-1) * 2);
|
|
@@ -54,7 +66,7 @@
|
|
|
54
66
|
box-shadow: var(--shadow-sm);
|
|
55
67
|
}
|
|
56
68
|
|
|
57
|
-
/* style2
|
|
69
|
+
/* style2 - bottom line */
|
|
58
70
|
&.indicator_style2 {
|
|
59
71
|
top: auto;
|
|
60
72
|
bottom: -1px;
|
|
@@ -65,7 +77,7 @@
|
|
|
65
77
|
}
|
|
66
78
|
|
|
67
79
|
/* ─────────────────────────────────────────────
|
|
68
|
-
Tab button
|
|
80
|
+
Tab button - shared base
|
|
69
81
|
───────────────────────────────────────────── */
|
|
70
82
|
.tab {
|
|
71
83
|
position: relative;
|
|
@@ -73,6 +85,8 @@
|
|
|
73
85
|
display: inline-flex;
|
|
74
86
|
align-items: center;
|
|
75
87
|
justify-content: center;
|
|
88
|
+
/* Never shrink in the scroller - keep the label on one line and scroll. */
|
|
89
|
+
flex-shrink: 0;
|
|
76
90
|
font-family: var(--font-family-1);
|
|
77
91
|
font-size: var(--font-size-sm);
|
|
78
92
|
line-height: var(--line-height-sm);
|
|
@@ -91,7 +105,7 @@
|
|
|
91
105
|
cursor: not-allowed;
|
|
92
106
|
}
|
|
93
107
|
|
|
94
|
-
/* style1
|
|
108
|
+
/* style1 - pill tab */
|
|
95
109
|
&.tab_style1 {
|
|
96
110
|
padding: var(--spacing-3) var(--spacing-6);
|
|
97
111
|
border-radius: calc(var(--radius-md) - var(--spacing-1));
|
|
@@ -110,7 +124,7 @@
|
|
|
110
124
|
}
|
|
111
125
|
}
|
|
112
126
|
|
|
113
|
-
/* style2
|
|
127
|
+
/* style2 - underline tab */
|
|
114
128
|
&.tab_style2 {
|
|
115
129
|
padding: var(--spacing-3) var(--spacing-1);
|
|
116
130
|
min-height: var(--spacing-11);
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
useEffect,
|
|
5
5
|
useId,
|
|
6
6
|
useCallback,
|
|
7
|
+
useLayoutEffect,
|
|
7
8
|
type ReactNode,
|
|
8
9
|
type ComponentProps,
|
|
9
10
|
} from "react";
|
|
@@ -15,8 +16,13 @@ import s from "./tabs.module.css";
|
|
|
15
16
|
export const variants = ["style1", "style2"] as const;
|
|
16
17
|
export type Variant = (typeof variants)[number];
|
|
17
18
|
|
|
19
|
+
const useIsomorphicLayoutEffect =
|
|
20
|
+
typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
21
|
+
|
|
18
22
|
export type TabItem = {
|
|
19
23
|
id: string;
|
|
24
|
+
// Consumers can compose counts, badges, icons, etc. at the call site.
|
|
25
|
+
// Dynamic label changes re-measure the indicator without the slide animation.
|
|
20
26
|
label: ReactNode;
|
|
21
27
|
content?: ReactNode;
|
|
22
28
|
disabled?: boolean;
|
|
@@ -49,28 +55,35 @@ export const Tabs = ({
|
|
|
49
55
|
|
|
50
56
|
const activeId = isControlled ? controlledActive : internalActive;
|
|
51
57
|
|
|
52
|
-
//
|
|
58
|
+
// `animate` separates deliberate tab switches from layout-driven re-fits.
|
|
53
59
|
const listRef = useRef<HTMLDivElement>(null);
|
|
54
|
-
const [indicator, setIndicator] = useState({ left: 0, width: 0 });
|
|
60
|
+
const [indicator, setIndicator] = useState({ left: 0, width: 0, animate: true });
|
|
55
61
|
|
|
56
|
-
const updateIndicator = useCallback((id: string) => {
|
|
62
|
+
const updateIndicator = useCallback((id: string, animate: boolean) => {
|
|
57
63
|
const list = listRef.current;
|
|
58
64
|
if (!list) return;
|
|
59
65
|
const btn = list.querySelector<HTMLButtonElement>(`[data-tab-id="${id}"]`);
|
|
60
66
|
if (!btn) return;
|
|
61
67
|
const listRect = list.getBoundingClientRect();
|
|
62
68
|
const btnRect = btn.getBoundingClientRect();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
// Measure in the scrollable list's content coordinates so the indicator
|
|
70
|
+
// stays aligned when the tab strip is horizontally scrolled.
|
|
71
|
+
const left = btnRect.left - listRect.left + list.scrollLeft;
|
|
72
|
+
const width = btnRect.width;
|
|
73
|
+
setIndicator((prev) =>
|
|
74
|
+
prev.left === left && prev.width === width ? prev : { left, width, animate },
|
|
75
|
+
);
|
|
67
76
|
}, []);
|
|
68
77
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
78
|
+
const prevActiveRef = useRef<string | null>(null);
|
|
79
|
+
useIsomorphicLayoutEffect(() => {
|
|
80
|
+
const isSwitch = prevActiveRef.current !== activeId;
|
|
81
|
+
prevActiveRef.current = activeId;
|
|
82
|
+
updateIndicator(activeId, isSwitch);
|
|
83
|
+
}, [tabs, activeId, updateIndicator]);
|
|
72
84
|
|
|
73
|
-
|
|
85
|
+
// Container and font reflows re-fit the indicator without animating.
|
|
86
|
+
useResizeObserver(listRef, () => updateIndicator(activeId, false));
|
|
74
87
|
const handleSelect = (id: string) => {
|
|
75
88
|
if (!isControlled) setInternalActive(id);
|
|
76
89
|
onTabChange?.(id);
|
|
@@ -89,7 +102,11 @@ export const Tabs = ({
|
|
|
89
102
|
{/* Animated indicator */}
|
|
90
103
|
<span
|
|
91
104
|
className={clsx(s.indicator, s[`indicator_${variant}`])}
|
|
92
|
-
style={{
|
|
105
|
+
style={{
|
|
106
|
+
left: indicator.left,
|
|
107
|
+
width: indicator.width,
|
|
108
|
+
transition: indicator.animate ? undefined : "none",
|
|
109
|
+
}}
|
|
93
110
|
aria-hidden="true"
|
|
94
111
|
/>
|
|
95
112
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
|
|
3
|
+
// The value never changes after the initial client commit, so nothing to
|
|
4
|
+
// subscribe to - a no-op unsubscribe.
|
|
5
|
+
const noopSubscribe = (): (() => void) => () => {};
|
|
6
|
+
const getClientSnapshot = (): boolean => true;
|
|
7
|
+
const getServerSnapshot = (): boolean => false;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `false` during SSR and on the FIRST client render, then `true` after mount.
|
|
11
|
+
*
|
|
12
|
+
* Gate any UI that depends on client-only state the server can't know (RTK Query
|
|
13
|
+
* caches, cookies, localStorage) behind this so the hydrated markup matches the
|
|
14
|
+
* server HTML - otherwise React throws the "tree hydrated but some attributes
|
|
15
|
+
* didn't match" warning (and won't patch the difference). The real client state
|
|
16
|
+
* reveals on the tick right after hydration.
|
|
17
|
+
*
|
|
18
|
+
* Built on `useSyncExternalStore` (React's hydration-safe server/client split)
|
|
19
|
+
* rather than a `useEffect` mount flag, so there's no setState-in-effect.
|
|
20
|
+
*/
|
|
21
|
+
export const useHasMounted = (): boolean =>
|
|
22
|
+
useSyncExternalStore(noopSubscribe, getClientSnapshot, getServerSnapshot);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
2
|
+
|
|
3
|
+
const noopSubscribe = (): (() => void) => () => {};
|
|
4
|
+
|
|
5
|
+
const canUseMatchMedia = (): boolean =>
|
|
6
|
+
typeof window !== "undefined" && typeof window.matchMedia === "function";
|
|
7
|
+
|
|
8
|
+
const getMatch = (query: string): boolean =>
|
|
9
|
+
canUseMatchMedia() ? window.matchMedia(query).matches : false;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Subscribe to a CSS media query; re-renders whenever it flips.
|
|
13
|
+
*
|
|
14
|
+
* SSR-safe via `useSyncExternalStore`: returns `false` on the server and on the
|
|
15
|
+
* first client render (so the hydrated markup matches the server HTML), then
|
|
16
|
+
* settles to the real `window.matchMedia` result on the next client tick - the
|
|
17
|
+
* same hydration split as `useHasMounted`. Keep the `query` string stable (a
|
|
18
|
+
* module-level const, not an inline template) so the subscription does not
|
|
19
|
+
* re-bind on every render.
|
|
20
|
+
*/
|
|
21
|
+
export const useMediaQuery = (query: string): boolean => {
|
|
22
|
+
const subscribe = useCallback(
|
|
23
|
+
(onStoreChange: () => void) => {
|
|
24
|
+
if (!canUseMatchMedia()) return noopSubscribe();
|
|
25
|
+
|
|
26
|
+
const mql = window.matchMedia(query);
|
|
27
|
+
mql.addEventListener("change", onStoreChange);
|
|
28
|
+
return () => mql.removeEventListener("change", onStoreChange);
|
|
29
|
+
},
|
|
30
|
+
[query],
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
return useSyncExternalStore(
|
|
34
|
+
subscribe,
|
|
35
|
+
() => getMatch(query),
|
|
36
|
+
() => false,
|
|
37
|
+
);
|
|
38
|
+
};
|
package/registry/registry.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://ui.aplance.com/registry.schema.json",
|
|
3
3
|
"version": 4,
|
|
4
|
-
"generated": "2026-
|
|
4
|
+
"generated": "2026-09-11T04:18:36.662Z",
|
|
5
5
|
"units": [
|
|
6
6
|
{
|
|
7
7
|
"name": "accordion",
|
|
@@ -213,6 +213,7 @@
|
|
|
213
213
|
"components/ui/data-table/data-table.module.css",
|
|
214
214
|
"components/ui/data-table/data-table.tsx",
|
|
215
215
|
"components/ui/data-table/filter-bar.tsx",
|
|
216
|
+
"components/ui/data-table/filter-modal.tsx",
|
|
216
217
|
"components/ui/data-table/pagination-bar.tsx",
|
|
217
218
|
"components/ui/data-table/types.ts",
|
|
218
219
|
"components/ui/data-table/useDataTable.tsx",
|
|
@@ -221,6 +222,7 @@
|
|
|
221
222
|
"clientFiles": [
|
|
222
223
|
"components/ui/data-table/checkbox-column.tsx",
|
|
223
224
|
"components/ui/data-table/data-table.tsx",
|
|
225
|
+
"components/ui/data-table/filter-modal.tsx",
|
|
224
226
|
"components/ui/data-table/pagination-bar.tsx",
|
|
225
227
|
"components/ui/data-table/types.ts",
|
|
226
228
|
"components/ui/data-table/useDataTable.tsx",
|
|
@@ -229,10 +231,13 @@
|
|
|
229
231
|
"deps": [
|
|
230
232
|
"button",
|
|
231
233
|
"custom-select",
|
|
234
|
+
"icon",
|
|
232
235
|
"input",
|
|
233
236
|
"lib-dom",
|
|
234
237
|
"loader",
|
|
235
|
-
"
|
|
238
|
+
"modal",
|
|
239
|
+
"tooltip",
|
|
240
|
+
"useMediaQuery"
|
|
236
241
|
],
|
|
237
242
|
"externalDeps": [
|
|
238
243
|
"@tabler/icons-react",
|
|
@@ -506,7 +511,8 @@
|
|
|
506
511
|
"deps": [
|
|
507
512
|
"button",
|
|
508
513
|
"icon",
|
|
509
|
-
"useFocusOutside"
|
|
514
|
+
"useFocusOutside",
|
|
515
|
+
"useHasMounted"
|
|
510
516
|
],
|
|
511
517
|
"externalDeps": [
|
|
512
518
|
"@tabler/icons-react",
|
|
@@ -551,7 +557,8 @@
|
|
|
551
557
|
"components/primitives/popover/popover.tsx"
|
|
552
558
|
],
|
|
553
559
|
"deps": [
|
|
554
|
-
"useFocusOutside"
|
|
560
|
+
"useFocusOutside",
|
|
561
|
+
"useHasMounted"
|
|
555
562
|
],
|
|
556
563
|
"externalDeps": [
|
|
557
564
|
"clsx",
|
|
@@ -783,6 +790,36 @@
|
|
|
783
790
|
"react"
|
|
784
791
|
]
|
|
785
792
|
},
|
|
793
|
+
{
|
|
794
|
+
"name": "useHasMounted",
|
|
795
|
+
"kind": "hook",
|
|
796
|
+
"group": null,
|
|
797
|
+
"files": [
|
|
798
|
+
"hooks/useHasMounted.ts"
|
|
799
|
+
],
|
|
800
|
+
"clientFiles": [
|
|
801
|
+
"hooks/useHasMounted.ts"
|
|
802
|
+
],
|
|
803
|
+
"deps": [],
|
|
804
|
+
"externalDeps": [
|
|
805
|
+
"react"
|
|
806
|
+
]
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
"name": "useMediaQuery",
|
|
810
|
+
"kind": "hook",
|
|
811
|
+
"group": null,
|
|
812
|
+
"files": [
|
|
813
|
+
"hooks/useMediaQuery.ts"
|
|
814
|
+
],
|
|
815
|
+
"clientFiles": [
|
|
816
|
+
"hooks/useMediaQuery.ts"
|
|
817
|
+
],
|
|
818
|
+
"deps": [],
|
|
819
|
+
"externalDeps": [
|
|
820
|
+
"react"
|
|
821
|
+
]
|
|
822
|
+
},
|
|
786
823
|
{
|
|
787
824
|
"name": "useResizeObserver",
|
|
788
825
|
"kind": "hook",
|