gbs-add-block 1.2.7 → 1.2.9

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.
Files changed (57) hide show
  1. package/README.md +3 -3
  2. package/index.cjs +10 -9
  3. package/package.json +1 -1
  4. package/source/components/combobox/README.md +170 -0
  5. package/source/components/combobox/__tests__/core.test.ts +134 -0
  6. package/source/components/combobox/core/filter.ts +155 -0
  7. package/source/components/combobox/core/index.ts +16 -0
  8. package/source/components/combobox/core/types.ts +48 -0
  9. package/source/components/combobox/core/virtual.ts +67 -0
  10. package/source/components/combobox/index.ts +13 -0
  11. package/source/components/combobox/react/Listbox.tsx +302 -0
  12. package/source/components/combobox/react/MultiSelect.tsx +263 -0
  13. package/source/components/combobox/react/Popover.tsx +70 -0
  14. package/source/components/combobox/react/Select.tsx +198 -0
  15. package/source/components/combobox/react/icons.tsx +34 -0
  16. package/source/components/combobox/react/locale.ts +16 -0
  17. package/source/components/combobox/react/props.ts +86 -0
  18. package/source/components/combobox/react/useCombobox.ts +399 -0
  19. package/source/components/combobox/styles.css +417 -0
  20. package/source/components/datagridbeta/README.md +232 -0
  21. package/source/components/datagridbeta/__tests__/core.test.ts +341 -0
  22. package/source/components/datagridbeta/__tests__/export.test.ts +70 -0
  23. package/source/components/datagridbeta/core/columnHelper.ts +21 -0
  24. package/source/components/datagridbeta/core/columns.ts +288 -0
  25. package/source/components/datagridbeta/core/filtering.ts +237 -0
  26. package/source/components/datagridbeta/core/grid.ts +616 -0
  27. package/source/components/datagridbeta/core/index.ts +14 -0
  28. package/source/components/datagridbeta/core/rows.ts +64 -0
  29. package/source/components/datagridbeta/core/sorting.ts +111 -0
  30. package/source/components/datagridbeta/core/state.ts +82 -0
  31. package/source/components/datagridbeta/core/store.ts +52 -0
  32. package/source/components/datagridbeta/core/types.ts +266 -0
  33. package/source/components/datagridbeta/core/values.ts +98 -0
  34. package/source/components/datagridbeta/core/virtual.ts +85 -0
  35. package/source/components/datagridbeta/export/csv.ts +32 -0
  36. package/source/components/datagridbeta/export/download.ts +12 -0
  37. package/source/components/datagridbeta/export/pdf.ts +61 -0
  38. package/source/components/datagridbeta/export/table.ts +73 -0
  39. package/source/components/datagridbeta/export/xlsx.ts +145 -0
  40. package/source/components/datagridbeta/export/zip.ts +87 -0
  41. package/source/components/datagridbeta/index.ts +7 -0
  42. package/source/components/datagridbeta/react/Cell.tsx +282 -0
  43. package/source/components/datagridbeta/react/ColumnMenu.tsx +135 -0
  44. package/source/components/datagridbeta/react/DataGrid.tsx +296 -0
  45. package/source/components/datagridbeta/react/FilterForm.tsx +142 -0
  46. package/source/components/datagridbeta/react/HeaderRow.tsx +306 -0
  47. package/source/components/datagridbeta/react/Pagination.tsx +107 -0
  48. package/source/components/datagridbeta/react/Popover.tsx +79 -0
  49. package/source/components/datagridbeta/react/Row.tsx +85 -0
  50. package/source/components/datagridbeta/react/Toolbar.tsx +266 -0
  51. package/source/components/datagridbeta/react/Viewport.tsx +167 -0
  52. package/source/components/datagridbeta/react/context.ts +57 -0
  53. package/source/components/datagridbeta/react/hooks.ts +80 -0
  54. package/source/components/datagridbeta/react/icons.tsx +72 -0
  55. package/source/components/datagridbeta/react/keyboard.ts +110 -0
  56. package/source/components/datagridbeta/react/locale.ts +128 -0
  57. package/source/components/datagridbeta/styles.css +717 -0
