@chromvoid/headless-ui 0.1.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 (191) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -0
  3. package/dist/a11y-contracts/index.d.ts +23 -0
  4. package/dist/a11y-contracts/index.js +1 -0
  5. package/dist/accordion/index.d.ts +78 -0
  6. package/dist/accordion/index.js +264 -0
  7. package/dist/adapters/index.d.ts +9 -0
  8. package/dist/adapters/index.js +1 -0
  9. package/dist/alert/index.d.ts +33 -0
  10. package/dist/alert/index.js +54 -0
  11. package/dist/alert-dialog/index.d.ts +69 -0
  12. package/dist/alert-dialog/index.js +94 -0
  13. package/dist/badge/index.d.ts +48 -0
  14. package/dist/badge/index.js +89 -0
  15. package/dist/breadcrumb/index.d.ts +55 -0
  16. package/dist/breadcrumb/index.js +77 -0
  17. package/dist/button/index.d.ts +46 -0
  18. package/dist/button/index.js +86 -0
  19. package/dist/callout/index.d.ts +41 -0
  20. package/dist/callout/index.js +63 -0
  21. package/dist/card/index.d.ts +54 -0
  22. package/dist/card/index.js +103 -0
  23. package/dist/carousel/index.d.ts +98 -0
  24. package/dist/carousel/index.js +243 -0
  25. package/dist/checkbox/index.d.ts +50 -0
  26. package/dist/checkbox/index.js +87 -0
  27. package/dist/combobox/index.d.ts +114 -0
  28. package/dist/combobox/index.js +431 -0
  29. package/dist/command-palette/index.d.ts +73 -0
  30. package/dist/command-palette/index.js +147 -0
  31. package/dist/context-menu/index.d.ts +111 -0
  32. package/dist/context-menu/index.js +372 -0
  33. package/dist/copy-button/index.d.ts +62 -0
  34. package/dist/copy-button/index.js +183 -0
  35. package/dist/core/index.d.ts +20 -0
  36. package/dist/core/index.js +2 -0
  37. package/dist/core/selection.d.ts +5 -0
  38. package/dist/core/selection.js +39 -0
  39. package/dist/core/value-range.d.ts +49 -0
  40. package/dist/core/value-range.js +134 -0
  41. package/dist/date-picker/index.d.ts +210 -0
  42. package/dist/date-picker/index.js +895 -0
  43. package/dist/dialog/index.d.ts +95 -0
  44. package/dist/dialog/index.js +153 -0
  45. package/dist/disclosure/index.d.ts +52 -0
  46. package/dist/disclosure/index.js +159 -0
  47. package/dist/drawer/index.d.ts +30 -0
  48. package/dist/drawer/index.js +39 -0
  49. package/dist/feed/index.d.ts +77 -0
  50. package/dist/feed/index.js +260 -0
  51. package/dist/grid/index.d.ts +103 -0
  52. package/dist/grid/index.js +415 -0
  53. package/dist/index.d.ts +51 -0
  54. package/dist/index.js +51 -0
  55. package/dist/input/index.d.ts +86 -0
  56. package/dist/input/index.js +156 -0
  57. package/dist/interactions/composite-navigation.d.ts +69 -0
  58. package/dist/interactions/composite-navigation.js +169 -0
  59. package/dist/interactions/index.d.ts +15 -0
  60. package/dist/interactions/index.js +4 -0
  61. package/dist/interactions/keyboard-intents.d.ts +16 -0
  62. package/dist/interactions/keyboard-intents.js +33 -0
  63. package/dist/interactions/overlay-focus.d.ts +40 -0
  64. package/dist/interactions/overlay-focus.js +93 -0
  65. package/dist/interactions/typeahead.d.ts +20 -0
  66. package/dist/interactions/typeahead.js +41 -0
  67. package/dist/landmarks/index.d.ts +39 -0
  68. package/dist/landmarks/index.js +58 -0
  69. package/dist/link/index.d.ts +34 -0
  70. package/dist/link/index.js +39 -0
  71. package/dist/listbox/index.d.ts +92 -0
  72. package/dist/listbox/index.js +337 -0
  73. package/dist/menu/index.d.ts +132 -0
  74. package/dist/menu/index.js +541 -0
  75. package/dist/menu-button/index.d.ts +71 -0
  76. package/dist/menu-button/index.js +121 -0
  77. package/dist/meter/index.d.ts +45 -0
  78. package/dist/meter/index.js +106 -0
  79. package/dist/number/index.d.ts +113 -0
  80. package/dist/number/index.js +252 -0
  81. package/dist/popover/index.d.ts +70 -0
  82. package/dist/popover/index.js +126 -0
  83. package/dist/progress/index.d.ts +49 -0
  84. package/dist/progress/index.js +79 -0
  85. package/dist/radio-group/index.d.ts +61 -0
  86. package/dist/radio-group/index.js +150 -0
  87. package/dist/select/index.d.ts +92 -0
  88. package/dist/select/index.js +239 -0
  89. package/dist/sidebar/index.d.ts +74 -0
  90. package/dist/sidebar/index.js +186 -0
  91. package/dist/slider/index.d.ts +61 -0
  92. package/dist/slider/index.js +150 -0
  93. package/dist/slider-multi-thumb/index.d.ts +70 -0
  94. package/dist/slider-multi-thumb/index.js +222 -0
  95. package/dist/spinbutton/index.d.ts +75 -0
  96. package/dist/spinbutton/index.js +214 -0
  97. package/dist/spinner/index.d.ts +1 -0
  98. package/dist/spinner/index.js +1 -0
  99. package/dist/spinner/spinner.d.ts +23 -0
  100. package/dist/spinner/spinner.js +25 -0
  101. package/dist/switch/index.d.ts +40 -0
  102. package/dist/switch/index.js +61 -0
  103. package/dist/table/index.d.ts +117 -0
  104. package/dist/table/index.js +377 -0
  105. package/dist/tabs/index.d.ts +63 -0
  106. package/dist/tabs/index.js +174 -0
  107. package/dist/textarea/index.d.ts +68 -0
  108. package/dist/textarea/index.js +137 -0
  109. package/dist/toast/index.d.ts +67 -0
  110. package/dist/toast/index.js +145 -0
  111. package/dist/toolbar/index.d.ts +59 -0
  112. package/dist/toolbar/index.js +139 -0
  113. package/dist/tooltip/index.d.ts +52 -0
  114. package/dist/tooltip/index.js +169 -0
  115. package/dist/treegrid/index.d.ts +101 -0
  116. package/dist/treegrid/index.js +463 -0
  117. package/dist/treeview/index.d.ts +68 -0
  118. package/dist/treeview/index.js +370 -0
  119. package/dist/window-splitter/index.d.ts +65 -0
  120. package/dist/window-splitter/index.js +204 -0
  121. package/package.json +92 -0
  122. package/specs/ADR-001-headless-architecture.md +461 -0
  123. package/specs/ADR-002-repo-release-model.md +108 -0
  124. package/specs/ADR-003-public-api-versioning.md +136 -0
  125. package/specs/ADR-004-focus-selection-policy.md +117 -0
  126. package/specs/IMPLEMENTATION-ROADMAP.md +237 -0
  127. package/specs/ISSUE-BACKLOG.md +681 -0
  128. package/specs/RELEASE-CANDIDATE.md +30 -0
  129. package/specs/components/accordion.md +130 -0
  130. package/specs/components/alert-dialog.md +72 -0
  131. package/specs/components/alert.md +65 -0
  132. package/specs/components/badge.md +220 -0
  133. package/specs/components/breadcrumb.md +74 -0
  134. package/specs/components/button.md +115 -0
  135. package/specs/components/callout.md +195 -0
  136. package/specs/components/card.md +280 -0
  137. package/specs/components/carousel.md +140 -0
  138. package/specs/components/checkbox.md +172 -0
  139. package/specs/components/combobox.md +423 -0
  140. package/specs/components/command-palette.md +92 -0
  141. package/specs/components/context-menu.md +556 -0
  142. package/specs/components/copy-button.md +293 -0
  143. package/specs/components/date-picker.md +400 -0
  144. package/specs/components/dialog.md +298 -0
  145. package/specs/components/disclosure.md +257 -0
  146. package/specs/components/drawer.md +353 -0
  147. package/specs/components/feed.md +265 -0
  148. package/specs/components/grid.md +186 -0
  149. package/specs/components/input.md +254 -0
  150. package/specs/components/landmarks.md +136 -0
  151. package/specs/components/link.md +134 -0
  152. package/specs/components/listbox.md +351 -0
  153. package/specs/components/menu-button.md +76 -0
  154. package/specs/components/menu.md +623 -0
  155. package/specs/components/meter.md +149 -0
  156. package/specs/components/number.md +393 -0
  157. package/specs/components/popover.md +252 -0
  158. package/specs/components/progress.md +188 -0
  159. package/specs/components/radio-group.md +151 -0
  160. package/specs/components/select.md +144 -0
  161. package/specs/components/sidebar.md +321 -0
  162. package/specs/components/slider-multi-thumb.md +78 -0
  163. package/specs/components/slider.md +84 -0
  164. package/specs/components/spinbutton.md +140 -0
  165. package/specs/components/spinner.md +132 -0
  166. package/specs/components/switch.md +175 -0
  167. package/specs/components/table.md +403 -0
  168. package/specs/components/tabs.md +265 -0
  169. package/specs/components/textarea.md +185 -0
  170. package/specs/components/toast.md +198 -0
  171. package/specs/components/toolbar.md +278 -0
  172. package/specs/components/tooltip.md +252 -0
  173. package/specs/components/treegrid.md +281 -0
  174. package/specs/components/treeview.md +91 -0
  175. package/specs/components/window-splitter.md +297 -0
  176. package/specs/ops/git-shard-sync.md +107 -0
  177. package/specs/ops/release-checklist.md +76 -0
  178. package/specs/release/GAP-TO-GREEN-ISSUES.md +88 -0
  179. package/specs/release/api-freeze-candidate.md +54 -0
  180. package/specs/release/changelog-automation.md +76 -0
  181. package/specs/release/changelog.generated.md +53 -0
  182. package/specs/release/changelog.patch.generated.md +46 -0
  183. package/specs/release/consumer-integration.md +53 -0
  184. package/specs/release/migration-notes-pre-v1.md +40 -0
  185. package/specs/release/mvp-changelog.md +57 -0
  186. package/specs/release/release-notes-template.md +61 -0
  187. package/specs/release/release-rehearsal.md +113 -0
  188. package/specs/release/semver-deprecation-dry-run.md +89 -0
  189. package/specs/release/shard-release-drill-report.md +50 -0
  190. package/specs/release/shard-release-follow-ups.md +31 -0
  191. package/specs/signals.md +208 -0
