@ariakit/solid-components 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.
- package/CHANGELOG.md +19 -0
- package/dist/as/as.d.ts +30 -0
- package/dist/as/as.d.ts.map +1 -0
- package/dist/as/as.js +40 -0
- package/dist/as/as.js.map +1 -0
- package/dist/focus-trap/focus-trap-region.d.ts +42 -0
- package/dist/focus-trap/focus-trap-region.d.ts.map +1 -0
- package/dist/focus-trap/focus-trap-region.js +71 -0
- package/dist/focus-trap/focus-trap-region.js.map +1 -0
- package/dist/focus-trap/focus-trap.d.ts +31 -0
- package/dist/focus-trap/focus-trap.d.ts.map +1 -0
- package/dist/focus-trap/focus-trap.js +42 -0
- package/dist/focus-trap/focus-trap.js.map +1 -0
- package/dist/group/group-label-context.d.ts +7 -0
- package/dist/group/group-label-context.d.ts.map +1 -0
- package/dist/group/group-label-context.js +7 -0
- package/dist/group/group-label-context.js.map +1 -0
- package/dist/group/group-label.d.ts +37 -0
- package/dist/group/group-label.d.ts.map +1 -0
- package/dist/group/group-label.js +51 -0
- package/dist/group/group-label.js.map +1 -0
- package/dist/group/group.d.ts +32 -0
- package/dist/group/group.d.ts.map +1 -0
- package/dist/group/group.js +49 -0
- package/dist/group/group.js.map +1 -0
- package/dist/heading/heading-context.d.ts +8 -0
- package/dist/heading/heading-context.d.ts.map +1 -0
- package/dist/heading/heading-context.js +7 -0
- package/dist/heading/heading-context.js.map +1 -0
- package/dist/heading/heading-level.d.ts +32 -0
- package/dist/heading/heading-level.d.ts.map +1 -0
- package/dist/heading/heading-level.js +34 -0
- package/dist/heading/heading-level.js.map +1 -0
- package/dist/heading/heading.d.ts +42 -0
- package/dist/heading/heading.d.ts.map +1 -0
- package/dist/heading/heading.js +57 -0
- package/dist/heading/heading.js.map +1 -0
- package/dist/heading/utils.d.ts +5 -0
- package/dist/heading/utils.d.ts.map +1 -0
- package/dist/heading/utils.js +0 -0
- package/dist/role/role.d.ts +34 -0
- package/dist/role/role.d.ts.map +1 -0
- package/dist/role/role.js +67 -0
- package/dist/role/role.js.map +1 -0
- package/dist/separator/separator.d.ts +36 -0
- package/dist/separator/separator.d.ts.map +1 -0
- package/dist/separator/separator.js +36 -0
- package/dist/separator/separator.js.map +1 -0
- package/dist/visually-hidden/visually-hidden.d.ts +37 -0
- package/dist/visually-hidden/visually-hidden.d.ts.map +1 -0
- package/dist/visually-hidden/visually-hidden.js +48 -0
- package/dist/visually-hidden/visually-hidden.js.map +1 -0
- package/index.ts +1 -0
- package/license +21 -0
- package/package.json +113 -0
- package/readme.md +19 -0
- package/solid/as/as.jsx +38 -0
- package/solid/as/as.jsx.map +1 -0
- package/solid/focus-trap/focus-trap-region.jsx +65 -0
- package/solid/focus-trap/focus-trap-region.jsx.map +1 -0
- package/solid/focus-trap/focus-trap.jsx +42 -0
- package/solid/focus-trap/focus-trap.jsx.map +1 -0
- package/solid/group/group-label-context.jsx +7 -0
- package/solid/group/group-label-context.jsx.map +1 -0
- package/solid/group/group-label.jsx +51 -0
- package/solid/group/group-label.jsx.map +1 -0
- package/solid/group/group.jsx +43 -0
- package/solid/group/group.jsx.map +1 -0
- package/solid/heading/heading-context.jsx +7 -0
- package/solid/heading/heading-context.jsx.map +1 -0
- package/solid/heading/heading-level.jsx +30 -0
- package/solid/heading/heading-level.jsx.map +1 -0
- package/solid/heading/heading.jsx +57 -0
- package/solid/heading/heading.jsx.map +1 -0
- package/solid/heading/utils.jsx +0 -0
- package/solid/role/role.jsx +67 -0
- package/solid/role/role.jsx.map +1 -0
- package/solid/separator/separator.jsx +36 -0
- package/solid/separator/separator.jsx.map +1 -0
- package/solid/visually-hidden/visually-hidden.jsx +48 -0
- package/solid/visually-hidden/visually-hidden.jsx.map +1 -0
- package/src/as/as.tsx +62 -0
- package/src/focus-trap/focus-trap-region.tsx +108 -0
- package/src/focus-trap/focus-trap.tsx +61 -0
- package/src/group/group-label-context.tsx +4 -0
- package/src/group/group-label.tsx +76 -0
- package/src/group/group.tsx +68 -0
- package/src/heading/heading-context.tsx +5 -0
- package/src/heading/heading-level.tsx +43 -0
- package/src/heading/heading.tsx +87 -0
- package/src/heading/utils.ts +1 -0
- package/src/role/role.tsx +91 -0
- package/src/separator/separator.tsx +66 -0
- package/src/visually-hidden/visually-hidden.tsx +63 -0
package/src/as/as.tsx
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { mergeProps } from "@ariakit/solid-utils";
|
|
2
|
+
import type { Component, ComponentProps, JSX, ValidComponent } from "solid-js";
|
|
3
|
+
import { Dynamic } from "solid-js/web";
|
|
4
|
+
|
|
5
|
+
type AsElements = {
|
|
6
|
+
[K in keyof JSX.IntrinsicElements]: Component<ComponentProps<K>>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type AsComponent = <T extends Component<any>, P = ComponentProps<T>>(
|
|
10
|
+
props: AsProps<T, P>,
|
|
11
|
+
) => JSX.Element;
|
|
12
|
+
|
|
13
|
+
const cache = new Map<string, Component<any>>();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Allows a component to be rendered as a different HTML element or Solid
|
|
17
|
+
* component. Must be passed to the `render` prop of a component that
|
|
18
|
+
* supports it.
|
|
19
|
+
*
|
|
20
|
+
* To render as an HTML element, use `<As.element />` (e.g. `<As.button />`).
|
|
21
|
+
*
|
|
22
|
+
* To render as a component, use `<As component={Component} />` (e.g. `<As
|
|
23
|
+
* component={MyButton} />`).
|
|
24
|
+
*
|
|
25
|
+
* Check out the [Composition](https://solid.ariakit.com/guide/composition)
|
|
26
|
+
* guide for more details.
|
|
27
|
+
* @example
|
|
28
|
+
* ```jsx
|
|
29
|
+
* <Role render={<As component={MyButton} variant="primary" />} />
|
|
30
|
+
* <Role render={<As.button type="button" />} />
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export const As = new Proxy(
|
|
34
|
+
function As(props: any) {
|
|
35
|
+
return ((parentProps: unknown) => (
|
|
36
|
+
// TODO: replace with LazyDynamic
|
|
37
|
+
<Dynamic
|
|
38
|
+
{...mergeProps(parentProps, props)}
|
|
39
|
+
component={props.component}
|
|
40
|
+
/>
|
|
41
|
+
)) as unknown as JSX.Element;
|
|
42
|
+
} as AsComponent & AsElements,
|
|
43
|
+
{
|
|
44
|
+
get: (_, key: keyof JSX.IntrinsicElements) => {
|
|
45
|
+
let component = cache.get(key);
|
|
46
|
+
if (!component) {
|
|
47
|
+
component = function AsElement(props: any): JSX.Element {
|
|
48
|
+
return ((parentProps: unknown) => (
|
|
49
|
+
// TODO: replace with LazyDynamic
|
|
50
|
+
<Dynamic {...mergeProps(parentProps, props)} component={key} />
|
|
51
|
+
)) as unknown as JSX.Element;
|
|
52
|
+
};
|
|
53
|
+
cache.set(key, component);
|
|
54
|
+
}
|
|
55
|
+
return component;
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
export type AsProps<T extends ValidComponent, P = ComponentProps<T>> = {
|
|
61
|
+
[K in keyof P]: P[K];
|
|
62
|
+
} & { component: T };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRef,
|
|
3
|
+
mergeProps,
|
|
4
|
+
createHook,
|
|
5
|
+
createInstance,
|
|
6
|
+
withOptions,
|
|
7
|
+
wrapInstance,
|
|
8
|
+
} from "@ariakit/solid-utils";
|
|
9
|
+
import type { Options, Props } from "@ariakit/solid-utils";
|
|
10
|
+
import { getAllTabbableIn } from "@ariakit/utils";
|
|
11
|
+
import type { ValidComponent } from "solid-js";
|
|
12
|
+
import { Show } from "solid-js";
|
|
13
|
+
import { FocusTrap } from "./focus-trap.tsx";
|
|
14
|
+
|
|
15
|
+
const TagName = "div" satisfies ValidComponent;
|
|
16
|
+
type TagName = typeof TagName;
|
|
17
|
+
type HTMLType = HTMLElementTagNameMap[TagName];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns props to create a `FocusTrapRegion` component.
|
|
21
|
+
* @see https://solid.ariakit.com/components/focus-trap-region
|
|
22
|
+
* @example
|
|
23
|
+
* ```jsx
|
|
24
|
+
* const props = useFocusTrapRegion();
|
|
25
|
+
* <Role {...props} />
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const useFocusTrapRegion = createHook<TagName, FocusTrapRegionOptions>(
|
|
29
|
+
withOptions({ enabled: false }, function useFocusTrapRegion(props, options) {
|
|
30
|
+
const ref = createRef<HTMLType>();
|
|
31
|
+
|
|
32
|
+
props = wrapInstance(props, (wrapperProps) => {
|
|
33
|
+
const renderFocusTrap = () => {
|
|
34
|
+
return (
|
|
35
|
+
<Show when={options.enabled}>
|
|
36
|
+
<FocusTrap
|
|
37
|
+
onFocus={(event) => {
|
|
38
|
+
// TODO: (react) opportunity to extract into @ariakit/components?
|
|
39
|
+
const container = ref.current;
|
|
40
|
+
if (!container) return;
|
|
41
|
+
const tabbables = getAllTabbableIn(container, true);
|
|
42
|
+
const first = tabbables[0];
|
|
43
|
+
const last = tabbables[tabbables.length - 1];
|
|
44
|
+
// Fallbacks to the container element
|
|
45
|
+
if (!tabbables.length) {
|
|
46
|
+
container.focus();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (event.relatedTarget === first) {
|
|
50
|
+
last?.focus();
|
|
51
|
+
} else {
|
|
52
|
+
first?.focus();
|
|
53
|
+
}
|
|
54
|
+
}}
|
|
55
|
+
/>
|
|
56
|
+
</Show>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
{renderFocusTrap()}
|
|
62
|
+
{wrapperProps.children}
|
|
63
|
+
{renderFocusTrap()}
|
|
64
|
+
</>
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
props = mergeProps({ ref: ref.set }, props);
|
|
69
|
+
|
|
70
|
+
return props;
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Renders a wrapper element that traps the focus inside it when the
|
|
76
|
+
* [`enabled`](https://solid.ariakit.com/reference/focus-trap-region#enabled)
|
|
77
|
+
* prop is `true`.
|
|
78
|
+
* @see https://solid.ariakit.com/components/focus-trap
|
|
79
|
+
* @example
|
|
80
|
+
* ```jsx
|
|
81
|
+
* <FocusTrapRegion>
|
|
82
|
+
* <Button>click me</Button>
|
|
83
|
+
* <Button>trap focus</Button>
|
|
84
|
+
* <Button disabled>disabled Button</Button>
|
|
85
|
+
* </FocusTrapRegion>
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export const FocusTrapRegion = function FocusTrapRegion(
|
|
89
|
+
props: FocusTrapRegionProps,
|
|
90
|
+
) {
|
|
91
|
+
const htmlProps = useFocusTrapRegion(props);
|
|
92
|
+
return createInstance(TagName, htmlProps);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export interface FocusTrapRegionOptions<
|
|
96
|
+
_T extends ValidComponent = TagName,
|
|
97
|
+
> extends Options {
|
|
98
|
+
/**
|
|
99
|
+
* If true, it will trap the focus in the region.
|
|
100
|
+
* @default false
|
|
101
|
+
*/
|
|
102
|
+
enabled?: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type FocusTrapRegionProps<T extends ValidComponent = TagName> = Props<
|
|
106
|
+
T,
|
|
107
|
+
FocusTrapRegionOptions<T>
|
|
108
|
+
>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { mergeProps, createHook, createInstance } from "@ariakit/solid-utils";
|
|
2
|
+
import type { Props } from "@ariakit/solid-utils";
|
|
3
|
+
import type { ValidComponent } from "solid-js";
|
|
4
|
+
import type { VisuallyHiddenOptions } from "../visually-hidden/visually-hidden.tsx";
|
|
5
|
+
import { useVisuallyHidden } from "../visually-hidden/visually-hidden.tsx";
|
|
6
|
+
|
|
7
|
+
const TagName = "span" satisfies ValidComponent;
|
|
8
|
+
type TagName = typeof TagName;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns props to create a `FocusTrap` component.
|
|
12
|
+
* @see https://solid.ariakit.com/components/focus-trap
|
|
13
|
+
* @example
|
|
14
|
+
* ```jsx
|
|
15
|
+
* const props = useFocusTrap();
|
|
16
|
+
* <Role {...props} />
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export const useFocusTrap = createHook<TagName, FocusTrapOptions>(
|
|
20
|
+
function useFocusTrap(props) {
|
|
21
|
+
props = mergeProps(
|
|
22
|
+
{
|
|
23
|
+
"data-focus-trap": "",
|
|
24
|
+
tabIndex: 0,
|
|
25
|
+
"aria-hidden": true,
|
|
26
|
+
style: {
|
|
27
|
+
// Prevents unintended scroll jumps.
|
|
28
|
+
position: "fixed",
|
|
29
|
+
top: 0,
|
|
30
|
+
left: 0,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
props,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
props = useVisuallyHidden(props);
|
|
37
|
+
|
|
38
|
+
return props;
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Renders a focus trap element.
|
|
44
|
+
* @see https://solid.ariakit.com/components/focus-trap
|
|
45
|
+
* @example
|
|
46
|
+
* ```jsx
|
|
47
|
+
* <FocusTrap onFocus={focusSomethingElse} />
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function FocusTrap(props: FocusTrapProps) {
|
|
51
|
+
const htmlProps = useFocusTrap(props);
|
|
52
|
+
return createInstance(TagName, htmlProps);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type FocusTrapOptions<T extends ValidComponent = TagName> =
|
|
56
|
+
VisuallyHiddenOptions<T>;
|
|
57
|
+
|
|
58
|
+
export type FocusTrapProps<T extends ValidComponent = TagName> = Props<
|
|
59
|
+
T,
|
|
60
|
+
FocusTrapOptions<T>
|
|
61
|
+
>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createId,
|
|
3
|
+
mergeProps,
|
|
4
|
+
stableAccessor,
|
|
5
|
+
createHook,
|
|
6
|
+
createInstance,
|
|
7
|
+
} from "@ariakit/solid-utils";
|
|
8
|
+
import type { Options, Props } from "@ariakit/solid-utils";
|
|
9
|
+
import type { ValidComponent } from "solid-js";
|
|
10
|
+
import { createEffect, onCleanup, useContext } from "solid-js";
|
|
11
|
+
import { GroupLabelContext } from "./group-label-context.tsx";
|
|
12
|
+
|
|
13
|
+
const TagName = "div" satisfies ValidComponent;
|
|
14
|
+
type TagName = typeof TagName;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns props to create a `GroupLabel` component. This hook must be used in a
|
|
18
|
+
* component that's wrapped with `Group` so the `aria-labelledby` prop is
|
|
19
|
+
* properly set on the group element.
|
|
20
|
+
* @see https://solid.ariakit.com/components/group
|
|
21
|
+
* @example
|
|
22
|
+
* ```jsx
|
|
23
|
+
* // This component must be wrapped with Group
|
|
24
|
+
* const props = useGroupLabel();
|
|
25
|
+
* <Role {...props}>Label</Role>
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const useGroupLabel = createHook<TagName, GroupLabelOptions>(
|
|
29
|
+
function useGroupLabel(props) {
|
|
30
|
+
const setLabelId = useContext(GroupLabelContext);
|
|
31
|
+
const id = createId(stableAccessor(props, (p) => p.id));
|
|
32
|
+
|
|
33
|
+
createEffect(() => {
|
|
34
|
+
setLabelId?.(id());
|
|
35
|
+
onCleanup(() => setLabelId?.(undefined));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
props = mergeProps(
|
|
39
|
+
{
|
|
40
|
+
get id() {
|
|
41
|
+
return id();
|
|
42
|
+
},
|
|
43
|
+
"aria-hidden": true,
|
|
44
|
+
},
|
|
45
|
+
props,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return props;
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Renders a label in a group. This component should be wrapped with a
|
|
54
|
+
* [`Group`](https://solid.ariakit.com/reference/group) so the `aria-labelledby`
|
|
55
|
+
* prop is correctly set on the group element.
|
|
56
|
+
* @see https://solid.ariakit.com/components/group
|
|
57
|
+
* @example
|
|
58
|
+
* ```jsx
|
|
59
|
+
* <Group>
|
|
60
|
+
* <GroupLabel>Label</GroupLabel>
|
|
61
|
+
* </Group>
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export const GroupLabel = function GroupLabel(props: GroupLabelProps) {
|
|
65
|
+
const htmlProps = useGroupLabel(props);
|
|
66
|
+
return createInstance(TagName, htmlProps);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export interface GroupLabelOptions<
|
|
70
|
+
_T extends ValidComponent = TagName,
|
|
71
|
+
> extends Options {}
|
|
72
|
+
|
|
73
|
+
export type GroupLabelProps<T extends ValidComponent = TagName> = Props<
|
|
74
|
+
T,
|
|
75
|
+
GroupLabelOptions<T>
|
|
76
|
+
>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mergeProps,
|
|
3
|
+
createHook,
|
|
4
|
+
createInstance,
|
|
5
|
+
wrapInstance,
|
|
6
|
+
} from "@ariakit/solid-utils";
|
|
7
|
+
import type { Options, Props } from "@ariakit/solid-utils";
|
|
8
|
+
import type { ValidComponent } from "solid-js";
|
|
9
|
+
import { createSignal } from "solid-js";
|
|
10
|
+
import { As } from "../as/as.tsx";
|
|
11
|
+
import { GroupLabelContext } from "./group-label-context.tsx";
|
|
12
|
+
|
|
13
|
+
const TagName = "div" satisfies ValidComponent;
|
|
14
|
+
type TagName = typeof TagName;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns props to create a `Group` component.
|
|
18
|
+
* @see https://solid.ariakit.com/components/group
|
|
19
|
+
* @example
|
|
20
|
+
* ```jsx
|
|
21
|
+
* const props = useGroup();
|
|
22
|
+
* <Role {...props}>Group</Role>
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export const useGroup = createHook<TagName, GroupOptions>(
|
|
26
|
+
function useGroup(props) {
|
|
27
|
+
const [labelId, setLabelId] = createSignal<string>();
|
|
28
|
+
|
|
29
|
+
props = wrapInstance(
|
|
30
|
+
props,
|
|
31
|
+
<As component={GroupLabelContext.Provider} value={setLabelId} />,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
props = mergeProps(
|
|
35
|
+
{
|
|
36
|
+
role: "group" as const,
|
|
37
|
+
get "aria-labelledby"() {
|
|
38
|
+
return labelId();
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
props,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return props;
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Renders a group element. Optionally, a
|
|
50
|
+
* [`GroupLabel`](https://solid.ariakit.com/reference/group-label) can be rendered as
|
|
51
|
+
* a child to provide a label for the group.
|
|
52
|
+
* @see https://solid.ariakit.com/components/group
|
|
53
|
+
* @example
|
|
54
|
+
* ```jsx
|
|
55
|
+
* <Group>Group</Group>
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export const Group = function Group(props: GroupProps) {
|
|
59
|
+
const htmlProps = useGroup(props);
|
|
60
|
+
return createInstance(TagName, htmlProps);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type GroupOptions<_T extends ValidComponent = TagName> = Options;
|
|
64
|
+
|
|
65
|
+
export type GroupProps<T extends ValidComponent = TagName> = Props<
|
|
66
|
+
T,
|
|
67
|
+
GroupOptions<T>
|
|
68
|
+
>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { JSX } from "solid-js";
|
|
2
|
+
import { useContext } from "solid-js";
|
|
3
|
+
import { HeadingContext } from "./heading-context.tsx";
|
|
4
|
+
import type { HeadingLevels } from "./utils.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A component that sets the heading level for its children. It doesn't render
|
|
8
|
+
* any HTML element, just sets the
|
|
9
|
+
* [`level`](https://solid.ariakit.com/reference/heading-level#level) prop on the
|
|
10
|
+
* context.
|
|
11
|
+
* @see https://solid.ariakit.com/components/heading
|
|
12
|
+
* @example
|
|
13
|
+
* ```jsx
|
|
14
|
+
* <HeadingLevel>
|
|
15
|
+
* <Heading>Heading 1</Heading>
|
|
16
|
+
* <HeadingLevel>
|
|
17
|
+
* <Heading>Heading 2</Heading>
|
|
18
|
+
* </HeadingLevel>
|
|
19
|
+
* </HeadingLevel>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function HeadingLevel(props: HeadingLevelProps) {
|
|
23
|
+
const contextLevel = useContext(HeadingContext);
|
|
24
|
+
const nextLevel = () =>
|
|
25
|
+
Math.max(
|
|
26
|
+
Math.min(props.level || (contextLevel?.() ?? 0) + 1, 6),
|
|
27
|
+
1,
|
|
28
|
+
) as HeadingLevels;
|
|
29
|
+
return (
|
|
30
|
+
<HeadingContext.Provider value={nextLevel}>
|
|
31
|
+
{props.children}
|
|
32
|
+
</HeadingContext.Provider>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface HeadingLevelProps {
|
|
37
|
+
/**
|
|
38
|
+
* The heading level. By default, it'll increase the level by 1 based on the
|
|
39
|
+
* context.
|
|
40
|
+
*/
|
|
41
|
+
level?: HeadingLevels;
|
|
42
|
+
children?: JSX.Element;
|
|
43
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extractTagName,
|
|
3
|
+
createRef,
|
|
4
|
+
mergeProps,
|
|
5
|
+
createHook,
|
|
6
|
+
createInstance,
|
|
7
|
+
} from "@ariakit/solid-utils";
|
|
8
|
+
import type { Options, Props } from "@ariakit/solid-utils";
|
|
9
|
+
import type { ValidComponent } from "solid-js";
|
|
10
|
+
import { createMemo, useContext } from "solid-js";
|
|
11
|
+
import { HeadingContext } from "./heading-context.tsx";
|
|
12
|
+
import type { HeadingLevels } from "./utils.ts";
|
|
13
|
+
|
|
14
|
+
type HeadingElements = `h${HeadingLevels}`;
|
|
15
|
+
const TagName = "h1" satisfies ValidComponent;
|
|
16
|
+
type TagName = HeadingElements;
|
|
17
|
+
type HTMLType = HTMLElementTagNameMap[TagName];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns props to create a `Heading` component. The element type (or the
|
|
21
|
+
* `aria-level` prop, if the element type is not a native heading) is determined
|
|
22
|
+
* by the context level provided by the parent `HeadingLevel` component.
|
|
23
|
+
* @see https://solid.ariakit.com/components/heading
|
|
24
|
+
* @example
|
|
25
|
+
* ```jsx
|
|
26
|
+
* const props = useHeading();
|
|
27
|
+
* <Role {...props}>Heading</Role>
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export const useHeading = createHook<TagName, HeadingOptions>(
|
|
31
|
+
function useHeading(props) {
|
|
32
|
+
const ref = createRef<HTMLType>();
|
|
33
|
+
const level = useContext(HeadingContext) || (() => 1);
|
|
34
|
+
const Element = () => `h${level()}` as const;
|
|
35
|
+
const tagName = extractTagName(ref.get);
|
|
36
|
+
const isNativeHeading = createMemo(
|
|
37
|
+
() => !!tagName() && /^h\d$/.test(tagName()!),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
props = mergeProps(
|
|
41
|
+
{
|
|
42
|
+
// TODO: replace with LazyDynamic
|
|
43
|
+
render: Element(),
|
|
44
|
+
get role() {
|
|
45
|
+
return !isNativeHeading() ? "heading" : undefined;
|
|
46
|
+
},
|
|
47
|
+
get "aria-level"() {
|
|
48
|
+
return !isNativeHeading() ? level() : undefined;
|
|
49
|
+
},
|
|
50
|
+
ref: ref.set,
|
|
51
|
+
},
|
|
52
|
+
props,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return props;
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Renders a heading element. The element type (or the `aria-level` attribute,
|
|
61
|
+
* if the element type is not a native heading) is determined by the context
|
|
62
|
+
* level provided by the closest
|
|
63
|
+
* [`HeadingLevel`](https://solid.ariakit.com/reference/heading-level) ancestor.
|
|
64
|
+
* @see https://solid.ariakit.com/components/heading
|
|
65
|
+
* @example
|
|
66
|
+
* ```jsx
|
|
67
|
+
* <HeadingLevel>
|
|
68
|
+
* <Heading>Heading 1</Heading>
|
|
69
|
+
* <HeadingLevel>
|
|
70
|
+
* <Heading>Heading 2</Heading>
|
|
71
|
+
* </HeadingLevel>
|
|
72
|
+
* </HeadingLevel>
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export const Heading = function Heading(props: HeadingProps) {
|
|
76
|
+
const htmlProps = useHeading(props);
|
|
77
|
+
return createInstance(TagName, htmlProps);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export interface HeadingOptions<
|
|
81
|
+
_T extends ValidComponent = TagName,
|
|
82
|
+
> extends Options {}
|
|
83
|
+
|
|
84
|
+
export type HeadingProps<T extends ValidComponent = TagName> = Props<
|
|
85
|
+
T,
|
|
86
|
+
HeadingOptions<T>
|
|
87
|
+
>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type HeadingLevels = 1 | 2 | 3 | 4 | 5 | 6;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { createHook, createInstance } from "@ariakit/solid-utils";
|
|
2
|
+
import type { Options, Props } from "@ariakit/solid-utils";
|
|
3
|
+
import type { Component, JSX, ValidComponent } from "solid-js";
|
|
4
|
+
|
|
5
|
+
const TagName = "div" satisfies ValidComponent;
|
|
6
|
+
type TagName = typeof TagName;
|
|
7
|
+
|
|
8
|
+
export const elements = [
|
|
9
|
+
"a",
|
|
10
|
+
"button",
|
|
11
|
+
"details",
|
|
12
|
+
"dialog",
|
|
13
|
+
"div",
|
|
14
|
+
"form",
|
|
15
|
+
"h1",
|
|
16
|
+
"h2",
|
|
17
|
+
"h3",
|
|
18
|
+
"h4",
|
|
19
|
+
"h5",
|
|
20
|
+
"h6",
|
|
21
|
+
"header",
|
|
22
|
+
"img",
|
|
23
|
+
"input",
|
|
24
|
+
"label",
|
|
25
|
+
"li",
|
|
26
|
+
"nav",
|
|
27
|
+
"ol",
|
|
28
|
+
"p",
|
|
29
|
+
"section",
|
|
30
|
+
"select",
|
|
31
|
+
"span",
|
|
32
|
+
"summary",
|
|
33
|
+
"textarea",
|
|
34
|
+
"ul",
|
|
35
|
+
"svg",
|
|
36
|
+
] as const;
|
|
37
|
+
|
|
38
|
+
type RoleElements = {
|
|
39
|
+
[K in (typeof elements)[number]]: Component<RoleProps<K>>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Returns props to create a `Role` component.
|
|
44
|
+
* @see https://solid.ariakit.com/components/role
|
|
45
|
+
* @example
|
|
46
|
+
* ```jsx
|
|
47
|
+
* const props = useRole();
|
|
48
|
+
* <Role {...props} />
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export const useRole = createHook<TagName, RoleOptions>(
|
|
52
|
+
function useRole(props) {
|
|
53
|
+
return props;
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// TODO: adapt docs wording to be more accurate for Solid
|
|
58
|
+
/**
|
|
59
|
+
* Renders an abstract element that supports the `render` prop and a
|
|
60
|
+
* `wrapInstance` prop that can be used to wrap the underlying component
|
|
61
|
+
* instance with Solid Portal, Context or other component types.
|
|
62
|
+
* @see https://solid.ariakit.com/components/role
|
|
63
|
+
* @example
|
|
64
|
+
* ```jsx
|
|
65
|
+
* <Role render={<As.div />} />
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export const Role = function Role(props: RoleProps): JSX.Element {
|
|
69
|
+
return createInstance(TagName, props);
|
|
70
|
+
} as Component<RoleProps> & RoleElements;
|
|
71
|
+
|
|
72
|
+
Object.assign(
|
|
73
|
+
Role,
|
|
74
|
+
elements.reduce((acc, element) => {
|
|
75
|
+
acc[element] = function Role(
|
|
76
|
+
props: RoleProps<typeof element>,
|
|
77
|
+
): JSX.Element {
|
|
78
|
+
return createInstance(element, props);
|
|
79
|
+
};
|
|
80
|
+
return acc;
|
|
81
|
+
}, {} as RoleElements),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
export interface RoleOptions<
|
|
85
|
+
_T extends ValidComponent = TagName,
|
|
86
|
+
> extends Options {}
|
|
87
|
+
|
|
88
|
+
export type RoleProps<T extends ValidComponent = TagName> = Props<
|
|
89
|
+
T,
|
|
90
|
+
RoleOptions
|
|
91
|
+
>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mergeProps,
|
|
3
|
+
createHook,
|
|
4
|
+
createInstance,
|
|
5
|
+
withOptions,
|
|
6
|
+
} from "@ariakit/solid-utils";
|
|
7
|
+
import type { Options, Props } from "@ariakit/solid-utils";
|
|
8
|
+
import type { ValidComponent } from "solid-js";
|
|
9
|
+
|
|
10
|
+
const TagName = "hr" satisfies ValidComponent;
|
|
11
|
+
type TagName = typeof TagName;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns props to create a `Separator` component.
|
|
15
|
+
* @see https://solid.ariakit.com/components/separator
|
|
16
|
+
* @example
|
|
17
|
+
* ```jsx
|
|
18
|
+
* const props = useSeparator({ orientation: "horizontal" });
|
|
19
|
+
* <Role {...props} />
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export const useSeparator = createHook<TagName, SeparatorOptions>(
|
|
23
|
+
withOptions(
|
|
24
|
+
{ orientation: "horizontal" },
|
|
25
|
+
function useSeparator(props, options) {
|
|
26
|
+
props = mergeProps(
|
|
27
|
+
{
|
|
28
|
+
role: "separator" as const,
|
|
29
|
+
get "aria-orientation"() {
|
|
30
|
+
return options.orientation;
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
props,
|
|
34
|
+
);
|
|
35
|
+
return props;
|
|
36
|
+
},
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Renders a separator element.
|
|
42
|
+
* @see https://solid.ariakit.com/components/separator
|
|
43
|
+
* @example
|
|
44
|
+
* ```jsx
|
|
45
|
+
* <Separator orientation="horizontal" />
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export const Separator = function Separator(props: SeparatorProps) {
|
|
49
|
+
const htmlProps = useSeparator(props);
|
|
50
|
+
return createInstance(TagName, htmlProps);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export interface SeparatorOptions<
|
|
54
|
+
_T extends ValidComponent = TagName,
|
|
55
|
+
> extends Options {
|
|
56
|
+
/**
|
|
57
|
+
* The orientation of the separator.
|
|
58
|
+
* @default "horizontal"
|
|
59
|
+
*/
|
|
60
|
+
orientation?: "horizontal" | "vertical";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type SeparatorProps<T extends ValidComponent = TagName> = Props<
|
|
64
|
+
T,
|
|
65
|
+
SeparatorOptions<T>
|
|
66
|
+
>;
|