@@ -0,0 +1,70 @@
1
+ "use client";
2
+
3
+ import { useLayoutEffect, useRef, type ReactNode, type ToggleEvent } from "react";
4
+ import { cx } from "./props";
5
+
6
+ interface PopoverProps {
7
+ anchor: HTMLElement;
8
+ onClose(): void;
9
+ children: ReactNode;
10
+ className?: string;
11
+ /** Distance between the anchor and the popover. */
12
+ gap?: number;
13
+ }
14
+
15
+ /**
16
+ * Anchored popover on the native Popover API: top layer (never clipped by a
17
+ * scrolling parent or overflow), with light dismiss and Escape built in.
18
+ * It matches the anchor's width and flips above when space is tight.
19
+ */
20
+ export function ComboboxPopover({ anchor, onClose, children, className, gap = 4 }: PopoverProps) {
21
+ const ref = useRef<HTMLDivElement>(null);
22
+
23
+ useLayoutEffect(() => {
24
+ const el = ref.current;
25
+ if (!el) return;
26
+
27
+ const position = () => {
28
+ const rect = anchor.getBoundingClientRect();
29
+ el.style.minWidth = `${rect.width}px`;
30
+ const height = el.offsetHeight;
31
+ const below = window.innerHeight - rect.bottom - gap - 8;
32
+ const flip = height > below && rect.top - gap - 8 > below;
33
+ const left = Math.min(Math.max(8, rect.left), window.innerWidth - el.offsetWidth - 8);
34
+ el.style.left = `${left}px`;
35
+ el.style.top = flip ? `${Math.max(8, rect.top - gap - height)}px` : `${rect.bottom + gap}px`;
36
+ el.style.maxHeight = `${Math.max(140, flip ? rect.top - gap - 8 : below)}px`;
37
+ };
38
+
39
+ el.showPopover();
40
+ position();
41
+ // Focus once the popover is actually shown; a popover is display:none until then.
42
+ el.querySelector<HTMLElement>("[data-autofocus], input, [tabindex]")?.focus({ preventScroll: true });
43
+
44
+ // Follow both sizes: the list as options filter, and the control as tags wrap.
45
+ const observer = new ResizeObserver(position);
46
+ observer.observe(el);
47
+ observer.observe(anchor);
48
+ window.addEventListener("resize", position);
49
+ window.addEventListener("scroll", position, true);
50
+ return () => {
51
+ observer.disconnect();
52
+ window.removeEventListener("resize", position);
53
+ window.removeEventListener("scroll", position, true);
54
+ if (el.matches(":popover-open")) el.hidePopover();
55
+ };
56
+ }, [anchor, gap]);
57
+
58
+ return (
59
+ <div
60
+ ref={ref}
61
+ popover="auto"
62
+ className={cx("cb-popover", className)}
63
+ onToggle={(event: ToggleEvent<HTMLDivElement>) => {
64
+ if (event.newState === "closed") onClose();
65
+ }}
66
+ >
67
+ {children}
68
+ </div>
69
+ );
70
+ }
@@ -0,0 +1,198 @@
1
+ "use client";
2
+
3
+ import { useId, useImperativeHandle, useMemo, useRef, type Ref } from "react";
4
+ import type { ComboboxOption, OptionValue } from "../core/types";
5
+ import { defaultComboboxText } from "./locale";
6
+ import { ChevronIcon, XIcon } from "./icons";
7
+ import { Listbox } from "./Listbox";
8
+ import { ComboboxPopover } from "./Popover";
9
+ import { cx, type ComboboxHandle, type ComboboxSharedProps } from "./props";
10
+ import { useCombobox } from "./useCombobox";
11
+
12
+ export interface SelectProps<V extends OptionValue = string> extends ComboboxSharedProps<V> {
13
+ value?: V | null;
14
+ defaultValue?: V | null;
15
+ onChange?(value: NoInfer<V> | null, option: ComboboxOption<NoInfer<V>> | null): void;
16
+ /** Keep the list open after choosing. Default false. */
17
+ closeOnSelect?: boolean;
18
+ ref?: Ref<ComboboxHandle<V>>;
19
+ }
20
+
21
+ /** Single-select combobox: a value picker with option search. */
22
+ export function Select<V extends OptionValue = string>(props: SelectProps<V>) {
23
+ const {
24
+ ref,
25
+ value,
26
+ defaultValue,
27
+ onChange,
28
+ closeOnSelect = true,
29
+ options,
30
+ label,
31
+ placeholder = "Select…",
32
+ description,
33
+ error,
34
+ required = false,
35
+ disabled = false,
36
+ clearable = true,
37
+ size = "md",
38
+ name,
39
+ id: idProp,
40
+ className,
41
+ classNames,
42
+ style,
43
+ localeText,
44
+ maxHeight = 280,
45
+ virtualize = true,
46
+ emptyMessage,
47
+ searchable = true,
48
+ loading = false,
49
+ hasMore,
50
+ onLoadMore,
51
+ renderOption,
52
+ } = props;
53
+
54
+ const reactId = useId();
55
+ const id = idProp ?? reactId;
56
+ const text = useMemo(() => ({ ...defaultComboboxText, ...localeText }), [localeText]);
57
+
58
+ const controlRef = useRef<HTMLDivElement>(null);
59
+ const searchRef = useRef<HTMLInputElement>(null);
60
+ const listRef = useRef<HTMLDivElement>(null);
61
+
62
+ const combobox = useCombobox<V>({
63
+ controlRef,
64
+ searchRef,
65
+ listRef,
66
+ ...props,
67
+ multiple: false,
68
+ closeOnSelect,
69
+ value: value === undefined ? undefined : value === null ? [] : [value],
70
+ defaultValue: defaultValue === null || defaultValue === undefined ? [] : [defaultValue],
71
+ onValuesChange: (values, selectedOptions) =>
72
+ onChange?.(values[0] ?? null, selectedOptions[0] ?? null),
73
+ text,
74
+ id,
75
+ });
76
+
77
+ const selected = combobox.selectedOptions[0];
78
+
79
+ useImperativeHandle(ref, () => combobox.handle, [combobox.handle]);
80
+
81
+ return (
82
+ <div
83
+ className={cx("cb-root", classNames?.root, className)}
84
+ style={style}
85
+ data-size={size}
86
+ data-state={combobox.open ? "open" : "closed"}
87
+ data-invalid={error ? "" : undefined}
88
+ >
89
+ {label && (
90
+ <span id={`${id}-label`} className={cx("cb-label", classNames?.label)} data-required={required || undefined}>
91
+ {label}
92
+ </span>
93
+ )}
94
+
95
+ <div
96
+ ref={controlRef}
97
+ id={id}
98
+ role="combobox"
99
+ tabIndex={disabled ? -1 : 0}
100
+ aria-expanded={combobox.open}
101
+ aria-haspopup="listbox"
102
+ aria-controls={combobox.open ? combobox.listboxId : undefined}
103
+ aria-labelledby={label ? `${id}-label` : undefined}
104
+ aria-describedby={cx(description ? `${id}-description` : "", error ? `${id}-error` : "") || undefined}
105
+ aria-required={required || undefined}
106
+ aria-invalid={error ? true : undefined}
107
+ aria-disabled={disabled || undefined}
108
+ className={cx("cb-control", classNames?.control)}
109
+ data-disabled={disabled || undefined}
110
+ data-invalid={error ? "" : undefined}
111
+ onPointerDown={combobox.onControlPointerDown}
112
+ onClick={combobox.onControlClick}
113
+ onKeyDown={combobox.onKeyDown}
114
+ >
115
+ <span className={cx("cb-value", classNames?.value)}>
116
+ {selected ? (
117
+ <>
118
+ {selected.icon && <span className="cb-value-icon">{selected.icon}</span>}
119
+ {selected.label}
120
+ </>
121
+ ) : (
122
+ <span className="cb-placeholder">{placeholder}</span>
123
+ )}
124
+ </span>
125
+
126
+ <span className="cb-actions">
127
+ {clearable && selected && !disabled && (
128
+ <button
129
+ type="button"
130
+ tabIndex={-1}
131
+ className="cb-icon-button"
132
+ aria-label={text.clear}
133
+ onClick={(event) => {
134
+ event.stopPropagation();
135
+ combobox.clear();
136
+ }}
137
+ >
138
+ <XIcon width={14} height={14} />
139
+ </button>
140
+ )}
141
+ <ChevronIcon className="cb-chevron" />
142
+ </span>
143
+ </div>
144
+
145
+ {name && <input type="hidden" name={name} value={selected?.value ?? ""} />}
146
+ {description && (
147
+ <span id={`${id}-description`} className="cb-description">
148
+ {description}
149
+ </span>
150
+ )}
151
+ {error && (
152
+ <span id={`${id}-error`} className="cb-error" role="alert">
153
+ {error}
154
+ </span>
155
+ )}
156
+
157
+ {combobox.anchor && (
158
+ <ComboboxPopover
159
+ anchor={combobox.anchor}
160
+ onClose={combobox.onPopoverClose}
161
+ className={classNames?.popover}
162
+ >
163
+ <Listbox<V>
164
+ listboxId={combobox.listboxId}
165
+ labelledBy={label ? `${id}-label` : id}
166
+ items={combobox.items}
167
+ optionCount={combobox.entries.length}
168
+ activeIndex={combobox.activeIndex}
169
+ multiple={false}
170
+ size={size}
171
+ maxHeight={maxHeight}
172
+ virtualize={virtualize}
173
+ loading={loading}
174
+ hasMore={hasMore}
175
+ searchable={searchable}
176
+ search={combobox.search}
177
+ searchRef={searchRef}
178
+ listRef={listRef}
179
+ text={text}
180
+ classNames={classNames}
181
+ emptyMessage={emptyMessage}
182
+ createLabel={combobox.createLabel}
183
+ getOptionId={combobox.getOptionId}
184
+ isSelected={combobox.isSelected}
185
+ onSelect={combobox.selectOption}
186
+ onActiveIndexChange={combobox.setActiveIndex}
187
+ onSearchChange={combobox.setSearch}
188
+ onKeyDown={combobox.onKeyDown}
189
+ onLoadMore={onLoadMore}
190
+ onCreate={combobox.commitCreate}
191
+ renderOption={renderOption}
192
+ />
193
+ </ComboboxPopover>
194
+ )}
195
+ {options.length === 0 && loading && <span className="cb-sr-only">{text.loading}</span>}
196
+ </div>
197
+ );
198
+ }
@@ -0,0 +1,34 @@
1
+ import type { SVGProps } from "react";
2
+
3
+ type IconProps = SVGProps<SVGSVGElement>;
4
+
5
+ function Svg(props: IconProps) {
6
+ return (
7
+ <svg
8
+ width="16"
9
+ height="16"
10
+ viewBox="0 0 24 24"
11
+ fill="none"
12
+ stroke="currentColor"
13
+ strokeWidth="2"
14
+ strokeLinecap="round"
15
+ strokeLinejoin="round"
16
+ aria-hidden="true"
17
+ focusable="false"
18
+ {...props}
19
+ />
20
+ );
21
+ }
22
+
23
+ export const SearchIcon = (p: IconProps) => (
24
+ <Svg {...p}><circle cx="11" cy="11" r="7" /><path d="m20 20-3.5-3.5" /></Svg>
25
+ );
26
+ export const CheckIcon = (p: IconProps) => <Svg {...p}><path d="M20 6 9 17l-5-5" /></Svg>;
27
+ export const ChevronIcon = (p: IconProps) => <Svg {...p}><path d="m6 9 6 6 6-6" /></Svg>;
28
+ export const XIcon = (p: IconProps) => <Svg {...p}><path d="M18 6 6 18M6 6l12 12" /></Svg>;
29
+ export const PlusIcon = (p: IconProps) => <Svg {...p}><path d="M12 5v14M5 12h14" /></Svg>;
30
+ export const SpinnerIcon = (p: IconProps) => (
31
+ <Svg {...p} className={["cb-spinner", p.className].filter(Boolean).join(" ")}>
32
+ <path d="M12 3a9 9 0 1 0 9 9" />
33
+ </Svg>
34
+ );
@@ -0,0 +1,16 @@
1
+ import type { ComboboxLocaleText } from "../core/types";
2
+
3
+ export const defaultComboboxText: ComboboxLocaleText = {
4
+ searchPlaceholder: "Search…",
5
+ noResults: "No options found",
6
+ loading: "Loading…",
7
+ loadMore: "Load more",
8
+ clear: "Clear",
9
+ clearAll: "Clear all",
10
+ selectAll: "Select all",
11
+ createOption: (label) => `Create “${label}”`,
12
+ selectedCount: (count) => `${count} selected`,
13
+ moreCount: (count) => `+${count} more`,
14
+ removeOption: (label) => `Remove ${label}`,
15
+ maxReached: (max) => `Maximum ${max} selected`,
16
+ };
@@ -0,0 +1,86 @@
1
+ import type { CSSProperties, ReactNode } from "react";
2
+ import type {
3
+ ComboboxLocaleText,
4
+ ComboboxOption,
5
+ ComboboxSize,
6
+ OptionValue,
7
+ } from "../core/types";
8
+
9
+ export type ComboboxSlot =
10
+ | "root"
11
+ | "label"
12
+ | "control"
13
+ | "value"
14
+ | "tag"
15
+ | "popover"
16
+ | "search"
17
+ | "list"
18
+ | "option"
19
+ | "footer";
20
+
21
+ export interface ComboboxHandle<V extends OptionValue = string> {
22
+ open(): void;
23
+ close(): void;
24
+ toggle(): void;
25
+ focus(): void;
26
+ clear(): void;
27
+ getValue(): V[];
28
+ getSelectedOptions(): ComboboxOption<V>[];
29
+ }
30
+
31
+ export interface OptionState {
32
+ selected: boolean;
33
+ active: boolean;
34
+ }
35
+
36
+ export interface ComboboxSharedProps<V extends OptionValue = string> {
37
+ options: readonly ComboboxOption<V>[];
38
+ /** `client` filters `options` locally; `server` expects `options` to be the current results. */
39
+ mode?: "client" | "server";
40
+ /** Shows a loading row; keeps current options visible. */
41
+ loading?: boolean;
42
+ /** Called with the search text (debounced in server mode), and on open. */
43
+ onSearchChange?(search: string): void;
44
+ /** Debounce for `onSearchChange` in server mode. Default 250 ms. */
45
+ searchDebounce?: number;
46
+ /** Show the search box. Default true. When false, typing jumps to a matching option. */
47
+ searchable?: boolean;
48
+ /** More results are available; the list calls `onLoadMore` when scrolled near the end. */
49
+ hasMore?: boolean;
50
+ onLoadMore?(): void;
51
+ /**
52
+ * Replaces the built-in search matching (client mode).
53
+ * `NoInfer` keeps the option type coming from `options` and `value` only.
54
+ */
55
+ filterFn?(option: ComboboxOption<NoInfer<V>>, search: string): boolean;
56
+ renderOption?(option: ComboboxOption<NoInfer<V>>, state: OptionState): ReactNode;
57
+ /** Offers "Create …" when the search matches no option. */
58
+ allowCreate?: boolean;
59
+ onCreate?(label: string): void;
60
+ label?: ReactNode;
61
+ placeholder?: string;
62
+ description?: ReactNode;
63
+ /** Message shown below the control; also marks the control invalid. */
64
+ error?: ReactNode;
65
+ required?: boolean;
66
+ disabled?: boolean;
67
+ /** Show the clear button. Default true. */
68
+ clearable?: boolean;
69
+ size?: ComboboxSize;
70
+ /** Name for the hidden form input. */
71
+ name?: string;
72
+ id?: string;
73
+ /** Max height of the option list in pixels. Default 280. */
74
+ maxHeight?: number;
75
+ /** `true` / `false` to force, or a count above which virtualization turns on (default 80). */
76
+ virtualize?: boolean | number;
77
+ emptyMessage?: ReactNode;
78
+ className?: string;
79
+ classNames?: Partial<Record<ComboboxSlot, string>>;
80
+ style?: CSSProperties;
81
+ localeText?: Partial<ComboboxLocaleText>;
82
+ onOpenChange?(open: boolean): void;
83
+ }
84
+
85
+ export const cx = (...names: (string | false | null | undefined)[]) =>
86
+ names.filter(Boolean).join(" ");