@astroapps/aria-base 1.4.0 → 1.5.1
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/.rush/temp/chunked-rush-logs/aria-base.build.chunks.jsonl +2 -2
- package/.rush/temp/operation/build/state.json +1 -1
- package/.rush/temp/package-deps_build.json +10 -6
- package/.rush/temp/shrinkwrap-deps.json +15 -2
- package/lib/Button.d.ts +2 -2
- package/lib/Field.d.ts +73 -0
- package/lib/ListBox.d.ts +76 -0
- package/lib/Popover.d.ts +8 -11
- package/lib/Select.d.ts +13 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +233 -44
- package/lib/index.js.map +1 -1
- package/lib/utils.d.ts +17 -0
- package/package.json +5 -2
- package/src/Button.tsx +14 -6
- package/src/Field.tsx +97 -0
- package/src/ListBox.tsx +130 -0
- package/src/Popover.tsx +56 -49
- package/src/Select.tsx +90 -0
- package/src/index.ts +3 -0
- package/src/utils.ts +20 -0
package/src/ListBox.tsx
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
ListBox as AriaListBox,
|
|
4
|
+
ListBoxItem as AriaListBoxItem,
|
|
5
|
+
ListBoxProps as AriaListBoxProps,
|
|
6
|
+
Collection,
|
|
7
|
+
Header,
|
|
8
|
+
ListBoxItemProps,
|
|
9
|
+
ListBoxSection,
|
|
10
|
+
SectionProps,
|
|
11
|
+
composeRenderProps,
|
|
12
|
+
} from "react-aria-components";
|
|
13
|
+
import { tv } from "tailwind-variants";
|
|
14
|
+
import { composeTailwindRenderProps, focusRing } from "./utils";
|
|
15
|
+
|
|
16
|
+
interface ListBoxProps<T>
|
|
17
|
+
extends Omit<AriaListBoxProps<T>, "layout" | "orientation"> {}
|
|
18
|
+
|
|
19
|
+
export function ListBox<T extends object>({
|
|
20
|
+
children,
|
|
21
|
+
...props
|
|
22
|
+
}: ListBoxProps<T>) {
|
|
23
|
+
return (
|
|
24
|
+
<AriaListBox
|
|
25
|
+
{...props}
|
|
26
|
+
className={composeTailwindRenderProps(
|
|
27
|
+
props.className,
|
|
28
|
+
"outline-0 p-1 w-[200px] bg-white border border-neutral-300 rounded-lg font-sans",
|
|
29
|
+
)}
|
|
30
|
+
>
|
|
31
|
+
{children}
|
|
32
|
+
</AriaListBox>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const itemStyles = tv({
|
|
37
|
+
extend: focusRing,
|
|
38
|
+
base: "group relative flex items-center gap-8 cursor-default select-none py-1.5 px-2.5 rounded-md will-change-transform text-sm forced-color-adjust-none",
|
|
39
|
+
variants: {
|
|
40
|
+
isSelected: {
|
|
41
|
+
false:
|
|
42
|
+
"text-neutral-700 hover:bg-neutral-100 pressed:bg-neutral-100 -outline-offset-2",
|
|
43
|
+
true: "bg-secondary-600 text-white forced-colors:bg-[Highlight] forced-colors:text-[HighlightText] [&:has(+[data-selected])]:rounded-b-none [&+[data-selected]]:rounded-t-none -outline-offset-4 outline-white forced-colors:outline-[HighlightText]",
|
|
44
|
+
},
|
|
45
|
+
isDisabled: {
|
|
46
|
+
true: "text-neutral-300 forced-colors:text-[GrayText]",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export function ListBoxItem(props: ListBoxItemProps) {
|
|
52
|
+
let textValue =
|
|
53
|
+
props.textValue ||
|
|
54
|
+
(typeof props.children === "string" ? props.children : undefined);
|
|
55
|
+
return (
|
|
56
|
+
<AriaListBoxItem {...props} textValue={textValue} className={itemStyles}>
|
|
57
|
+
{composeRenderProps(props.children, (children) => (
|
|
58
|
+
<>
|
|
59
|
+
{children}
|
|
60
|
+
<div className="absolute left-4 right-4 bottom-0 h-px bg-white/20 forced-colors:bg-[HighlightText] hidden [.group[data-selected]:has(+[data-selected])_&]:block" />
|
|
61
|
+
</>
|
|
62
|
+
))}
|
|
63
|
+
</AriaListBoxItem>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const dropdownItemStyles = tv({
|
|
68
|
+
base: "group flex items-center gap-4 cursor-default select-none py-2 pl-3 pr-3 selected:pr-1 rounded-lg outline outline-0 text-sm forced-color-adjust-none no-underline [&[href]]:cursor-pointer [-webkit-tap-highlight-color:transparent]",
|
|
69
|
+
variants: {
|
|
70
|
+
isDisabled: {
|
|
71
|
+
false: "text-neutral-900",
|
|
72
|
+
true: "text-neutral-300 forced-colors:text-[GrayText]",
|
|
73
|
+
},
|
|
74
|
+
isPressed: {
|
|
75
|
+
true: "bg-neutral-100",
|
|
76
|
+
},
|
|
77
|
+
isFocused: {
|
|
78
|
+
true: "bg-secondary-600 text-white forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]",
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
compoundVariants: [
|
|
82
|
+
{
|
|
83
|
+
isFocused: false,
|
|
84
|
+
isOpen: true,
|
|
85
|
+
className: "bg-neutral-100",
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export function DropdownItem(props: ListBoxItemProps) {
|
|
91
|
+
let textValue =
|
|
92
|
+
props.textValue ||
|
|
93
|
+
(typeof props.children === "string" ? props.children : undefined);
|
|
94
|
+
return (
|
|
95
|
+
<AriaListBoxItem
|
|
96
|
+
{...props}
|
|
97
|
+
textValue={textValue}
|
|
98
|
+
className={dropdownItemStyles}
|
|
99
|
+
>
|
|
100
|
+
{composeRenderProps(props.children, (children, { isSelected }) => (
|
|
101
|
+
<>
|
|
102
|
+
<span className="flex items-center flex-1 gap-2 font-normal truncate group-selected:font-semibold">
|
|
103
|
+
{children}
|
|
104
|
+
</span>
|
|
105
|
+
<span className="flex items-center w-5">
|
|
106
|
+
{isSelected && <i className="w-4 h-4 fa fa-check" />}
|
|
107
|
+
</span>
|
|
108
|
+
</>
|
|
109
|
+
))}
|
|
110
|
+
</AriaListBoxItem>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface DropdownSectionProps<T> extends SectionProps<T> {
|
|
115
|
+
title?: string;
|
|
116
|
+
items?: any;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function DropdownSection<T extends object>(
|
|
120
|
+
props: DropdownSectionProps<T>,
|
|
121
|
+
) {
|
|
122
|
+
return (
|
|
123
|
+
<ListBoxSection className="first:-mt-[5px] after:content-[''] after:block after:h-[5px] last:after:hidden">
|
|
124
|
+
<Header className="text-sm font-semibold text-neutral-500 px-4 py-1 truncate sticky -top-[5px] -mt-px -mx-1 z-10 bg-neutral-100/60 backdrop-blur-md supports-[-moz-appearance:none]:bg-neutral-100 border-y border-y-neutral-200 [&+*]:mt-1">
|
|
125
|
+
{props.title}
|
|
126
|
+
</Header>
|
|
127
|
+
<Collection items={props.items}>{props.children}</Collection>
|
|
128
|
+
</ListBoxSection>
|
|
129
|
+
);
|
|
130
|
+
}
|
package/src/Popover.tsx
CHANGED
|
@@ -1,65 +1,72 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import {
|
|
2
|
+
OverlayArrow,
|
|
3
|
+
Popover as AriaPopover,
|
|
4
|
+
PopoverProps as AriaPopoverProps,
|
|
5
|
+
composeRenderProps,
|
|
6
|
+
} from "react-aria-components";
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { tv } from "tailwind-variants";
|
|
9
|
+
import { OverlayTriggerState } from "react-stately";
|
|
6
10
|
|
|
7
|
-
export interface
|
|
8
|
-
|
|
9
|
-
popoverClass?: string;
|
|
10
|
-
}
|
|
11
|
-
export interface PopoverProps
|
|
12
|
-
extends Omit<AriaPopoverProps, "popoverRef">,
|
|
13
|
-
PopoverClasses {
|
|
11
|
+
export interface PopoverProps extends Omit<AriaPopoverProps, "children"> {
|
|
12
|
+
showArrow?: boolean;
|
|
14
13
|
children: React.ReactNode;
|
|
15
|
-
state: OverlayTriggerState;
|
|
16
|
-
portalContainer?: Element;
|
|
17
|
-
renderArrow?: (props: DOMAttributes) => ReactNode;
|
|
18
14
|
}
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
const styles = tv({
|
|
17
|
+
base: "font-sans bg-white forced-colors:bg-[Canvas] shadow-2xl rounded-xl bg-clip-padding border border-black/10 text-neutral-700 outline-0",
|
|
18
|
+
variants: {
|
|
19
|
+
isEntering: {
|
|
20
|
+
true: "animate-in fade-in placement-bottom:slide-in-from-top-1 placement-top:slide-in-from-bottom-1 placement-left:slide-in-from-right-1 placement-right:slide-in-from-left-1 ease-out duration-200",
|
|
21
|
+
},
|
|
22
|
+
isExiting: {
|
|
23
|
+
true: "animate-out fade-out placement-bottom:slide-out-to-top-1 placement-top:slide-out-to-bottom-1 placement-left:slide-out-to-right-1 placement-right:slide-out-to-left-1 ease-in duration-150",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
24
27
|
|
|
25
28
|
export function Popover({
|
|
26
29
|
children,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
portalContainer,
|
|
30
|
+
showArrow,
|
|
31
|
+
className,
|
|
30
32
|
...props
|
|
31
33
|
}: PopoverProps) {
|
|
32
|
-
let
|
|
33
|
-
const { popoverClass, underlayClass } = {
|
|
34
|
-
...DefaultPopoverClasses,
|
|
35
|
-
...props,
|
|
36
|
-
};
|
|
37
|
-
let { popoverProps, underlayProps, arrowProps } = usePopover(
|
|
38
|
-
{
|
|
39
|
-
...props,
|
|
40
|
-
popoverRef,
|
|
41
|
-
},
|
|
42
|
-
state,
|
|
43
|
-
);
|
|
44
|
-
|
|
34
|
+
let offset = showArrow ? 12 : 8;
|
|
45
35
|
return (
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
36
|
+
<AriaPopover
|
|
37
|
+
offset={offset}
|
|
38
|
+
{...props}
|
|
39
|
+
className={composeRenderProps(className, (className, renderProps) =>
|
|
40
|
+
styles({ ...renderProps, className }),
|
|
41
|
+
)}
|
|
42
|
+
>
|
|
43
|
+
{showArrow && (
|
|
44
|
+
<OverlayArrow className="group">
|
|
45
|
+
<svg
|
|
46
|
+
width={12}
|
|
47
|
+
height={12}
|
|
48
|
+
viewBox="0 0 12 12"
|
|
49
|
+
className="block fill-white forced-colors:fill-[Canvas] stroke-1 stroke-black/10 forced-colors:stroke-[ButtonBorder] group-placement-bottom:rotate-180 group-placement-left:-rotate-90 group-placement-right:rotate-90"
|
|
50
|
+
>
|
|
51
|
+
<path d="M0 0 L6 6 L12 0" />
|
|
52
|
+
</svg>
|
|
53
|
+
</OverlayArrow>
|
|
54
|
+
)}
|
|
55
|
+
{children}
|
|
56
|
+
</AriaPopover>
|
|
60
57
|
);
|
|
61
58
|
}
|
|
62
59
|
|
|
60
|
+
export interface PopoverClasses {
|
|
61
|
+
underlayClass?: string;
|
|
62
|
+
popoverClass?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const DefaultPopoverClasses = {
|
|
66
|
+
underlayClass: "fixed inset-0",
|
|
67
|
+
popoverClass: "bg-white",
|
|
68
|
+
};
|
|
69
|
+
|
|
63
70
|
export function createOverlayState(ref: {
|
|
64
71
|
value: boolean;
|
|
65
72
|
}): OverlayTriggerState {
|
package/src/Select.tsx
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Select as AriaSelect,
|
|
4
|
+
SelectProps as AriaSelectProps,
|
|
5
|
+
Button,
|
|
6
|
+
ListBox,
|
|
7
|
+
ListBoxItemProps,
|
|
8
|
+
SelectValue,
|
|
9
|
+
ValidationResult,
|
|
10
|
+
} from "react-aria-components";
|
|
11
|
+
import { tv } from "tailwind-variants";
|
|
12
|
+
import { Description, FieldError, Label } from "./Field";
|
|
13
|
+
import { DropdownItem, DropdownSection, DropdownSectionProps } from "./ListBox";
|
|
14
|
+
import { Popover } from "./Popover";
|
|
15
|
+
import { composeTailwindRenderProps, focusRing } from "./utils";
|
|
16
|
+
|
|
17
|
+
const styles = tv({
|
|
18
|
+
extend: focusRing,
|
|
19
|
+
base: "flex items-center text-start gap-4 w-full font-sans border border-black/10 cursor-default rounded-lg pl-3 pr-2 h-9 min-w-[72px] transition bg-neutral-50 [-webkit-tap-highlight-color:transparent]",
|
|
20
|
+
variants: {
|
|
21
|
+
isDisabled: {
|
|
22
|
+
false:
|
|
23
|
+
"text-neutral-800 hover:bg-neutral-100 pressed:bg-neutral-200 group-invalid:outline group-invalid:outline-red-600 forced-colors:group-invalid:outline-[Mark]",
|
|
24
|
+
true: "border-transparent text-neutral-200 forced-colors:text-[GrayText] bg-neutral-100",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export interface SelectProps<T extends object>
|
|
30
|
+
extends Omit<AriaSelectProps<T>, "children"> {
|
|
31
|
+
label?: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
errorMessage?: string | ((validation: ValidationResult) => string);
|
|
34
|
+
items?: Iterable<T>;
|
|
35
|
+
children: React.ReactNode | ((item: T) => React.ReactNode);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function Select<T extends object>({
|
|
39
|
+
label,
|
|
40
|
+
description,
|
|
41
|
+
errorMessage,
|
|
42
|
+
children,
|
|
43
|
+
items,
|
|
44
|
+
...props
|
|
45
|
+
}: SelectProps<T>) {
|
|
46
|
+
return (
|
|
47
|
+
<AriaSelect
|
|
48
|
+
{...props}
|
|
49
|
+
className={composeTailwindRenderProps(
|
|
50
|
+
props.className,
|
|
51
|
+
"group flex flex-col gap-1 relative font-sans",
|
|
52
|
+
)}
|
|
53
|
+
>
|
|
54
|
+
{label && <Label>{label}</Label>}
|
|
55
|
+
<Button className={styles}>
|
|
56
|
+
<SelectValue className="flex-1 text-sm text-nowrap">
|
|
57
|
+
{({ selectedText, defaultChildren }) =>
|
|
58
|
+
selectedText || defaultChildren
|
|
59
|
+
}
|
|
60
|
+
</SelectValue>
|
|
61
|
+
<i
|
|
62
|
+
aria-hidden
|
|
63
|
+
className={
|
|
64
|
+
"w-4 h-4 fa fa-chevron-down text-neutral-600 forced-colors:text-[ButtonText] group-disabled:text-neutral-200 forced-colors:group-disabled:text-[GrayText]"
|
|
65
|
+
}
|
|
66
|
+
/>
|
|
67
|
+
</Button>
|
|
68
|
+
{description && <Description>{description}</Description>}
|
|
69
|
+
<FieldError>{errorMessage}</FieldError>
|
|
70
|
+
<Popover className="min-w-(--trigger-width)">
|
|
71
|
+
<ListBox
|
|
72
|
+
items={items}
|
|
73
|
+
className="outline-hidden box-border p-1 max-h-[inherit] overflow-auto [clip-path:inset(0_0_0_0_round_.75rem)]"
|
|
74
|
+
>
|
|
75
|
+
{children}
|
|
76
|
+
</ListBox>
|
|
77
|
+
</Popover>
|
|
78
|
+
</AriaSelect>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function SelectItem(props: ListBoxItemProps) {
|
|
83
|
+
return <DropdownItem {...props} />;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function SelectSection<T extends object>(
|
|
87
|
+
props: DropdownSectionProps<T>,
|
|
88
|
+
) {
|
|
89
|
+
return <DropdownSection {...props} />;
|
|
90
|
+
}
|
package/src/index.ts
CHANGED
package/src/utils.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { composeRenderProps } from "react-aria-components";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
export const focusRing = tv({
|
|
6
|
+
base: "outline outline-secondary-600 forced-colors:outline-[Highlight] outline-offset-2",
|
|
7
|
+
variants: {
|
|
8
|
+
isFocusVisible: {
|
|
9
|
+
false: "outline-0",
|
|
10
|
+
true: "outline-2",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export function composeTailwindRenderProps<T>(
|
|
16
|
+
className: string | ((v: T) => string) | undefined,
|
|
17
|
+
tw: string,
|
|
18
|
+
): string | ((v: T) => string) {
|
|
19
|
+
return composeRenderProps(className, (className) => twMerge(tw, className));
|
|
20
|
+
}
|