@mlola-ui/registry 1.0.4 → 1.0.6
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/components/accordion.json +15 -7
- package/components/activity-heatmap.json +15 -0
- package/components/alert.json +29 -9
- package/components/app-shell.json +4 -1
- package/components/avatar.json +33 -9
- package/components/badge.json +46 -15
- package/components/breadcrumb.json +4 -3
- package/components/button.json +40 -14
- package/components/calendar.json +15 -2
- package/components/card.json +20 -7
- package/components/carousel.json +16 -4
- package/components/checkbox.json +4 -3
- package/components/color-picker.json +15 -1
- package/components/combobox.json +13 -1
- package/components/command-menu.json +4 -1
- package/components/context-menu.json +6 -2
- package/components/date-picker.json +13 -1
- package/components/dropdown-menu.json +15 -4
- package/components/dropzone.json +4 -1
- package/components/empty-state.json +16 -0
- package/components/hover-card.json +29 -2
- package/components/input.json +24 -10
- package/components/kbd.json +4 -1
- package/components/modal.json +26 -12
- package/components/number-input.json +4 -1
- package/components/otp-input.json +13 -0
- package/components/pagination.json +4 -3
- package/components/popover.json +29 -2
- package/components/priority-icon.json +15 -0
- package/components/progress.json +52 -11
- package/components/radio-group.json +13 -2
- package/components/rating.json +3 -2
- package/components/resizable.json +32 -1
- package/components/segmented-control.json +4 -3
- package/components/select.json +20 -9
- package/components/sheet.json +32 -14
- package/components/skeleton.json +3 -2
- package/components/slider.json +17 -6
- package/components/status-icon.json +16 -0
- package/components/stepper.json +14 -1
- package/components/table.json +37 -9
- package/components/tabs.json +28 -7
- package/components/tag-input.json +5 -2
- package/components/textarea.json +3 -0
- package/components/time-picker.json +4 -1
- package/components/timeline.json +37 -7
- package/components/toast.json +16 -9
- package/components/toggle.json +28 -6
- package/components/tooltip.json +15 -2
- package/components/tour.json +6 -2
- package/components/virtual-list.json +3 -0
- package/generated/catalog.ts +696 -93
- package/generated/examples.json +2538 -0
- package/index.json +1 -1
- package/package.json +1 -1
- package/snapshot/index.json +876 -225
- package/snapshot/items/components/accordion.json +15 -7
- package/snapshot/items/components/activity-heatmap.json +15 -0
- package/snapshot/items/components/alert.json +29 -9
- package/snapshot/items/components/app-shell.json +4 -1
- package/snapshot/items/components/avatar.json +33 -9
- package/snapshot/items/components/badge.json +46 -15
- package/snapshot/items/components/breadcrumb.json +4 -3
- package/snapshot/items/components/button.json +40 -14
- package/snapshot/items/components/calendar.json +15 -2
- package/snapshot/items/components/card.json +20 -7
- package/snapshot/items/components/carousel.json +16 -4
- package/snapshot/items/components/checkbox.json +4 -3
- package/snapshot/items/components/color-picker.json +15 -1
- package/snapshot/items/components/combobox.json +13 -1
- package/snapshot/items/components/command-menu.json +4 -1
- package/snapshot/items/components/context-menu.json +6 -2
- package/snapshot/items/components/date-picker.json +13 -1
- package/snapshot/items/components/dropdown-menu.json +15 -4
- package/snapshot/items/components/dropzone.json +4 -1
- package/snapshot/items/components/empty-state.json +16 -0
- package/snapshot/items/components/hover-card.json +29 -2
- package/snapshot/items/components/input.json +24 -10
- package/snapshot/items/components/kbd.json +4 -1
- package/snapshot/items/components/modal.json +26 -12
- package/snapshot/items/components/number-input.json +4 -1
- package/snapshot/items/components/otp-input.json +13 -0
- package/snapshot/items/components/pagination.json +4 -3
- package/snapshot/items/components/popover.json +29 -2
- package/snapshot/items/components/priority-icon.json +15 -0
- package/snapshot/items/components/progress.json +52 -11
- package/snapshot/items/components/radio-group.json +13 -2
- package/snapshot/items/components/rating.json +3 -2
- package/snapshot/items/components/resizable.json +32 -1
- package/snapshot/items/components/segmented-control.json +4 -3
- package/snapshot/items/components/select.json +20 -9
- package/snapshot/items/components/sheet.json +32 -14
- package/snapshot/items/components/skeleton.json +3 -2
- package/snapshot/items/components/slider.json +17 -6
- package/snapshot/items/components/status-icon.json +16 -0
- package/snapshot/items/components/stepper.json +14 -1
- package/snapshot/items/components/table.json +37 -9
- package/snapshot/items/components/tabs.json +28 -7
- package/snapshot/items/components/tag-input.json +5 -2
- package/snapshot/items/components/textarea.json +3 -0
- package/snapshot/items/components/time-picker.json +4 -1
- package/snapshot/items/components/timeline.json +37 -7
- package/snapshot/items/components/toast.json +16 -9
- package/snapshot/items/components/toggle.json +28 -6
- package/snapshot/items/components/tooltip.json +15 -2
- package/snapshot/items/components/tour.json +6 -2
- package/snapshot/items/components/virtual-list.json +3 -0
- package/snapshot/source/packages/components/_internal/anchor.ts +3 -52
- package/snapshot/source/packages/components/_internal/dialog.ts +25 -1
- package/snapshot/source/packages/components/avatar/avatar.tsx +6 -1
- package/snapshot/source/packages/components/calendar/calendar.tsx +2 -1
- package/snapshot/source/packages/components/kbd/kbd.tsx +3 -1
- package/snapshot/source/packages/components/modal/modal.tsx +5 -2
- package/snapshot/source/packages/components/resizable/resizable.tsx +2 -1
- package/snapshot/source/packages/components/table/table.tsx +28 -6
- package/snapshot/source/packages/components/tag-input/tag-input.tsx +1 -0
- package/snapshot/source-manifest.json +9 -9
|
@@ -19,6 +19,22 @@
|
|
|
19
19
|
},
|
|
20
20
|
"registryDependencies": [],
|
|
21
21
|
"engineDependencies": {},
|
|
22
|
+
"options": [
|
|
23
|
+
{
|
|
24
|
+
"component": "StatusIcon",
|
|
25
|
+
"prop": "status",
|
|
26
|
+
"values": [
|
|
27
|
+
"backlog",
|
|
28
|
+
"todo",
|
|
29
|
+
"in-progress",
|
|
30
|
+
"in-review",
|
|
31
|
+
"done",
|
|
32
|
+
"canceled"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"variants": [],
|
|
37
|
+
"sizes": [],
|
|
22
38
|
"files": [
|
|
23
39
|
{
|
|
24
40
|
"path": "packages/components/_internal/react.ts",
|
|
@@ -20,8 +20,21 @@
|
|
|
20
20
|
},
|
|
21
21
|
"registryDependencies": [],
|
|
22
22
|
"engineDependencies": {
|
|
23
|
-
"@mlola-ui/icons": "^1.0.
|
|
23
|
+
"@mlola-ui/icons": "^1.0.6"
|
|
24
24
|
},
|
|
25
|
+
"options": [
|
|
26
|
+
{
|
|
27
|
+
"component": "Stepper",
|
|
28
|
+
"prop": "orientation",
|
|
29
|
+
"values": [
|
|
30
|
+
"horizontal",
|
|
31
|
+
"vertical"
|
|
32
|
+
],
|
|
33
|
+
"default": "horizontal"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"variants": [],
|
|
37
|
+
"sizes": [],
|
|
25
38
|
"files": [
|
|
26
39
|
{
|
|
27
40
|
"path": "packages/components/_internal/react.ts",
|
|
@@ -6,14 +6,6 @@
|
|
|
6
6
|
"title": "Table",
|
|
7
7
|
"description": "A compositional data table with sortable columns, striped rows, density sizes, and footer totals",
|
|
8
8
|
"category": "data",
|
|
9
|
-
"variants": [
|
|
10
|
-
"default",
|
|
11
|
-
"striped"
|
|
12
|
-
],
|
|
13
|
-
"sizes": [
|
|
14
|
-
"sm",
|
|
15
|
-
"md"
|
|
16
|
-
],
|
|
17
9
|
"tags": [
|
|
18
10
|
"data",
|
|
19
11
|
"layout"
|
|
@@ -25,6 +17,42 @@
|
|
|
25
17
|
},
|
|
26
18
|
"registryDependencies": [],
|
|
27
19
|
"engineDependencies": {},
|
|
20
|
+
"options": [
|
|
21
|
+
{
|
|
22
|
+
"component": "Table",
|
|
23
|
+
"prop": "size",
|
|
24
|
+
"values": [
|
|
25
|
+
"sm",
|
|
26
|
+
"md"
|
|
27
|
+
],
|
|
28
|
+
"default": "md"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"component": "TableHead",
|
|
32
|
+
"prop": "align",
|
|
33
|
+
"values": [
|
|
34
|
+
"left",
|
|
35
|
+
"center",
|
|
36
|
+
"right"
|
|
37
|
+
],
|
|
38
|
+
"default": "left"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"component": "TableCell",
|
|
42
|
+
"prop": "align",
|
|
43
|
+
"values": [
|
|
44
|
+
"left",
|
|
45
|
+
"center",
|
|
46
|
+
"right"
|
|
47
|
+
],
|
|
48
|
+
"default": "left"
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"variants": [],
|
|
52
|
+
"sizes": [
|
|
53
|
+
"sm",
|
|
54
|
+
"md"
|
|
55
|
+
],
|
|
28
56
|
"files": [
|
|
29
57
|
{
|
|
30
58
|
"path": "packages/components/_internal/react.ts",
|
|
@@ -36,7 +64,7 @@
|
|
|
36
64
|
"path": "packages/components/table/table.tsx",
|
|
37
65
|
"target": "{{aliases.components}}/table.tsx",
|
|
38
66
|
"type": "registry:ui",
|
|
39
|
-
"integrity": "sha256-
|
|
67
|
+
"integrity": "sha256-xJlWzCH76Lrgg/HrlnVTHkG5dfCkbzjWXEdTVG5Jk4w="
|
|
40
68
|
}
|
|
41
69
|
],
|
|
42
70
|
"themeContract": {
|
|
@@ -6,12 +6,6 @@
|
|
|
6
6
|
"title": "Tabs",
|
|
7
7
|
"description": "Tabbed interface with underline, pills, and enclosed variants",
|
|
8
8
|
"category": "navigation",
|
|
9
|
-
"variants": [
|
|
10
|
-
"default",
|
|
11
|
-
"pills",
|
|
12
|
-
"enclosed"
|
|
13
|
-
],
|
|
14
|
-
"sizes": [],
|
|
15
9
|
"tags": [
|
|
16
10
|
"navigation",
|
|
17
11
|
"interactive",
|
|
@@ -24,8 +18,35 @@
|
|
|
24
18
|
},
|
|
25
19
|
"registryDependencies": [],
|
|
26
20
|
"engineDependencies": {
|
|
27
|
-
"@mlola-ui/behavior": "^1.0.
|
|
21
|
+
"@mlola-ui/behavior": "^1.0.6"
|
|
28
22
|
},
|
|
23
|
+
"options": [
|
|
24
|
+
{
|
|
25
|
+
"component": "Tabs",
|
|
26
|
+
"prop": "orientation",
|
|
27
|
+
"values": [
|
|
28
|
+
"horizontal",
|
|
29
|
+
"vertical"
|
|
30
|
+
],
|
|
31
|
+
"default": "horizontal"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"component": "Tabs",
|
|
35
|
+
"prop": "variant",
|
|
36
|
+
"values": [
|
|
37
|
+
"default",
|
|
38
|
+
"pills",
|
|
39
|
+
"enclosed"
|
|
40
|
+
],
|
|
41
|
+
"default": "default"
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"variants": [
|
|
45
|
+
"default",
|
|
46
|
+
"pills",
|
|
47
|
+
"enclosed"
|
|
48
|
+
],
|
|
49
|
+
"sizes": [],
|
|
29
50
|
"files": [
|
|
30
51
|
{
|
|
31
52
|
"path": "packages/components/_internal/react.ts",
|
|
@@ -23,8 +23,11 @@
|
|
|
23
23
|
"input"
|
|
24
24
|
],
|
|
25
25
|
"engineDependencies": {
|
|
26
|
-
"@mlola-ui/icons": "^1.0.
|
|
26
|
+
"@mlola-ui/icons": "^1.0.6"
|
|
27
27
|
},
|
|
28
|
+
"options": [],
|
|
29
|
+
"variants": [],
|
|
30
|
+
"sizes": [],
|
|
28
31
|
"files": [
|
|
29
32
|
{
|
|
30
33
|
"path": "packages/components/_internal/react.ts",
|
|
@@ -36,7 +39,7 @@
|
|
|
36
39
|
"path": "packages/components/tag-input/tag-input.tsx",
|
|
37
40
|
"target": "{{aliases.components}}/tag-input.tsx",
|
|
38
41
|
"type": "registry:ui",
|
|
39
|
-
"integrity": "sha256-
|
|
42
|
+
"integrity": "sha256-lc4ofz1/lUnb7B/GlFElVKXLbPwtJFAjE5vWwAY99fA="
|
|
40
43
|
}
|
|
41
44
|
],
|
|
42
45
|
"themeContract": {
|
|
@@ -6,12 +6,6 @@
|
|
|
6
6
|
"title": "Timeline",
|
|
7
7
|
"description": "A vertical event timeline with status dots, timestamps, and custom icon nodes",
|
|
8
8
|
"category": "data",
|
|
9
|
-
"variants": [
|
|
10
|
-
"complete",
|
|
11
|
-
"current",
|
|
12
|
-
"upcoming"
|
|
13
|
-
],
|
|
14
|
-
"sizes": [],
|
|
15
9
|
"tags": [
|
|
16
10
|
"data",
|
|
17
11
|
"history",
|
|
@@ -24,8 +18,44 @@
|
|
|
24
18
|
},
|
|
25
19
|
"registryDependencies": [],
|
|
26
20
|
"engineDependencies": {
|
|
27
|
-
"@mlola-ui/icons": "^1.0.
|
|
21
|
+
"@mlola-ui/icons": "^1.0.6"
|
|
28
22
|
},
|
|
23
|
+
"options": [
|
|
24
|
+
{
|
|
25
|
+
"component": "TimelineItem",
|
|
26
|
+
"prop": "status",
|
|
27
|
+
"values": [
|
|
28
|
+
"complete",
|
|
29
|
+
"current",
|
|
30
|
+
"upcoming"
|
|
31
|
+
],
|
|
32
|
+
"default": "upcoming"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"component": "TimelineItem",
|
|
36
|
+
"prop": "tone",
|
|
37
|
+
"values": [
|
|
38
|
+
"neutral",
|
|
39
|
+
"primary",
|
|
40
|
+
"info",
|
|
41
|
+
"success",
|
|
42
|
+
"warning",
|
|
43
|
+
"danger"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"component": "TimelineDot",
|
|
48
|
+
"prop": "status",
|
|
49
|
+
"values": [
|
|
50
|
+
"complete",
|
|
51
|
+
"current",
|
|
52
|
+
"upcoming"
|
|
53
|
+
],
|
|
54
|
+
"default": "upcoming"
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"variants": [],
|
|
58
|
+
"sizes": [],
|
|
29
59
|
"files": [
|
|
30
60
|
{
|
|
31
61
|
"path": "packages/components/_internal/react.ts",
|
|
@@ -6,14 +6,6 @@
|
|
|
6
6
|
"title": "Toast",
|
|
7
7
|
"description": "Toast notification system with an imperative API, auto-dismiss, and stacking",
|
|
8
8
|
"category": "feedback",
|
|
9
|
-
"variants": [
|
|
10
|
-
"default",
|
|
11
|
-
"success",
|
|
12
|
-
"warning",
|
|
13
|
-
"error",
|
|
14
|
-
"info"
|
|
15
|
-
],
|
|
16
|
-
"sizes": [],
|
|
17
9
|
"tags": [
|
|
18
10
|
"notification",
|
|
19
11
|
"feedback",
|
|
@@ -26,8 +18,23 @@
|
|
|
26
18
|
},
|
|
27
19
|
"registryDependencies": [],
|
|
28
20
|
"engineDependencies": {
|
|
29
|
-
"@mlola-ui/icons": "^1.0.
|
|
21
|
+
"@mlola-ui/icons": "^1.0.6"
|
|
30
22
|
},
|
|
23
|
+
"options": [
|
|
24
|
+
{
|
|
25
|
+
"component": "Toaster",
|
|
26
|
+
"prop": "position",
|
|
27
|
+
"values": [
|
|
28
|
+
"top-right",
|
|
29
|
+
"top-center",
|
|
30
|
+
"bottom-right",
|
|
31
|
+
"bottom-center"
|
|
32
|
+
],
|
|
33
|
+
"default": "top-right"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"variants": [],
|
|
37
|
+
"sizes": [],
|
|
31
38
|
"files": [
|
|
32
39
|
{
|
|
33
40
|
"path": "packages/components/_internal/react.ts",
|
|
@@ -6,12 +6,6 @@
|
|
|
6
6
|
"title": "Toggle",
|
|
7
7
|
"description": "iOS-style switch and button-style toggle with controlled and uncontrolled modes",
|
|
8
8
|
"category": "actions",
|
|
9
|
-
"variants": [],
|
|
10
|
-
"sizes": [
|
|
11
|
-
"sm",
|
|
12
|
-
"md",
|
|
13
|
-
"lg"
|
|
14
|
-
],
|
|
15
9
|
"tags": [
|
|
16
10
|
"form",
|
|
17
11
|
"interactive",
|
|
@@ -24,6 +18,34 @@
|
|
|
24
18
|
},
|
|
25
19
|
"registryDependencies": [],
|
|
26
20
|
"engineDependencies": {},
|
|
21
|
+
"options": [
|
|
22
|
+
{
|
|
23
|
+
"component": "Switch",
|
|
24
|
+
"prop": "size",
|
|
25
|
+
"values": [
|
|
26
|
+
"sm",
|
|
27
|
+
"md",
|
|
28
|
+
"lg"
|
|
29
|
+
],
|
|
30
|
+
"default": "md"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"component": "Toggle",
|
|
34
|
+
"prop": "size",
|
|
35
|
+
"values": [
|
|
36
|
+
"sm",
|
|
37
|
+
"md",
|
|
38
|
+
"lg"
|
|
39
|
+
],
|
|
40
|
+
"default": "md"
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"variants": [],
|
|
44
|
+
"sizes": [
|
|
45
|
+
"sm",
|
|
46
|
+
"md",
|
|
47
|
+
"lg"
|
|
48
|
+
],
|
|
27
49
|
"files": [
|
|
28
50
|
{
|
|
29
51
|
"path": "packages/components/_internal/react.ts",
|
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
"title": "Tooltip",
|
|
7
7
|
"description": "Hover and focus tooltip with four placements, delay, and arrow",
|
|
8
8
|
"category": "overlays",
|
|
9
|
-
"variants": [],
|
|
10
|
-
"sizes": [],
|
|
11
9
|
"placements": [
|
|
12
10
|
"top",
|
|
13
11
|
"bottom",
|
|
@@ -26,6 +24,21 @@
|
|
|
26
24
|
},
|
|
27
25
|
"registryDependencies": [],
|
|
28
26
|
"engineDependencies": {},
|
|
27
|
+
"options": [
|
|
28
|
+
{
|
|
29
|
+
"component": "Tooltip",
|
|
30
|
+
"prop": "placement",
|
|
31
|
+
"values": [
|
|
32
|
+
"top",
|
|
33
|
+
"bottom",
|
|
34
|
+
"left",
|
|
35
|
+
"right"
|
|
36
|
+
],
|
|
37
|
+
"default": "top"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"variants": [],
|
|
41
|
+
"sizes": [],
|
|
29
42
|
"files": [
|
|
30
43
|
{
|
|
31
44
|
"path": "packages/components/_internal/react.ts",
|
|
@@ -20,14 +20,18 @@
|
|
|
20
20
|
},
|
|
21
21
|
"registryDependencies": [],
|
|
22
22
|
"engineDependencies": {
|
|
23
|
-
"@mlola-ui/
|
|
23
|
+
"@mlola-ui/behavior": "^1.0.6",
|
|
24
|
+
"@mlola-ui/icons": "^1.0.6"
|
|
24
25
|
},
|
|
26
|
+
"options": [],
|
|
27
|
+
"variants": [],
|
|
28
|
+
"sizes": [],
|
|
25
29
|
"files": [
|
|
26
30
|
{
|
|
27
31
|
"path": "packages/components/_internal/anchor.ts",
|
|
28
32
|
"target": "{{aliases.components}}/_internal/anchor.ts",
|
|
29
33
|
"type": "registry:ui",
|
|
30
|
-
"integrity": "sha256-
|
|
34
|
+
"integrity": "sha256-OPm/4Jf4UUo8IG+/4TAKpGDHtYpvxvGwyq3/RSZQIGc="
|
|
31
35
|
},
|
|
32
36
|
{
|
|
33
37
|
"path": "packages/components/_internal/floating.ts",
|
|
@@ -1,54 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Where a floating layer goes next to its anchor
|
|
3
|
-
*
|
|
4
|
-
* along the edge to stay inside the viewport. No DOM, so it is testable.
|
|
2
|
+
* Where a floating layer goes next to its anchor. The math is shared with the
|
|
3
|
+
* framework-free runtime, so it lives in @mlola-ui/behavior/logic.
|
|
5
4
|
*/
|
|
6
|
-
export type
|
|
7
|
-
export type Align = "start" | "center" | "end";
|
|
8
|
-
|
|
9
|
-
export interface Rect {
|
|
10
|
-
x: number;
|
|
11
|
-
y: number;
|
|
12
|
-
width: number;
|
|
13
|
-
height: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface Placement {
|
|
17
|
-
x: number;
|
|
18
|
-
y: number;
|
|
19
|
-
side: Side;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const OPPOSITE: Record<Side, Side> = { top: "bottom", bottom: "top", left: "right", right: "left" };
|
|
23
|
-
|
|
24
|
-
export function placeFloating(
|
|
25
|
-
anchor: Rect,
|
|
26
|
-
floating: { width: number; height: number },
|
|
27
|
-
viewport: { width: number; height: number },
|
|
28
|
-
{ side = "bottom", align = "center", offset = 8, padding = 8 }: { side?: Side; align?: Align; offset?: number; padding?: number } = {},
|
|
29
|
-
): Placement {
|
|
30
|
-
const room: Record<Side, number> = {
|
|
31
|
-
top: anchor.y - padding,
|
|
32
|
-
bottom: viewport.height - (anchor.y + anchor.height) - padding,
|
|
33
|
-
left: anchor.x - padding,
|
|
34
|
-
right: viewport.width - (anchor.x + anchor.width) - padding,
|
|
35
|
-
};
|
|
36
|
-
const needs = (candidate: Side) => (candidate === "top" || candidate === "bottom" ? floating.height : floating.width) + offset;
|
|
37
|
-
let chosen = side;
|
|
38
|
-
if (room[side] < needs(side) && room[OPPOSITE[side]] > room[side]) chosen = OPPOSITE[side];
|
|
39
|
-
|
|
40
|
-
const vertical = chosen === "top" || chosen === "bottom";
|
|
41
|
-
let x: number;
|
|
42
|
-
let y: number;
|
|
43
|
-
if (vertical) {
|
|
44
|
-
y = chosen === "bottom" ? anchor.y + anchor.height + offset : anchor.y - floating.height - offset;
|
|
45
|
-
x = align === "start" ? anchor.x : align === "end" ? anchor.x + anchor.width - floating.width : anchor.x + anchor.width / 2 - floating.width / 2;
|
|
46
|
-
} else {
|
|
47
|
-
x = chosen === "right" ? anchor.x + anchor.width + offset : anchor.x - floating.width - offset;
|
|
48
|
-
y = align === "start" ? anchor.y : align === "end" ? anchor.y + anchor.height - floating.height : anchor.y + anchor.height / 2 - floating.height / 2;
|
|
49
|
-
}
|
|
50
|
-
// Shift along the edge to stay on screen.
|
|
51
|
-
x = Math.min(Math.max(x, padding), Math.max(padding, viewport.width - floating.width - padding));
|
|
52
|
-
y = Math.min(Math.max(y, padding), Math.max(padding, viewport.height - floating.height - padding));
|
|
53
|
-
return { x: Math.round(x), y: Math.round(y), side: chosen };
|
|
54
|
-
}
|
|
5
|
+
export { placeFloating, type Align, type Placement, type Rect, type Side } from "@mlola-ui/behavior/logic";
|
|
@@ -44,6 +44,24 @@ function hideBackground(portal: HTMLElement) {
|
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// Safari does not focus a button it clicks, so when a dialog opens with
|
|
48
|
+
// nothing focused, focus returns on close to the control just pressed.
|
|
49
|
+
let lastPressed: HTMLElement | null = null;
|
|
50
|
+
let pressedAt = 0;
|
|
51
|
+
let tracking = false;
|
|
52
|
+
function trackPresses() {
|
|
53
|
+
if (tracking) return;
|
|
54
|
+
tracking = true;
|
|
55
|
+
document.addEventListener(
|
|
56
|
+
"pointerdown",
|
|
57
|
+
(event) => {
|
|
58
|
+
lastPressed = event.target instanceof Element ? event.target.closest<HTMLElement>(FOCUSABLE) : null;
|
|
59
|
+
pressedAt = performance.now();
|
|
60
|
+
},
|
|
61
|
+
true,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
47
65
|
export function useDialogLayer({
|
|
48
66
|
open,
|
|
49
67
|
onClose,
|
|
@@ -61,6 +79,7 @@ export function useDialogLayer({
|
|
|
61
79
|
closeRef.current = onClose;
|
|
62
80
|
|
|
63
81
|
React.useEffect(() => {
|
|
82
|
+
trackPresses();
|
|
64
83
|
const node = document.createElement("div");
|
|
65
84
|
node.setAttribute("data-ml-portal", "");
|
|
66
85
|
document.body.appendChild(node);
|
|
@@ -73,8 +92,13 @@ export function useDialogLayer({
|
|
|
73
92
|
|
|
74
93
|
React.useEffect(() => {
|
|
75
94
|
if (!open || !portal) return;
|
|
95
|
+
const active = document.activeElement;
|
|
76
96
|
restoreFocusRef.current =
|
|
77
|
-
|
|
97
|
+
active instanceof HTMLElement && active !== document.body
|
|
98
|
+
? active
|
|
99
|
+
: lastPressed?.isConnected && performance.now() - pressedAt < 2000
|
|
100
|
+
? lastPressed
|
|
101
|
+
: null;
|
|
78
102
|
const releaseScroll = lockScroll();
|
|
79
103
|
const restoreBackground = hideBackground(portal);
|
|
80
104
|
const focusPanel = () => {
|
|
@@ -47,7 +47,12 @@ const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
|
|
|
47
47
|
return (
|
|
48
48
|
<div ref={ref} role="group" aria-label={`Avatar group, ${items.length} people`} className={cx("ml-avatar-group", className)} {...props}>
|
|
49
49
|
{visible.map((child, index) => <span key={index} className="ml-avatar-group-item">{child}</span>)}
|
|
50
|
-
{overflow ?
|
|
50
|
+
{overflow ? (
|
|
51
|
+
<span className="ml-avatar-overflow">
|
|
52
|
+
<span aria-hidden="true">+{overflow}</span>
|
|
53
|
+
<span className="ml-visually-hidden">{`${overflow} more ${overflow === 1 ? "person" : "people"}`}</span>
|
|
54
|
+
</span>
|
|
55
|
+
) : null}
|
|
51
56
|
</div>
|
|
52
57
|
);
|
|
53
58
|
}
|
|
@@ -257,7 +257,8 @@ export function Calendar(props: CalendarProps) {
|
|
|
257
257
|
))}
|
|
258
258
|
</div>
|
|
259
259
|
{monthGrid(first, weekStart).map((week) => (
|
|
260
|
-
|
|
260
|
+
// A week drawn only to keep months aligned has no day to read, so it is hidden whole.
|
|
261
|
+
<div key={week[0].date} role="row" className="ml-calendar-row" aria-hidden={(week.every(({ inMonth }) => !inMonth) && (!showOutsideDays || months > 1)) || undefined}>
|
|
261
262
|
{week.map(({ date, inMonth }) => {
|
|
262
263
|
if (!inMonth && (!showOutsideDays || months > 1)) return <span key={date} role="gridcell" className="ml-calendar-cell" aria-hidden="true" />;
|
|
263
264
|
const selected = isSelected(date);
|
|
@@ -43,12 +43,14 @@ export function Shortcut({ keys, className, ...props }: ShortcutProps) {
|
|
|
43
43
|
const shown = parts.map((key) => SYMBOLS[key]?.[apple ? "apple" : "other"] ?? key.toUpperCase());
|
|
44
44
|
const spoken = parts.map((key) => (apple ? SYMBOLS[key]?.spoken : SYMBOLS[key]?.spokenOther ?? SYMBOLS[key]?.spoken) ?? key.toUpperCase()).join(" ");
|
|
45
45
|
return (
|
|
46
|
-
<kbd className={cx("ml-shortcut", className)}
|
|
46
|
+
<kbd className={cx("ml-shortcut", className)} {...props}>
|
|
47
47
|
{shown.map((key, index) => (
|
|
48
48
|
<kbd key={index} className="ml-kbd" aria-hidden="true">
|
|
49
49
|
{key}
|
|
50
50
|
</kbd>
|
|
51
51
|
))}
|
|
52
|
+
{/* A kbd cannot carry a label, so the words are text a screen reader reads. */}
|
|
53
|
+
<span className="ml-visually-hidden">{spoken}</span>
|
|
52
54
|
</kbd>
|
|
53
55
|
);
|
|
54
56
|
}
|
|
@@ -12,6 +12,8 @@ export interface ModalProps {
|
|
|
12
12
|
open: boolean;
|
|
13
13
|
onClose: () => void;
|
|
14
14
|
size?: ModalSize;
|
|
15
|
+
/** "alertdialog" for a confirmation that interrupts: it is announced at once, and the backdrop does not dismiss it. */
|
|
16
|
+
role?: "dialog" | "alertdialog";
|
|
15
17
|
closeOnBackdrop?: boolean;
|
|
16
18
|
closeOnEscape?: boolean;
|
|
17
19
|
label?: string;
|
|
@@ -24,7 +26,8 @@ export function Modal({
|
|
|
24
26
|
open,
|
|
25
27
|
onClose,
|
|
26
28
|
size = "md",
|
|
27
|
-
|
|
29
|
+
role = "dialog",
|
|
30
|
+
closeOnBackdrop = role !== "alertdialog",
|
|
28
31
|
closeOnEscape = true,
|
|
29
32
|
label = "Dialog",
|
|
30
33
|
description,
|
|
@@ -45,7 +48,7 @@ export function Modal({
|
|
|
45
48
|
>
|
|
46
49
|
<div
|
|
47
50
|
ref={panelRef}
|
|
48
|
-
role=
|
|
51
|
+
role={role}
|
|
49
52
|
aria-modal="true"
|
|
50
53
|
aria-label={label}
|
|
51
54
|
aria-describedby={description ? descriptionId : undefined}
|
|
@@ -134,7 +134,8 @@ export function Resizable({
|
|
|
134
134
|
aria-valuemin={collapsible ? 0 : min}
|
|
135
135
|
aria-valuemax={max}
|
|
136
136
|
aria-valuenow={Math.round(shown)}
|
|
137
|
-
|
|
137
|
+
// A separator has no expanded state: folded is its value at 0.
|
|
138
|
+
aria-valuetext={isFolded ? "Collapsed" : undefined}
|
|
138
139
|
className="ml-resizable-handle"
|
|
139
140
|
onPointerDown={(event) => {
|
|
140
141
|
dragging.current = true;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { cx } from "../_internal/react";
|
|
4
|
+
import { composeRefs, cx } from "../_internal/react";
|
|
5
5
|
|
|
6
6
|
type TableSize = "sm" | "md";
|
|
7
7
|
type TableAlign = "left" | "center" | "right";
|
|
@@ -9,11 +9,33 @@ export type SortedState = "asc" | "desc" | false;
|
|
|
9
9
|
interface Sortable { sorted: SortedState; onToggle: () => void; label?: string; }
|
|
10
10
|
interface TableProps extends React.HTMLAttributes<HTMLDivElement> { size?: TableSize; striped?: boolean; }
|
|
11
11
|
const Table = React.forwardRef<HTMLDivElement, TableProps>(
|
|
12
|
-
({ size = "md", striped = false, className, children, ...props }, ref) =>
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
({ size = "md", striped = false, className, children, ...props }, ref) => {
|
|
13
|
+
// A table wider than its container scrolls sideways, and then the keyboard must reach it too.
|
|
14
|
+
const container = React.useRef<HTMLDivElement | null>(null);
|
|
15
|
+
const [scrolls, setScrolls] = React.useState(false);
|
|
16
|
+
React.useEffect(() => {
|
|
17
|
+
const element = container.current;
|
|
18
|
+
if (!element || typeof ResizeObserver === "undefined") return;
|
|
19
|
+
const measure = () => setScrolls(element.scrollWidth > element.clientWidth + 1);
|
|
20
|
+
const observer = new ResizeObserver(measure);
|
|
21
|
+
observer.observe(element);
|
|
22
|
+
if (element.firstElementChild) observer.observe(element.firstElementChild);
|
|
23
|
+
measure();
|
|
24
|
+
return () => observer.disconnect();
|
|
25
|
+
}, []);
|
|
26
|
+
const named = Boolean(props["aria-label"] || props["aria-labelledby"]);
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
ref={composeRefs(ref, container)}
|
|
30
|
+
className={cx("ml-table-container", className)}
|
|
31
|
+
tabIndex={scrolls ? 0 : undefined}
|
|
32
|
+
role={scrolls && named ? "region" : undefined}
|
|
33
|
+
{...props}
|
|
34
|
+
>
|
|
35
|
+
<table data-size={size} data-striped={striped ? "" : undefined} className="ml-table">{children}</table>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
17
39
|
);
|
|
18
40
|
Table.displayName = "Table";
|
|
19
41
|
const TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(
|
|
@@ -181,6 +181,7 @@ export const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(functi
|
|
|
181
181
|
ref={control}
|
|
182
182
|
className="ml-tag-input-control"
|
|
183
183
|
data-disabled={disabled || undefined}
|
|
184
|
+
aria-disabled={disabled || undefined}
|
|
184
185
|
data-invalid={message ? "" : undefined}
|
|
185
186
|
onMouseDown={(event) => {
|
|
186
187
|
// Clicking the field's empty space puts the caret in the input.
|