@gntik-ai/blocks 0.6.0 → 0.8.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.
Files changed (58) hide show
  1. package/dist/index.d.ts +202 -74
  2. package/dist/index.js +849 -283
  3. package/dist/index.js.map +1 -1
  4. package/package.json +2 -2
  5. package/src/feedback/ConfirmDestructive/ConfirmDestructive.examples.test.tsx +25 -0
  6. package/src/feedback/ConfirmDestructive/ConfirmDestructive.extended.test.tsx +252 -0
  7. package/src/feedback/ConfirmDestructive/ConfirmDestructive.focus.test.tsx +178 -0
  8. package/src/feedback/ConfirmDestructive/ConfirmDestructive.tsx +219 -28
  9. package/src/feedback/ConfirmDestructive/block.meta.ts +2 -2
  10. package/src/feedback/ConfirmDestructive/examples/FailureStates.tsx +39 -0
  11. package/src/feedback/ConfirmDestructive/examples/PlainDeletion.tsx +6 -0
  12. package/src/feedback/ConfirmDestructive/examples/SecretRevocation.tsx +34 -0
  13. package/src/feedback/EmptyStates/EmptyStates.test.tsx +11 -1
  14. package/src/feedback/EmptyStates/EmptyStates.tsx +31 -1
  15. package/src/feedback/EmptyStates/block.meta.ts +1 -1
  16. package/src/feedback/EmptyStates/fixtures.ts +5 -0
  17. package/src/feedback/ErrorPanel/ErrorPanel.test.tsx +7 -0
  18. package/src/feedback/ErrorPanel/ErrorPanel.tsx +8 -3
  19. package/src/forms/CredentialDisclosureDialog/CredentialDisclosureDialog.additional.test.tsx +467 -0
  20. package/src/forms/CredentialDisclosureDialog/CredentialDisclosureDialog.tsx +175 -106
  21. package/src/forms/CredentialDisclosureDialog/IssueOnClick.test.tsx +56 -0
  22. package/src/forms/CredentialDisclosureDialog/SecretCopy.tsx +83 -0
  23. package/src/forms/CredentialDisclosureDialog/block.meta.ts +1 -1
  24. package/src/forms/CredentialDisclosureDialog/examples/Inline.tsx +19 -0
  25. package/src/forms/CredentialDisclosureDialog/examples/IssueOnClick.tsx +87 -0
  26. package/src/forms/CredentialDisclosureDialog/examples/MultiSecret.tsx +29 -0
  27. package/src/forms/CredentialDisclosureDialog/focus.ts +18 -0
  28. package/src/forms/CredentialDisclosureDialog/types.ts +5 -0
  29. package/src/tables/CursorPagerFooter/CursorPagerFooter.examples.test.tsx +33 -0
  30. package/src/tables/CursorPagerFooter/CursorPagerFooter.test.tsx +51 -0
  31. package/src/tables/CursorPagerFooter/CursorPagerFooter.tsx +10 -0
  32. package/src/tables/CursorPagerFooter/block.meta.ts +23 -0
  33. package/src/tables/CursorPagerFooter/examples/Busy.tsx +7 -0
  34. package/src/tables/CursorPagerFooter/examples/CursorStack.tsx +40 -0
  35. package/src/tables/CursorPagerFooter/examples/Hidden.tsx +16 -0
  36. package/src/tables/CursorPagerFooter/examples/RowsPerPage.tsx +21 -0
  37. package/src/tables/CursorPagerFooter/fixtures.ts +13 -0
  38. package/src/tables/DataTable/DataTable.server-pagination.test.tsx +173 -0
  39. package/src/tables/DataTable/DataTable.tsx +20 -12
  40. package/src/tables/DataTable/block.meta.ts +2 -1
  41. package/src/tables/DataTable/data-table-props.ts +27 -4
  42. package/src/tables/DataTable/examples/server-pagination.tsx +68 -0
  43. package/src/tables/LoadMoreFooter/LoadMoreFooter.announcements.test.tsx +106 -0
  44. package/src/tables/LoadMoreFooter/LoadMoreFooter.examples.test.tsx +36 -0
  45. package/src/tables/LoadMoreFooter/LoadMoreFooter.labels.test.tsx +71 -0
  46. package/src/tables/LoadMoreFooter/LoadMoreFooter.test.tsx +184 -0
  47. package/src/tables/LoadMoreFooter/LoadMoreFooter.tsx +151 -0
  48. package/src/tables/LoadMoreFooter/block.meta.ts +28 -0
  49. package/src/tables/LoadMoreFooter/examples/cards.tsx +22 -0
  50. package/src/tables/LoadMoreFooter/examples/cursor.tsx +25 -0
  51. package/src/tables/LoadMoreFooter/examples/exhausted.tsx +6 -0
  52. package/src/tables/LoadMoreFooter/examples/failed.tsx +13 -0
  53. package/src/tables/LoadMoreFooter/examples/flag.tsx +33 -0
  54. package/src/tables/LoadMoreFooter/examples/useMockCursor.ts +30 -0
  55. package/src/tables/LoadMoreFooter/fixtures.ts +9 -0
  56. package/src/tables/LoadMoreFooter/useLoadMoreFooter.ts +56 -0
  57. package/src/tables/index.ts +4 -0
  58. package/src/test/CredentialDisclosureDialog.compatibility.tsx +30 -0
