Skip to content

Commit 3d7d499

Browse files
committed
feat(toolbar): ✨ Integrate scrollable area in TipTap toolbar for better accessibility
1 parent e9479c8 commit 3d7d499

File tree

10 files changed

+248
-136
lines changed

10 files changed

+248
-136
lines changed

.github/pull_request_template.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,3 @@
88
### What?
99

1010
### Why?
11-
12-
### How?

apps/docs/content/docs/ui/radio-group.mdx

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ description: Pick one option from a list.
99

1010
## Usage
1111

12-
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
12+
import { Tab, Tabs } from "fumadocs-ui/components/tabs";
1313

1414
<Tabs items={['Auto Form', 'Manual']}>
1515
<Tab value="Auto Form">
@@ -21,7 +21,7 @@ import { AutoFormRadioGroup } from '@vitnode/core/components/form/fields/radio-g
2121

2222
```ts
2323
const formSchema = z.object({
24-
options: z.enum(['option1', 'option2', 'option3']),
24+
options: z.enum(["option1", "option2", "option3"])
2525
});
2626
```
2727

@@ -30,29 +30,29 @@ const formSchema = z.object({
3030
formSchema={formSchema}
3131
fields={[
3232
{
33-
id: 'options',
34-
component: props => (
33+
id: "options",
34+
component: (props) => (
3535
<AutoFormRadioGroup
3636
{...props}
3737
description="By checking this box, you agree to the terms and conditions."
3838
label="I agree to the terms and conditions"
3939
labels={[
4040
{
41-
value: 'option1',
42-
label: 'Option 1',
41+
value: "option1",
42+
label: "Option 1"
4343
},
4444
{
45-
value: 'option2',
46-
label: 'Option 2',
45+
value: "option2",
46+
label: "Option 2"
4747
},
4848
{
49-
value: 'option3',
50-
label: 'Option 3',
51-
},
49+
value: "option3",
50+
label: "Option 3"
51+
}
5252
]}
5353
/>
54-
),
55-
},
54+
)
55+
}
5656
]}
5757
/>
5858
```
@@ -86,28 +86,26 @@ import {
8686

8787
## Props
8888

89-
import { TypeTable } from 'fumadocs-ui/components/type-table';
89+
import { TypeTable } from "fumadocs-ui/components/type-table";
9090

9191
<TypeTable
9292
type={{
9393
label: {
94-
description:
95-
'The label for the input field. This is displayed above the input.',
96-
type: 'string',
97-
default: '',
94+
description: "The label for the input field. This is displayed above the input.",
95+
type: "string",
96+
default: ""
9897
},
9998
description: {
100-
description:
101-
'A short description of the input field. This is displayed below the input.',
102-
type: 'string',
103-
default: '',
99+
description: "A short description of the input field. This is displayed below the input.",
100+
type: "string",
101+
default: ""
104102
},
105103
labels: {
106104
description:
107-
'An array of objects representing the radio button options. Each object should have a `value` and a `label` property.',
108-
type: '{ value: string; label: string }[]',
109-
default: '[]',
110-
},
105+
"An array of objects representing the radio button options. Each object should have a `value` and a `label` property.",
106+
type: "{ value: string; label: string }[]",
107+
default: "[]"
108+
}
111109
}}
112110
/>
113111

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
title: Scroll Area
3+
description: Container for scrollable content with custom scrollbars.
4+
---
5+
6+
## Preview
7+
8+
<Preview name="scroll-area" />
9+
10+
## Usage
11+
12+
```ts
13+
import { ScrollArea } from "@vitnode/core/components/ui/scroll-area";
14+
import { Separator } from "@vitnode/core/components/ui/separator";
15+
import React from "react";
16+
```
17+
18+
```tsx
19+
const tags = Array.from({ length: 50 }).map((_, i, a) => `v1.2.0-beta.${a.length - i}`);
20+
21+
export function ScrollAreaDemo() {
22+
return (
23+
<ScrollArea className="h-72 w-48 rounded-md border">
24+
<div className="p-4">
25+
<h4 className="mb-4 text-sm leading-none font-medium">Tags</h4>
26+
{tags.map((tag) => (
27+
<React.Fragment key={tag}>
28+
<div className="text-sm">{tag}</div>
29+
<Separator className="my-2" />
30+
</React.Fragment>
31+
))}
32+
</div>
33+
</ScrollArea>
34+
);
35+
}
36+
```
37+
38+
## API Reference
39+
40+
[Radix UI - Scroll area](https://www.radix-ui.com/primitives/docs/components/scroll-area#api-reference)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ScrollArea } from "@vitnode/core/components/ui/scroll-area";
2+
import { Separator } from "@vitnode/core/components/ui/separator";
3+
import React from "react";
4+
5+
const tags = Array.from({ length: 50 }).map(
6+
(_, i, a) => `v1.2.0-beta.${a.length - i}`,
7+
);
8+
9+
export default function ScrollAreaDemo() {
10+
return (
11+
<ScrollArea className="h-72 w-48 rounded-md border">
12+
<div className="p-4">
13+
<h4 className="mb-4 text-sm leading-none font-medium">Tags</h4>
14+
{tags.map(tag => (
15+
<React.Fragment key={tag}>
16+
<div className="text-sm">{tag}</div>
17+
<Separator className="my-2" />
18+
</React.Fragment>
19+
))}
20+
</div>
21+
</ScrollArea>
22+
);
23+
}

packages/config/biome.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@
5858
"noReactPropAssignments": "error",
5959
"noRenderReturnValue": "error",
6060
"useJsonImportAttributes": "warn",
61-
"useUniqueElementIds": "off"
61+
"useUniqueElementIds": "off",
62+
"useExhaustiveDependencies": "off"
6263
},
6364
"performance": {
6465
"noAwaitInLoops": "error",

packages/vitnode/src/components/tiptap/toolbar/actions/alignment-action.tsx

Lines changed: 31 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,42 +11,27 @@ import { Button } from "@/components/ui/button";
1111
import {
1212
DropdownMenu,
1313
DropdownMenuContent,
14-
DropdownMenuItem,
14+
DropdownMenuRadioGroup,
15+
DropdownMenuRadioItem,
1516
DropdownMenuShortcut,
1617
DropdownMenuTrigger,
1718
} from "@/components/ui/dropdown-menu";
1819
import { CtrlOrCommandCharacter } from "@/lib/ctrl-or-command-character";
19-
import { cn } from "@/lib/utils";
2020
import { useToolbarEditor } from "../use-toolbar-editor";
2121

2222
export const AlignmentAction = () => {
2323
const t = useTranslations("core.global.editor");
2424
const { editor } = useToolbarEditor();
25-
const { isAlignActive } = useEditorState({
25+
const activeValue = useEditorState({
2626
editor,
2727
selector: ctx => {
28-
return {
29-
isAlignActive: () => {
30-
if (ctx.editor.isActive({ textAlign: "left" })) {
31-
return "left";
32-
}
33-
34-
if (ctx.editor.isActive({ textAlign: "center" })) {
35-
return "center";
36-
}
37-
if (ctx.editor.isActive({ textAlign: "right" })) {
38-
return "right";
39-
}
40-
if (ctx.editor.isActive({ textAlign: "justify" })) {
41-
return "justify";
42-
}
43-
44-
return "left";
45-
},
46-
};
28+
return (
29+
["left", "center", "right", "justify"].find(align =>
30+
ctx.editor.isActive({ textAlign: align }),
31+
) || "left"
32+
);
4733
},
4834
});
49-
5035
const alignments = [
5136
{
5237
label: t("alignments.left"),
@@ -73,38 +58,39 @@ export const AlignmentAction = () => {
7358
shortcut: "J",
7459
},
7560
];
76-
const activeAlignment = alignments.find(a => a.value === isAlignActive());
61+
const activeAlignment =
62+
alignments.find(a => a.value === activeValue) || alignments[0];
7763

7864
return (
7965
<DropdownMenu>
8066
<DropdownMenuTrigger asChild>
8167
<Button className="w-40 justify-between" variant="ghost">
82-
{activeAlignment?.icon ?? <AlignLeftIcon />}
83-
{t(`alignments.${activeAlignment?.value ?? "left"}`)}
68+
{activeAlignment.icon}
69+
{t(`alignments.${activeAlignment.value}`)}
8470

8571
<ChevronDown className="ml-auto" />
8672
</Button>
8773
</DropdownMenuTrigger>
8874

89-
<DropdownMenuContent className="min-w-[14rem]">
90-
{alignments.map(item => (
91-
<DropdownMenuItem
92-
key={item.value}
93-
onClick={() =>
94-
editor.chain().focus().setTextAlign(item.value).run()
95-
}
96-
className={cn({
97-
"bg-accent": activeAlignment?.value === item.value,
98-
})}
99-
>
100-
{item.icon}
101-
{t(`alignments.${item.value}`)}
102-
<DropdownMenuShortcut>
103-
<CtrlOrCommandCharacter />
104-
+Shift+{item.shortcut}
105-
</DropdownMenuShortcut>
106-
</DropdownMenuItem>
107-
))}
75+
<DropdownMenuContent className="min-w-[16rem]">
76+
<DropdownMenuRadioGroup value={activeAlignment.value}>
77+
{alignments.map(item => (
78+
<DropdownMenuRadioItem
79+
key={item.value}
80+
onClick={() =>
81+
editor.chain().focus().setTextAlign(item.value).run()
82+
}
83+
value={item.value}
84+
>
85+
{item.icon}
86+
{t(`alignments.${item.value}`)}
87+
<DropdownMenuShortcut>
88+
<CtrlOrCommandCharacter />
89+
+Shift+{item.shortcut}
90+
</DropdownMenuShortcut>
91+
</DropdownMenuRadioItem>
92+
))}
93+
</DropdownMenuRadioGroup>
10894
</DropdownMenuContent>
10995
</DropdownMenu>
11096
);

0 commit comments

Comments
 (0)