@@ -0,0 +1,94 @@
1
+ import { action } from '@reatom/core';
2
+ import { createDialog } from '../dialog/index.js';
3
+ export function createAlertDialog(options = {}) {
4
+ const idBase = options.idBase ?? 'alert-dialog';
5
+ const cancelButtonId = `${idBase}-cancel`;
6
+ const dialog = createDialog({
7
+ idBase,
8
+ initialOpen: options.initialOpen,
9
+ isModal: true,
10
+ closeOnEscape: options.closeOnEscape,
11
+ closeOnOutsidePointer: options.closeOnOutsidePointer,
12
+ closeOnOutsideFocus: options.closeOnOutsideFocus,
13
+ initialFocusId: options.initialFocusId ?? cancelButtonId,
14
+ ariaLabelledBy: options.ariaLabelledBy,
15
+ ariaDescribedBy: options.ariaDescribedBy,
16
+ });
17
+ if (options.triggerId != null) {
18
+ dialog.actions.setTriggerId(options.triggerId);
19
+ }
20
+ const open = action(() => {
21
+ dialog.actions.open('programmatic');
22
+ }, `${idBase}.open`);
23
+ const close = action(() => {
24
+ dialog.actions.close('programmatic');
25
+ }, `${idBase}.close`);
26
+ const handleKeyDown = action((event) => {
27
+ dialog.actions.handleKeyDown(event);
28
+ }, `${idBase}.handleKeyDown`);
29
+ const handleCancel = action(() => {
30
+ options.onCancel?.();
31
+ close();
32
+ }, `${idBase}.cancel`);
33
+ const handleAction = action(() => {
34
+ options.onAction?.();
35
+ if (options.closeOnAction !== false) {
36
+ close();
37
+ }
38
+ }, `${idBase}.action`);
39
+ const actions = {
40
+ open,
41
+ close,
42
+ handleKeyDown,
43
+ };
44
+ const contracts = {
45
+ getDialogProps() {
46
+ const content = dialog.contracts.getContentProps();
47
+ const labelledBy = content['aria-labelledby'] ?? `${idBase}-title`;
48
+ const describedBy = content['aria-describedby'] ?? `${idBase}-description`;
49
+ return {
50
+ ...content,
51
+ role: 'alertdialog',
52
+ 'aria-modal': 'true',
53
+ 'aria-labelledby': labelledBy,
54
+ 'aria-describedby': describedBy,
55
+ };
56
+ },
57
+ getOverlayProps() {
58
+ return dialog.contracts.getOverlayProps();
59
+ },
60
+ getTitleProps() {
61
+ return dialog.contracts.getTitleProps();
62
+ },
63
+ getDescriptionProps() {
64
+ return dialog.contracts.getDescriptionProps();
65
+ },
66
+ getCancelButtonProps() {
67
+ return {
68
+ id: cancelButtonId,
69
+ role: 'button',
70
+ tabindex: '0',
71
+ onClick: handleCancel,
72
+ };
73
+ },
74
+ getActionButtonProps() {
75
+ return {
76
+ id: `${idBase}-action`,
77
+ role: 'button',
78
+ tabindex: '0',
79
+ onClick: handleAction,
80
+ };
81
+ },
82
+ };
83
+ const state = {
84
+ isOpen: dialog.state.isOpen,
85
+ restoreTargetId: dialog.state.restoreTargetId,
86
+ isFocusTrapped: dialog.state.isFocusTrapped,
87
+ initialFocusTargetId: dialog.state.initialFocusTargetId,
88
+ };
89
+ return {
90
+ state,
91
+ actions,
92
+ contracts,
93
+ };
94
+ }
@@ -0,0 +1,48 @@
1
+ import { type Atom, type Computed } from '@reatom/core';
2
+ export type BadgeVariant = 'primary' | 'success' | 'neutral' | 'warning' | 'danger';
3
+ export type BadgeSize = 'small' | 'medium' | 'large';
4
+ export interface CreateBadgeOptions {
5
+ variant?: BadgeVariant;
6
+ size?: BadgeSize;
7
+ dot?: boolean;
8
+ pulse?: boolean;
9
+ pill?: boolean;
10
+ isDynamic?: boolean;
11
+ isDecorative?: boolean;
12
+ ariaLabel?: string;
13
+ }
14
+ export interface BadgeState {
15
+ variant: Atom<BadgeVariant>;
16
+ size: Atom<BadgeSize>;
17
+ dot: Atom<boolean>;
18
+ pulse: Atom<boolean>;
19
+ pill: Atom<boolean>;
20
+ isDynamic: Atom<boolean>;
21
+ isDecorative: Atom<boolean>;
22
+ isEmpty: Computed<boolean>;
23
+ }
24
+ export interface BadgeActions {
25
+ setVariant(value: BadgeVariant): void;
26
+ setSize(value: BadgeSize): void;
27
+ setDot(value: boolean): void;
28
+ setPulse(value: boolean): void;
29
+ setPill(value: boolean): void;
30
+ setDynamic(value: boolean): void;
31
+ setDecorative(value: boolean): void;
32
+ }
33
+ export type BadgeProps = {
34
+ role?: 'status' | 'presentation';
35
+ 'aria-live'?: 'polite';
36
+ 'aria-atomic'?: 'true';
37
+ 'aria-hidden'?: 'true';
38
+ 'aria-label'?: string;
39
+ };
40
+ export interface BadgeContracts {
41
+ getBadgeProps(): BadgeProps;
42
+ }
43
+ export interface BadgeModel {
44
+ readonly state: BadgeState;
45
+ readonly actions: BadgeActions;
46
+ readonly contracts: BadgeContracts;
47
+ }
48
+ export declare function createBadge(options?: CreateBadgeOptions): BadgeModel;
@@ -0,0 +1,89 @@
1
+ import { action, atom, computed } from '@reatom/core';
2
+ const VALID_VARIANTS = new Set([
3
+ 'primary',
4
+ 'success',
5
+ 'neutral',
6
+ 'warning',
7
+ 'danger',
8
+ ]);
9
+ const VALID_SIZES = new Set(['small', 'medium', 'large']);
10
+ export function createBadge(options = {}) {
11
+ const initialVariant = VALID_VARIANTS.has(options.variant) ? options.variant : 'neutral';
12
+ const initialSize = VALID_SIZES.has(options.size) ? options.size : 'medium';
13
+ const variantAtom = atom(initialVariant, 'badge.variant');
14
+ const sizeAtom = atom(initialSize, 'badge.size');
15
+ const dotAtom = atom(options.dot ?? false, 'badge.dot');
16
+ const pulseAtom = atom(options.pulse ?? false, 'badge.pulse');
17
+ const pillAtom = atom(options.pill ?? false, 'badge.pill');
18
+ const isDynamicAtom = atom(options.isDynamic ?? false, 'badge.isDynamic');
19
+ const isDecorativeAtom = atom(options.isDecorative ?? false, 'badge.isDecorative');
20
+ const isEmptyAtom = computed(() => dotAtom(), 'badge.isEmpty');
21
+ const actions = {
22
+ setVariant: action((value) => {
23
+ if (VALID_VARIANTS.has(value)) {
24
+ variantAtom.set(value);
25
+ }
26
+ }, 'badge.setVariant'),
27
+ setSize: action((value) => {
28
+ if (VALID_SIZES.has(value)) {
29
+ sizeAtom.set(value);
30
+ }
31
+ }, 'badge.setSize'),
32
+ setDot: action((value) => {
33
+ dotAtom.set(value);
34
+ }, 'badge.setDot'),
35
+ setPulse: action((value) => {
36
+ pulseAtom.set(value);
37
+ }, 'badge.setPulse'),
38
+ setPill: action((value) => {
39
+ pillAtom.set(value);
40
+ }, 'badge.setPill'),
41
+ setDynamic: action((value) => {
42
+ isDynamicAtom.set(value);
43
+ }, 'badge.setDynamic'),
44
+ setDecorative: action((value) => {
45
+ isDecorativeAtom.set(value);
46
+ }, 'badge.setDecorative'),
47
+ };
48
+ const contracts = {
49
+ getBadgeProps() {
50
+ if (isDecorativeAtom()) {
51
+ return {
52
+ role: 'presentation',
53
+ 'aria-hidden': 'true',
54
+ };
55
+ }
56
+ if (isDynamicAtom()) {
57
+ const props = {
58
+ role: 'status',
59
+ 'aria-live': 'polite',
60
+ 'aria-atomic': 'true',
61
+ };
62
+ if (options.ariaLabel != null) {
63
+ props['aria-label'] = options.ariaLabel;
64
+ }
65
+ return props;
66
+ }
67
+ const props = {};
68
+ if (options.ariaLabel != null) {
69
+ props['aria-label'] = options.ariaLabel;
70
+ }
71
+ return props;
72
+ },
73
+ };
74
+ const state = {
75
+ variant: variantAtom,
76
+ size: sizeAtom,
77
+ dot: dotAtom,
78
+ pulse: pulseAtom,
79
+ pill: pillAtom,
80
+ isDynamic: isDynamicAtom,
81
+ isDecorative: isDecorativeAtom,
82
+ isEmpty: isEmptyAtom,
83
+ };
84
+ return {
85
+ state,
86
+ actions,
87
+ contracts,
88
+ };
89
+ }
@@ -0,0 +1,55 @@
1
+ import { type Atom, type Computed } from '@reatom/core';
2
+ export interface BreadcrumbItem {
3
+ id: string;
4
+ label: string;
5
+ href: string;
6
+ isCurrent?: boolean;
7
+ }
8
+ export interface BreadcrumbItemState extends BreadcrumbItem {
9
+ isCurrent: boolean;
10
+ }
11
+ export interface CreateBreadcrumbOptions {
12
+ items: readonly BreadcrumbItem[];
13
+ idBase?: string;
14
+ ariaLabel?: string;
15
+ ariaLabelledBy?: string;
16
+ }
17
+ export interface BreadcrumbState {
18
+ items: Atom<BreadcrumbItemState[]>;
19
+ currentId: Computed<string | null>;
20
+ }
21
+ export interface BreadcrumbRootProps {
22
+ role: 'navigation';
23
+ 'aria-label'?: string;
24
+ 'aria-labelledby'?: string;
25
+ }
26
+ export interface BreadcrumbListProps {
27
+ role?: undefined;
28
+ }
29
+ export interface BreadcrumbItemProps {
30
+ id: string;
31
+ role?: undefined;
32
+ 'data-current': 'true' | 'false';
33
+ }
34
+ export interface BreadcrumbLinkProps {
35
+ id: string;
36
+ role: 'link';
37
+ href: string;
38
+ 'aria-current'?: 'page';
39
+ }
40
+ export interface BreadcrumbSeparatorProps {
41
+ 'aria-hidden': 'true';
42
+ }
43
+ export interface BreadcrumbContracts {
44
+ getRootProps(): BreadcrumbRootProps;
45
+ getListProps(): BreadcrumbListProps;
46
+ getItemProps(id: string): BreadcrumbItemProps;
47
+ getLinkProps(id: string): BreadcrumbLinkProps;
48
+ getSeparatorProps(id: string): BreadcrumbSeparatorProps;
49
+ }
50
+ export interface BreadcrumbModel {
51
+ readonly state: BreadcrumbState;
52
+ readonly contracts: BreadcrumbContracts;
53
+ }
54
+ export declare const normalizeBreadcrumbItems: (items: readonly BreadcrumbItem[]) => BreadcrumbItemState[];
55
+ export declare function createBreadcrumb(options: CreateBreadcrumbOptions): BreadcrumbModel;
@@ -0,0 +1,77 @@
1
+ import { atom, computed } from '@reatom/core';
2
+ export const normalizeBreadcrumbItems = (items) => {
3
+ if (items.length === 0)
4
+ return [];
5
+ let currentIndex = -1;
6
+ items.forEach((item, index) => {
7
+ if (item.isCurrent) {
8
+ currentIndex = index;
9
+ }
10
+ });
11
+ if (currentIndex < 0) {
12
+ currentIndex = items.length - 1;
13
+ }
14
+ return items.map((item, index) => ({
15
+ ...item,
16
+ isCurrent: index === currentIndex,
17
+ }));
18
+ };
19
+ export function createBreadcrumb(options) {
20
+ const idBase = options.idBase ?? 'breadcrumb';
21
+ const itemsAtom = atom(normalizeBreadcrumbItems(options.items), `${idBase}.items`);
22
+ const currentIdAtom = computed(() => itemsAtom().find((item) => item.isCurrent)?.id ?? null, `${idBase}.currentId`);
23
+ const itemDomId = (id) => `${idBase}-item-${id}`;
24
+ const linkDomId = (id) => `${idBase}-link-${id}`;
25
+ const contracts = {
26
+ getRootProps() {
27
+ return {
28
+ role: 'navigation',
29
+ 'aria-label': options.ariaLabel ?? (options.ariaLabelledBy == null ? 'Breadcrumb' : undefined),
30
+ 'aria-labelledby': options.ariaLabelledBy,
31
+ };
32
+ },
33
+ getListProps() {
34
+ return {};
35
+ },
36
+ getItemProps(id) {
37
+ const item = itemsAtom().find((candidate) => candidate.id === id);
38
+ if (!item) {
39
+ throw new Error(`Unknown breadcrumb item id: ${id}`);
40
+ }
41
+ return {
42
+ id: itemDomId(id),
43
+ role: undefined,
44
+ 'data-current': item.isCurrent ? 'true' : 'false',
45
+ };
46
+ },
47
+ getLinkProps(id) {
48
+ const item = itemsAtom().find((candidate) => candidate.id === id);
49
+ if (!item) {
50
+ throw new Error(`Unknown breadcrumb item id for link: ${id}`);
51
+ }
52
+ return {
53
+ id: linkDomId(id),
54
+ role: 'link',
55
+ href: item.href,
56
+ 'aria-current': item.isCurrent ? 'page' : undefined,
57
+ };
58
+ },
59
+ getSeparatorProps(id) {
60
+ const item = itemsAtom().find((candidate) => candidate.id === id);
61
+ if (!item) {
62
+ throw new Error(`Unknown breadcrumb item id for separator: ${id}`);
63
+ }
64
+ return {
65
+ 'aria-hidden': 'true',
66
+ };
67
+ },
68
+ };
69
+ const state = {
70
+ items: itemsAtom,
71
+ currentId: currentIdAtom,
72
+ };
73
+ return {
74
+ state,
75
+ contracts,
76
+ };
77
+ }
@@ -0,0 +1,46 @@
1
+ import { type Atom } from '@reatom/core';
2
+ export interface CreateButtonOptions {
3
+ idBase?: string;
4
+ isDisabled?: boolean;
5
+ isLoading?: boolean;
6
+ isPressed?: boolean;
7
+ onPress?: () => void;
8
+ }
9
+ export interface ButtonState {
10
+ isDisabled: Atom<boolean>;
11
+ isLoading: Atom<boolean>;
12
+ isPressed: Atom<boolean>;
13
+ }
14
+ export interface ButtonActions {
15
+ setDisabled(next: boolean): void;
16
+ setLoading(next: boolean): void;
17
+ setPressed(next: boolean): void;
18
+ press(): void;
19
+ handleClick(): void;
20
+ handleKeyDown(event: Pick<KeyboardEvent, 'key'> & {
21
+ preventDefault?: () => void;
22
+ }): void;
23
+ handleKeyUp(event: Pick<KeyboardEvent, 'key'>): void;
24
+ }
25
+ export interface ButtonProps {
26
+ id: string;
27
+ role: 'button';
28
+ tabindex: '0' | '-1';
29
+ 'aria-disabled'?: 'true';
30
+ 'aria-busy'?: 'true';
31
+ 'aria-pressed'?: 'true' | 'false';
32
+ onClick: () => void;
33
+ onKeyDown: (event: Pick<KeyboardEvent, 'key'> & {
34
+ preventDefault?: () => void;
35
+ }) => void;
36
+ onKeyUp: (event: Pick<KeyboardEvent, 'key'>) => void;
37
+ }
38
+ export interface ButtonContracts {
39
+ getButtonProps(): ButtonProps;
40
+ }
41
+ export interface ButtonModel {
42
+ readonly state: ButtonState;
43
+ readonly actions: ButtonActions;
44
+ readonly contracts: ButtonContracts;
45
+ }
46
+ export declare function createButton(options?: CreateButtonOptions): ButtonModel;
@@ -0,0 +1,86 @@
1
+ import { action, atom } from '@reatom/core';
2
+ const isSpaceKey = (key) => key === ' ' || key === 'Spacebar';
3
+ export function createButton(options = {}) {
4
+ const idBase = options.idBase ?? 'button';
5
+ const isToggleButton = options.isPressed != null;
6
+ const isDisabledAtom = atom(options.isDisabled ?? false, `${idBase}.isDisabled`);
7
+ const isLoadingAtom = atom(options.isLoading ?? false, `${idBase}.isLoading`);
8
+ const isPressedAtom = atom(options.isPressed ?? false, `${idBase}.isPressed`);
9
+ const setDisabled = action((next) => {
10
+ isDisabledAtom.set(next);
11
+ }, `${idBase}.setDisabled`);
12
+ const setLoading = action((next) => {
13
+ isLoadingAtom.set(next);
14
+ }, `${idBase}.setLoading`);
15
+ const setPressed = action((next) => {
16
+ isPressedAtom.set(next);
17
+ }, `${idBase}.setPressed`);
18
+ const isInteractionBlocked = () => isDisabledAtom() || isLoadingAtom();
19
+ const press = action(() => {
20
+ if (isInteractionBlocked())
21
+ return;
22
+ if (isToggleButton) {
23
+ isPressedAtom.set(!isPressedAtom());
24
+ }
25
+ options.onPress?.();
26
+ }, `${idBase}.press`);
27
+ const handleClick = action(() => {
28
+ press();
29
+ }, `${idBase}.handleClick`);
30
+ const handleKeyDown = action((event) => {
31
+ if (isInteractionBlocked())
32
+ return;
33
+ if (event.key === 'Enter') {
34
+ press();
35
+ return;
36
+ }
37
+ if (isSpaceKey(event.key)) {
38
+ event.preventDefault?.();
39
+ }
40
+ }, `${idBase}.handleKeyDown`);
41
+ const handleKeyUp = action((event) => {
42
+ if (isInteractionBlocked())
43
+ return;
44
+ if (isSpaceKey(event.key)) {
45
+ press();
46
+ }
47
+ }, `${idBase}.handleKeyUp`);
48
+ const actions = {
49
+ setDisabled,
50
+ setLoading,
51
+ setPressed,
52
+ press,
53
+ handleClick,
54
+ handleKeyDown,
55
+ handleKeyUp,
56
+ };
57
+ const contracts = {
58
+ getButtonProps() {
59
+ const isDisabled = isDisabledAtom();
60
+ const isLoading = isLoadingAtom();
61
+ const isUnavailable = isDisabled || isLoading;
62
+ const isPressed = isPressedAtom();
63
+ return {
64
+ id: `${idBase}-root`,
65
+ role: 'button',
66
+ tabindex: isUnavailable ? '-1' : '0',
67
+ 'aria-disabled': isUnavailable ? 'true' : undefined,
68
+ 'aria-busy': isLoading ? 'true' : undefined,
69
+ 'aria-pressed': isToggleButton ? (isPressed ? 'true' : 'false') : undefined,
70
+ onClick: handleClick,
71
+ onKeyDown: handleKeyDown,
72
+ onKeyUp: handleKeyUp,
73
+ };
74
+ },
75
+ };
76
+ const state = {
77
+ isDisabled: isDisabledAtom,
78
+ isLoading: isLoadingAtom,
79
+ isPressed: isPressedAtom,
80
+ };
81
+ return {
82
+ state,
83
+ actions,
84
+ contracts,
85
+ };
86
+ }
@@ -0,0 +1,41 @@
1
+ import { type Atom } from '@reatom/core';
2
+ export type CalloutVariant = 'info' | 'success' | 'warning' | 'danger' | 'neutral';
3
+ export interface CreateCalloutOptions {
4
+ idBase?: string;
5
+ variant?: CalloutVariant;
6
+ closable?: boolean;
7
+ open?: boolean;
8
+ }
9
+ export interface CalloutState {
10
+ variant: Atom<CalloutVariant>;
11
+ closable: Atom<boolean>;
12
+ open: Atom<boolean>;
13
+ }
14
+ export interface CalloutActions {
15
+ setVariant(value: CalloutVariant): void;
16
+ setClosable(value: boolean): void;
17
+ close(): void;
18
+ show(): void;
19
+ }
20
+ export interface CalloutProps {
21
+ id: string;
22
+ role: 'note';
23
+ 'data-variant': CalloutVariant;
24
+ }
25
+ export interface CalloutCloseButtonProps {
26
+ id: string;
27
+ role: 'button';
28
+ tabindex: '0';
29
+ 'aria-label': 'Dismiss';
30
+ onClick: () => void;
31
+ }
32
+ export interface CalloutContracts {
33
+ getCalloutProps(): CalloutProps;
34
+ getCloseButtonProps(): CalloutCloseButtonProps;
35
+ }
36
+ export interface CalloutModel {
37
+ readonly state: CalloutState;
38
+ readonly actions: CalloutActions;
39
+ readonly contracts: CalloutContracts;
40
+ }
41
+ export declare function createCallout(options?: CreateCalloutOptions): CalloutModel;
@@ -0,0 +1,63 @@
1
+ import { action, atom } from '@reatom/core';
2
+ const VALID_VARIANTS = new Set([
3
+ 'info',
4
+ 'success',
5
+ 'warning',
6
+ 'danger',
7
+ 'neutral',
8
+ ]);
9
+ export function createCallout(options = {}) {
10
+ const idBase = options.idBase ?? 'callout';
11
+ const initialVariant = VALID_VARIANTS.has(options.variant) ? options.variant : 'info';
12
+ const variantAtom = atom(initialVariant, `${idBase}.variant`);
13
+ const closableAtom = atom(options.closable ?? false, `${idBase}.closable`);
14
+ const openAtom = atom(options.open ?? true, `${idBase}.open`);
15
+ const close = action(() => {
16
+ if (closableAtom()) {
17
+ openAtom.set(false);
18
+ }
19
+ }, `${idBase}.close`);
20
+ const show = action(() => {
21
+ openAtom.set(true);
22
+ }, `${idBase}.show`);
23
+ const actions = {
24
+ setVariant: action((value) => {
25
+ if (VALID_VARIANTS.has(value)) {
26
+ variantAtom.set(value);
27
+ }
28
+ }, `${idBase}.setVariant`),
29
+ setClosable: action((value) => {
30
+ closableAtom.set(value);
31
+ }, `${idBase}.setClosable`),
32
+ close,
33
+ show,
34
+ };
35
+ const contracts = {
36
+ getCalloutProps() {
37
+ return {
38
+ id: `${idBase}-root`,
39
+ role: 'note',
40
+ 'data-variant': variantAtom(),
41
+ };
42
+ },
43
+ getCloseButtonProps() {
44
+ return {
45
+ id: `${idBase}-close-btn`,
46
+ role: 'button',
47
+ tabindex: '0',
48
+ 'aria-label': 'Dismiss',
49
+ onClick: () => close(),
50
+ };
51
+ },
52
+ };
53
+ const state = {
54
+ variant: variantAtom,
55
+ closable: closableAtom,
56
+ open: openAtom,
57
+ };
58
+ return {
59
+ state,
60
+ actions,
61
+ contracts,
62
+ };
63
+ }
@@ -0,0 +1,54 @@
1
+ import { type Atom } from '@reatom/core';
2
+ export interface CreateCardOptions {
3
+ idBase?: string;
4
+ isExpandable?: boolean;
5
+ isExpanded?: boolean;
6
+ isDisabled?: boolean;
7
+ onExpandedChange?: (isExpanded: boolean) => void;
8
+ }
9
+ export interface CardState {
10
+ isExpandable: Atom<boolean>;
11
+ isExpanded: Atom<boolean>;
12
+ isDisabled: Atom<boolean>;
13
+ }
14
+ export interface CardActions {
15
+ toggle(): void;
16
+ expand(): void;
17
+ collapse(): void;
18
+ setDisabled(value: boolean): void;
19
+ handleClick(): void;
20
+ handleKeyDown(event: Pick<KeyboardEvent, 'key'> & {
21
+ preventDefault?: () => void;
22
+ }): void;
23
+ }
24
+ export interface CardProps {
25
+ }
26
+ export interface CardTriggerProps {
27
+ id: string;
28
+ role: 'button';
29
+ tabindex: '0' | '-1';
30
+ 'aria-expanded': 'true' | 'false';
31
+ 'aria-controls': string;
32
+ 'aria-disabled'?: 'true';
33
+ onClick: () => void;
34
+ onKeyDown: (event: Pick<KeyboardEvent, 'key'> & {
35
+ preventDefault?: () => void;
36
+ }) => void;
37
+ }
38
+ export interface CardContentProps {
39
+ id: string;
40
+ role: 'region';
41
+ 'aria-labelledby': string;
42
+ hidden: boolean;
43
+ }
44
+ export interface CardContracts {
45
+ getCardProps(): CardProps;
46
+ getTriggerProps(): CardTriggerProps | Record<string, never>;
47
+ getContentProps(): CardContentProps | Record<string, never>;
48
+ }
49
+ export interface CardModel {
50
+ readonly state: CardState;
51
+ readonly actions: CardActions;
52
+ readonly contracts: CardContracts;
53
+ }
54
+ export declare function createCard(options?: CreateCardOptions): CardModel;