@@ -0,0 +1,33 @@
1
+ import { useState } from 'react';
2
+ import { LoadMoreFooter } from '../LoadMoreFooter';
3
+ import { LOAD_MORE_RECORDS } from '../fixtures';
4
+
5
+ export default function FlagExample() {
6
+ const [state, setState] = useState({ rows: LOAD_MORE_RECORDS.slice(0, 2), hasMore: true, loading: false, error: '' });
7
+ const [lastAppend, setLastAppend] = useState<{ seq: number; count: number }>();
8
+ const load = async () => {
9
+ setState((previous) => ({ ...previous, loading: true }));
10
+ const batch = await Promise.resolve(LOAD_MORE_RECORDS.slice(2));
11
+ // Same-tick updates are batched: settle loading=false together with the result. Split settles are unsupported.
12
+ setState((previous) => ({ rows: [...previous.rows, ...batch], hasMore: false, loading: false, error: '' }));
13
+ setLastAppend({ seq: 1, count: batch.length });
14
+ };
15
+ return (
16
+ <div className="space-y-4">
17
+ <ul>
18
+ {state.rows.map((row) => (
19
+ <li key={row.id}>{row.name}</li>
20
+ ))}
21
+ </ul>
22
+ <LoadMoreFooter
23
+ hasMore={state.hasMore}
24
+ loading={state.loading}
25
+ error={state.error}
26
+ loadedCount={state.rows.length}
27
+ lastAppend={lastAppend}
28
+ onLoadMore={load}
29
+ exhaustedFocusTarget="root"
30
+ />
31
+ </div>
32
+ );
33
+ }
@@ -0,0 +1,30 @@
1
+ import { useState } from 'react';
2
+ import { LOAD_MORE_RECORDS } from '../fixtures';
3
+
4
+ /** Mock consumer: real consumers normalize cursors and preserve their filters here. */
5
+ export function useMockCursor() {
6
+ const [state, setState] = useState({
7
+ rows: LOAD_MORE_RECORDS.slice(0, 2),
8
+ nextCursor: 'c1' as string | null,
9
+ loading: false,
10
+ error: '',
11
+ lastAppend: undefined as { seq: number; count: number } | undefined,
12
+ });
13
+ const load = async (cursor?: string) => {
14
+ setState((previous) => ({ ...previous, loading: true }));
15
+ try {
16
+ const batch = await Promise.resolve(cursor === 'c1' ? LOAD_MORE_RECORDS.slice(2) : []);
17
+ // Settle loading=false and the result together in one update. Split settles are unsupported.
18
+ setState((previous) => ({
19
+ rows: [...previous.rows, ...batch],
20
+ nextCursor: null,
21
+ loading: false,
22
+ error: '',
23
+ lastAppend: { seq: (previous.lastAppend?.seq ?? 0) + 1, count: batch.length },
24
+ }));
25
+ } catch {
26
+ setState((previous) => ({ ...previous, loading: false, error: 'Could not load more items.' }));
27
+ }
28
+ };
29
+ return { ...state, load };
30
+ }
@@ -0,0 +1,9 @@
1
+ /** Meaningful defaults for the no-props catalogue preview. */
2
+ export const LOAD_MORE_PREVIEW = { nextCursor: 'page-2', loadedCount: 20 } as const;
3
+
4
+ export const LOAD_MORE_RECORDS = [
5
+ { id: 'r1', name: 'Record 1', status: 'Ready' },
6
+ { id: 'r2', name: 'Record 2', status: 'Ready' },
7
+ { id: 'r3', name: 'Record 3', status: 'Ready' },
8
+ { id: 'r4', name: 'Record 4', status: 'Ready' },
9
+ ];
@@ -0,0 +1,56 @@
1
+ import { useAnnounce } from '@gntik-ai/ui';
2
+ import { useCallback, useEffect, useLayoutEffect, useRef } from 'react';
3
+
4
+ /** Capture focus before React removes the control, then restore it after the replacement commits. */
5
+ export function useLoadMoreFooter(
6
+ control: 'button' | 'retry' | 'none',
7
+ target: 'summary' | 'root',
8
+ loading: boolean,
9
+ error: string,
10
+ ) {
11
+ const rootRef = useRef<HTMLDivElement>(null);
12
+ const summaryRef = useRef<HTMLParagraphElement>(null);
13
+ const currentControl = useRef<HTMLElement | null>(null);
14
+ const moveFocus = useRef(false);
15
+ const inFlightCursorRef = useRef<string | undefined>(undefined);
16
+ useEffect(() => {
17
+ // A successful settle releases the cursor; later external failures use nextCursor.
18
+ if (!loading && !error) inFlightCursorRef.current = undefined;
19
+ }, [loading, error]);
20
+ const controlRef = useCallback((node: HTMLElement | null) => {
21
+ if (!node && currentControl.current?.contains(document.activeElement)) moveFocus.current = true;
22
+ currentControl.current = node;
23
+ }, []);
24
+
25
+ useLayoutEffect(() => {
26
+ if (!moveFocus.current) return;
27
+ moveFocus.current = false;
28
+ const nextControl = currentControl.current;
29
+ const next = nextControl?.matches('button') ? nextControl : nextControl?.querySelector('button');
30
+ (next ?? (target === 'root' ? rootRef.current : summaryRef.current))?.focus();
31
+ }, [control, target]);
32
+
33
+ return { rootRef, summaryRef, controlRef, inFlightCursorRef };
34
+ }
35
+
36
+ /** Track committed results; mounting and intermediate loading states remain silent. */
37
+ export function useLoadMoreAnnouncements(
38
+ loading: boolean,
39
+ error: string,
40
+ lastAppend: { seq: number; count: number } | undefined,
41
+ loadedAnnouncement: (count: number) => string,
42
+ ) {
43
+ const announce = useAnnounce();
44
+ const previous = useRef({ loading, error });
45
+ const lastSeq = useRef(lastAppend?.seq);
46
+ useEffect(() => {
47
+ const before = previous.current;
48
+ previous.current = { loading, error };
49
+ if (!loading && error && (before.loading || before.error !== error)) announce(error, 'assertive');
50
+ }, [loading, error, announce]);
51
+ useEffect(() => {
52
+ if (!lastAppend || (lastSeq.current !== undefined && lastAppend.seq <= lastSeq.current)) return;
53
+ lastSeq.current = lastAppend.seq;
54
+ if (lastAppend.count > 0) announce(loadedAnnouncement(lastAppend.count), 'polite');
55
+ }, [lastAppend, loadedAnnouncement, announce]);
56
+ }
@@ -35,7 +35,11 @@ export * from './FilterBar/FilterBar';
35
35
  export { meta as filterBarMeta } from './FilterBar/block.meta';
36
36
  export { DEPLOYMENT_DEFAULT_FILTERS, DEPLOYMENT_FILTER_FIELDS, DEPLOYMENT_SAVED_VIEWS } from './FilterBar/fixtures';
37
37
  export * from './PaginationFooter/PaginationFooter';
38
+ export * from './CursorPagerFooter/CursorPagerFooter';
39
+ export { meta as cursorPagerFooterMeta } from './CursorPagerFooter/block.meta';
38
40
  export { meta as paginationFooterMeta } from './PaginationFooter/block.meta';
41
+ export * from './LoadMoreFooter/LoadMoreFooter';
42
+ export { meta as loadMoreFooterMeta } from './LoadMoreFooter/block.meta';
39
43
  export * from './TruncatedCollectionFooter/TruncatedCollectionFooter';
40
44
  export { meta as truncatedCollectionFooterMeta } from './TruncatedCollectionFooter/block.meta';
41
45
  export * from './InlineEditRow/InlineEditRow';
@@ -0,0 +1,30 @@
1
+ import {
2
+ CredentialDisclosureDialog,
3
+ type CredentialDisclosureDialogProps,
4
+ } from '../forms/CredentialDisclosureDialog/CredentialDisclosureDialog';
5
+ import { storedDisclosure } from '../forms/CredentialDisclosureDialog/fixtures';
6
+
7
+ /** Compile-only consumer fixture for every pre-amendment prop. */
8
+ export function LegacyCredentialDisclosure() {
9
+ const props: CredentialDisclosureDialogProps = {
10
+ disclosure: storedDisclosure,
11
+ onClose: () => {},
12
+ resolveReturnFocus: () => null,
13
+ fallbackFocus: () => null,
14
+ title: 'Credential',
15
+ description: 'Keep this value safe.',
16
+ credentialIdLabel: 'Identifier',
17
+ expiryLabel: 'Expiry',
18
+ secretLabel: 'Secret',
19
+ showLabel: 'Show',
20
+ hideLabel: 'Hide',
21
+ copyLabel: 'Copy',
22
+ copiedLabel: 'Copied',
23
+ successAnnouncement: 'Copied safely',
24
+ failureAnnouncement: 'Copy manually',
25
+ warningTitle: 'Save this value',
26
+ warningText: 'Shown once',
27
+ closeLabel: 'Close',
28
+ };
29
+ return <CredentialDisclosureDialog {...props} />;
30
+ }