@rovula/ui 0.0.69 → 0.0.71
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/dist/cjs/bundle.css +23 -9
- package/dist/cjs/bundle.js +3 -3
- package/dist/cjs/bundle.js.map +1 -1
- package/dist/cjs/types/components/Dropdown/Dropdown.styles.d.ts +0 -1
- package/dist/cjs/types/components/FocusedScrollView/FocusedScrollView.d.ts +12 -0
- package/dist/cjs/types/components/FocusedScrollView/FocusedScrollView.stories.d.ts +7 -0
- package/dist/cjs/types/index.d.ts +1 -0
- package/dist/components/Dropdown/Dropdown.js +2 -2
- package/dist/components/Dropdown/Dropdown.stories.js +1 -0
- package/dist/components/Dropdown/Dropdown.styles.js +6 -6
- package/dist/components/DropdownMenu/DropdownMenu.js +2 -2
- package/dist/components/FocusedScrollView/FocusedScrollView.js +67 -0
- package/dist/components/FocusedScrollView/FocusedScrollView.stories.js +33 -0
- package/dist/esm/bundle.css +23 -9
- package/dist/esm/bundle.js +2 -2
- package/dist/esm/bundle.js.map +1 -1
- package/dist/esm/types/components/Dropdown/Dropdown.styles.d.ts +0 -1
- package/dist/esm/types/components/FocusedScrollView/FocusedScrollView.d.ts +12 -0
- package/dist/esm/types/components/FocusedScrollView/FocusedScrollView.stories.d.ts +7 -0
- package/dist/esm/types/index.d.ts +1 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.js +1 -0
- package/dist/src/theme/global.css +30 -11
- package/package.json +1 -1
- package/src/components/Dropdown/Dropdown.stories.tsx +2 -0
- package/src/components/Dropdown/Dropdown.styles.ts +6 -6
- package/src/components/Dropdown/Dropdown.tsx +4 -10
- package/src/components/DropdownMenu/DropdownMenu.tsx +10 -2
- package/src/components/FocusedScrollView/FocusedScrollView.stories.tsx +114 -0
- package/src/components/FocusedScrollView/FocusedScrollView.tsx +112 -0
- package/src/index.ts +1 -0
|
@@ -2,7 +2,6 @@ export declare const iconWrapperVariant: (props?: ({
|
|
|
2
2
|
size?: "sm" | "md" | "lg" | null | undefined;
|
|
3
3
|
} & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
|
|
4
4
|
export declare const dropdownIconVariant: (props?: ({
|
|
5
|
-
size?: "sm" | "md" | "lg" | null | undefined;
|
|
6
5
|
disabled?: boolean | null | undefined;
|
|
7
6
|
isFocus?: boolean | null | undefined;
|
|
8
7
|
} & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React, { ReactNode, HTMLAttributes } from "react";
|
|
2
|
+
type ScrollAlign = "start" | "center" | "end";
|
|
3
|
+
type FocusedScrollViewProps = {
|
|
4
|
+
selectedKey?: string;
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
direction?: "vertical" | "horizontal";
|
|
7
|
+
className?: string;
|
|
8
|
+
containerProps?: HTMLAttributes<HTMLDivElement>;
|
|
9
|
+
scrollAlign?: ScrollAlign;
|
|
10
|
+
};
|
|
11
|
+
export declare const FocusedScrollView: React.FC<FocusedScrollViewProps>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { FocusedScrollView } from "./FocusedScrollView";
|
|
3
|
+
declare const meta: Meta<typeof FocusedScrollView>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof FocusedScrollView>;
|
|
6
|
+
export declare const VerticalScroll: Story;
|
|
7
|
+
export declare const HorizontalScroll: Story;
|
|
@@ -33,6 +33,7 @@ export * from "./components/Toast/Toast";
|
|
|
33
33
|
export * from "./components/Toast/Toaster";
|
|
34
34
|
export * from "./components/Toast/useToast";
|
|
35
35
|
export * from "./components/Tree";
|
|
36
|
+
export * from "./components/FocusedScrollView/FocusedScrollView";
|
|
36
37
|
export type { ButtonProps } from "./components/Button/Button";
|
|
37
38
|
export type { InputProps } from "./components/TextInput/TextInput";
|
|
38
39
|
export type { DropdownProps, Options } from "./components/Dropdown/Dropdown";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import React__default, { ReactElement, ReactNode, CSSProperties, FC, ComponentPropsWithoutRef } from 'react';
|
|
2
|
+
import React__default, { ReactElement, ReactNode, CSSProperties, FC, ComponentPropsWithoutRef, HTMLAttributes } from 'react';
|
|
3
3
|
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
|
4
4
|
import * as class_variance_authority_dist_types from 'class-variance-authority/dist/types';
|
|
5
5
|
import * as LabelPrimitive from '@radix-ui/react-label';
|
|
@@ -736,6 +736,17 @@ declare const Tree: FC<TreeProps>;
|
|
|
736
736
|
|
|
737
737
|
declare const TreeItem: FC<TreeItemProps>;
|
|
738
738
|
|
|
739
|
+
type ScrollAlign = "start" | "center" | "end";
|
|
740
|
+
type FocusedScrollViewProps = {
|
|
741
|
+
selectedKey?: string;
|
|
742
|
+
children: ReactNode;
|
|
743
|
+
direction?: "vertical" | "horizontal";
|
|
744
|
+
className?: string;
|
|
745
|
+
containerProps?: HTMLAttributes<HTMLDivElement>;
|
|
746
|
+
scrollAlign?: ScrollAlign;
|
|
747
|
+
};
|
|
748
|
+
declare const FocusedScrollView: React__default.FC<FocusedScrollViewProps>;
|
|
749
|
+
|
|
739
750
|
declare const resloveTimestamp: (timestamp: number) => number;
|
|
740
751
|
declare const getStartDateOfDay: (date: Date) => Date;
|
|
741
752
|
declare const getEndDateOfDay: (date: Date) => Date;
|
|
@@ -749,4 +760,4 @@ declare function usePrevious<T>(value: T): T | undefined;
|
|
|
749
760
|
|
|
750
761
|
declare function cn(...inputs: ClassValue[]): string;
|
|
751
762
|
|
|
752
|
-
export { ActionButton, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, Button, type ButtonProps, Calendar, Checkbox, Collapsible, DataTable, type DataTableProps, DatePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Dropdown, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, type DropdownProps, Icon, Input, InputFilter, type InputFilterProps, type InputProps, Label, Loading, Navbar, type NavbarProps, type Options$1 as Options, Popover, PopoverContent, PopoverTrigger, ProgressBar, Search, type SearchProps, Slider, type SliderProps, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, Text, TextInput, Toast$1 as Toast, ToastAction, type ToastActionElement, ToastClose, ToastDescription, type ToastProps, ToastProvider, ToastTitle, ToastViewport, Toaster, Tooltip, TooltipArrow, TooltipContent, TooltipProvider, TooltipSimple, TooltipTrigger, Tree, type TreeData, TreeItem, type TreeItemProps, type TreeProps, cn, getEndDateOfDay, getStartDateOfDay, getStartEndTimestampOfDay, getTimestampUTC, reducer, resloveTimestamp, toast, usePrevious, useToast };
|
|
763
|
+
export { ActionButton, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, Button, type ButtonProps, Calendar, Checkbox, Collapsible, DataTable, type DataTableProps, DatePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Dropdown, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, type DropdownProps, FocusedScrollView, Icon, Input, InputFilter, type InputFilterProps, type InputProps, Label, Loading, Navbar, type NavbarProps, type Options$1 as Options, Popover, PopoverContent, PopoverTrigger, ProgressBar, Search, type SearchProps, Slider, type SliderProps, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, Text, TextInput, Toast$1 as Toast, ToastAction, type ToastActionElement, ToastClose, ToastDescription, type ToastProps, ToastProvider, ToastTitle, ToastViewport, Toaster, Tooltip, TooltipArrow, TooltipContent, TooltipProvider, TooltipSimple, TooltipTrigger, Tree, type TreeData, TreeItem, type TreeItemProps, type TreeProps, cn, getEndDateOfDay, getStartDateOfDay, getStartEndTimestampOfDay, getTimestampUTC, reducer, resloveTimestamp, toast, usePrevious, useToast };
|
package/dist/index.js
CHANGED
|
@@ -35,6 +35,7 @@ export * from "./components/Toast/Toast";
|
|
|
35
35
|
export * from "./components/Toast/Toaster";
|
|
36
36
|
export * from "./components/Toast/useToast";
|
|
37
37
|
export * from "./components/Tree";
|
|
38
|
+
export * from "./components/FocusedScrollView/FocusedScrollView";
|
|
38
39
|
// UTILS
|
|
39
40
|
export { resloveTimestamp, getStartDateOfDay, getEndDateOfDay, getStartEndTimestampOfDay, getTimestampUTC, } from "./utils/datetime";
|
|
40
41
|
// Hooks
|
|
@@ -2361,6 +2361,11 @@ input[type=number] {
|
|
|
2361
2361
|
margin-right: -0.5rem;
|
|
2362
2362
|
}
|
|
2363
2363
|
|
|
2364
|
+
.mx-2 {
|
|
2365
|
+
margin-left: 0.5rem;
|
|
2366
|
+
margin-right: 0.5rem;
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2364
2369
|
.mx-4 {
|
|
2365
2370
|
margin-left: 1rem;
|
|
2366
2371
|
margin-right: 1rem;
|
|
@@ -2452,6 +2457,10 @@ input[type=number] {
|
|
|
2452
2457
|
display: block;
|
|
2453
2458
|
}
|
|
2454
2459
|
|
|
2460
|
+
.inline-block {
|
|
2461
|
+
display: inline-block;
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2455
2464
|
.flex {
|
|
2456
2465
|
display: flex;
|
|
2457
2466
|
}
|
|
@@ -2663,6 +2672,10 @@ input[type=number] {
|
|
|
2663
2672
|
width: 0.5rem;
|
|
2664
2673
|
}
|
|
2665
2674
|
|
|
2675
|
+
.w-28 {
|
|
2676
|
+
width: 7rem;
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2666
2679
|
.w-3 {
|
|
2667
2680
|
width: 0.75rem;
|
|
2668
2681
|
}
|
|
@@ -2753,6 +2766,10 @@ input[type=number] {
|
|
|
2753
2766
|
min-width: fit-content;
|
|
2754
2767
|
}
|
|
2755
2768
|
|
|
2769
|
+
.max-w-full {
|
|
2770
|
+
max-width: 100%;
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2756
2773
|
.max-w-lg {
|
|
2757
2774
|
max-width: 32rem;
|
|
2758
2775
|
}
|
|
@@ -3027,6 +3044,10 @@ input[type=number] {
|
|
|
3027
3044
|
text-overflow: ellipsis;
|
|
3028
3045
|
}
|
|
3029
3046
|
|
|
3047
|
+
.whitespace-nowrap {
|
|
3048
|
+
white-space: nowrap;
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3030
3051
|
.rounded {
|
|
3031
3052
|
border-radius: 0.25rem;
|
|
3032
3053
|
}
|
|
@@ -4889,6 +4910,11 @@ input[type=number] {
|
|
|
4889
4910
|
background-color: color-mix(in srgb, var(--other-transparency-warning-8) calc(100% * var(--tw-bg-opacity, 1)), transparent);
|
|
4890
4911
|
}
|
|
4891
4912
|
|
|
4913
|
+
.bg-white {
|
|
4914
|
+
--tw-bg-opacity: 1;
|
|
4915
|
+
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
|
|
4916
|
+
}
|
|
4917
|
+
|
|
4892
4918
|
.bg-white-transparent-12 {
|
|
4893
4919
|
--tw-bg-opacity: 1;
|
|
4894
4920
|
background-color: color-mix(in srgb, var(--other-transparency-white-12) calc(100% * var(--tw-bg-opacity, 1)), transparent);
|
|
@@ -5545,6 +5571,10 @@ input[type=number] {
|
|
|
5545
5571
|
text-transform: capitalize;
|
|
5546
5572
|
}
|
|
5547
5573
|
|
|
5574
|
+
.leading-\[3rem\] {
|
|
5575
|
+
line-height: 3rem;
|
|
5576
|
+
}
|
|
5577
|
+
|
|
5548
5578
|
.leading-none {
|
|
5549
5579
|
line-height: 1;
|
|
5550
5580
|
}
|
|
@@ -5936,12 +5966,6 @@ input[type=number] {
|
|
|
5936
5966
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
|
5937
5967
|
}
|
|
5938
5968
|
|
|
5939
|
-
.shadow-\[0px_2px_24px_0px_rgba\(145\2c _158\2c _171\2c _0\.24\)\] {
|
|
5940
|
-
--tw-shadow: 0px 2px 24px 0px rgba(145, 158, 171, 0.24);
|
|
5941
|
-
--tw-shadow-colored: 0px 2px 24px 0px var(--tw-shadow-color);
|
|
5942
|
-
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
|
5943
|
-
}
|
|
5944
|
-
|
|
5945
5969
|
.shadow-lg {
|
|
5946
5970
|
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
5947
5971
|
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
|
|
@@ -5954,11 +5978,6 @@ input[type=number] {
|
|
|
5954
5978
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
|
5955
5979
|
}
|
|
5956
5980
|
|
|
5957
|
-
.shadow-\[var\(--dropdown-menu-shadow\)\] {
|
|
5958
|
-
--tw-shadow-color: var(--dropdown-menu-shadow);
|
|
5959
|
-
--tw-shadow: var(--tw-shadow-colored);
|
|
5960
|
-
}
|
|
5961
|
-
|
|
5962
5981
|
.outline-none {
|
|
5963
5982
|
outline: 2px solid transparent;
|
|
5964
5983
|
outline-offset: 2px;
|
package/package.json
CHANGED
|
@@ -19,11 +19,12 @@ export const iconWrapperVariant = cva(
|
|
|
19
19
|
|
|
20
20
|
export const dropdownIconVariant = cva(["transition-all"], {
|
|
21
21
|
variants: {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
// Controll by text-input
|
|
23
|
+
// size: {
|
|
24
|
+
// sm: "size-[14px]",
|
|
25
|
+
// md: "size-5",
|
|
26
|
+
// lg: "size-6",
|
|
27
|
+
// },
|
|
27
28
|
disabled: {
|
|
28
29
|
true: "fill-input-text-disabled",
|
|
29
30
|
false: "fill-inherit",
|
|
@@ -34,7 +35,6 @@ export const dropdownIconVariant = cva(["transition-all"], {
|
|
|
34
35
|
},
|
|
35
36
|
},
|
|
36
37
|
defaultVariants: {
|
|
37
|
-
size: "md",
|
|
38
38
|
disabled: false,
|
|
39
39
|
isFocus: false,
|
|
40
40
|
},
|
|
@@ -12,11 +12,7 @@ import React, {
|
|
|
12
12
|
} from "react";
|
|
13
13
|
import * as Portal from "@radix-ui/react-portal";
|
|
14
14
|
import TextInput, { InputProps } from "../TextInput/TextInput";
|
|
15
|
-
import {
|
|
16
|
-
customInputVariant,
|
|
17
|
-
dropdownIconVariant,
|
|
18
|
-
iconWrapperVariant,
|
|
19
|
-
} from "./Dropdown.styles";
|
|
15
|
+
import { customInputVariant, dropdownIconVariant } from "./Dropdown.styles";
|
|
20
16
|
|
|
21
17
|
import { ChevronDownIcon } from "@heroicons/react/16/solid";
|
|
22
18
|
import { cn } from "@/utils/cn";
|
|
@@ -339,11 +335,9 @@ const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
|
|
|
339
335
|
<TextInput
|
|
340
336
|
hasClearIcon={false}
|
|
341
337
|
endIcon={
|
|
342
|
-
<
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
/>
|
|
346
|
-
</div>
|
|
338
|
+
<ChevronDownIcon
|
|
339
|
+
className={dropdownIconVariant({ isFocus: isFocused })}
|
|
340
|
+
/>
|
|
347
341
|
}
|
|
348
342
|
{...props}
|
|
349
343
|
ref={inputRef}
|
|
@@ -52,10 +52,14 @@ const DropdownMenuSubContent = React.forwardRef<
|
|
|
52
52
|
<DropdownMenuPrimitive.SubContent
|
|
53
53
|
ref={ref}
|
|
54
54
|
className={cn(
|
|
55
|
-
"z-50 min-w-[154px] overflow-hidden rounded-md bg-base-popup text-base-popup-foreground
|
|
55
|
+
"z-50 min-w-[154px] overflow-hidden rounded-md bg-base-popup text-base-popup-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
56
56
|
className
|
|
57
57
|
)}
|
|
58
58
|
{...props}
|
|
59
|
+
style={{
|
|
60
|
+
boxShadow: "var(--dropdown-menu-shadow)",
|
|
61
|
+
...props.style,
|
|
62
|
+
}}
|
|
59
63
|
/>
|
|
60
64
|
));
|
|
61
65
|
DropdownMenuSubContent.displayName =
|
|
@@ -70,10 +74,14 @@ const DropdownMenuContent = React.forwardRef<
|
|
|
70
74
|
ref={ref}
|
|
71
75
|
sideOffset={sideOffset}
|
|
72
76
|
className={cn(
|
|
73
|
-
"z-50 min-w-[154px] overflow-hidden rounded-md bg-base-popup text-base-popup-foreground
|
|
77
|
+
"z-50 min-w-[154px] overflow-hidden rounded-md bg-base-popup text-base-popup-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
74
78
|
className
|
|
75
79
|
)}
|
|
76
80
|
{...props}
|
|
81
|
+
style={{
|
|
82
|
+
boxShadow: "var(--dropdown-menu-shadow)",
|
|
83
|
+
...props.style,
|
|
84
|
+
}}
|
|
77
85
|
/>
|
|
78
86
|
</DropdownMenuPrimitive.Portal>
|
|
79
87
|
));
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import React, { useState } from "react";
|
|
3
|
+
import { FocusedScrollView } from "./FocusedScrollView";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof FocusedScrollView> = {
|
|
6
|
+
title: "Components/FocusedScrollView",
|
|
7
|
+
component: FocusedScrollView,
|
|
8
|
+
args: {
|
|
9
|
+
direction: "vertical",
|
|
10
|
+
scrollAlign: "center",
|
|
11
|
+
className: "border border-input-default-stroke rounded",
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
|
|
17
|
+
type Story = StoryObj<typeof FocusedScrollView>;
|
|
18
|
+
|
|
19
|
+
const items = Array.from({ length: 30 }, (_, i) => `Item ${i + 1}`);
|
|
20
|
+
|
|
21
|
+
export const VerticalScroll: Story = {
|
|
22
|
+
render: (args) => {
|
|
23
|
+
const [selected, setSelected] = useState("item-5");
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="p-6 space-y-4">
|
|
27
|
+
<div className="flex gap-2">
|
|
28
|
+
<button
|
|
29
|
+
onClick={() => setSelected("item-5")}
|
|
30
|
+
className="px-3 py-1 bg-primary text-white rounded"
|
|
31
|
+
>
|
|
32
|
+
Scroll to Item 5
|
|
33
|
+
</button>
|
|
34
|
+
<button
|
|
35
|
+
onClick={() => setSelected("item-20")}
|
|
36
|
+
className="px-3 py-1 bg-primary text-white rounded"
|
|
37
|
+
>
|
|
38
|
+
Scroll to Item 20
|
|
39
|
+
</button>
|
|
40
|
+
<button
|
|
41
|
+
onClick={() => setSelected("item-29")}
|
|
42
|
+
className="px-3 py-1 bg-primary text-white rounded"
|
|
43
|
+
>
|
|
44
|
+
Scroll to Item 30
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<FocusedScrollView {...args} selectedKey={selected}>
|
|
49
|
+
{items.map((item, index) => (
|
|
50
|
+
<div
|
|
51
|
+
key={`item-${index + 1}`}
|
|
52
|
+
className={`px-4 py-2 ${
|
|
53
|
+
selected === `item-${index + 1}`
|
|
54
|
+
? "bg-secondary text-secondary-foreground"
|
|
55
|
+
: "bg-white"
|
|
56
|
+
}`}
|
|
57
|
+
>
|
|
58
|
+
{item}
|
|
59
|
+
</div>
|
|
60
|
+
))}
|
|
61
|
+
</FocusedScrollView>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const HorizontalScroll: Story = {
|
|
68
|
+
args: {
|
|
69
|
+
direction: "horizontal",
|
|
70
|
+
},
|
|
71
|
+
render: (args) => {
|
|
72
|
+
const [selected, setSelected] = useState("item-15");
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div className="p-6 space-y-4">
|
|
76
|
+
<div className="flex gap-2">
|
|
77
|
+
<button
|
|
78
|
+
onClick={() => setSelected("item-5")}
|
|
79
|
+
className="px-3 py-1 bg-primary text-white rounded"
|
|
80
|
+
>
|
|
81
|
+
Scroll to Item 5
|
|
82
|
+
</button>
|
|
83
|
+
<button
|
|
84
|
+
onClick={() => setSelected("item-15")}
|
|
85
|
+
className="px-3 py-1 bg-primary text-white rounded"
|
|
86
|
+
>
|
|
87
|
+
Scroll to Item 15
|
|
88
|
+
</button>
|
|
89
|
+
<button
|
|
90
|
+
onClick={() => setSelected("item-25")}
|
|
91
|
+
className="px-3 py-1 bg-primary text-white rounded"
|
|
92
|
+
>
|
|
93
|
+
Scroll to Item 25
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<FocusedScrollView {...args} selectedKey={selected}>
|
|
98
|
+
{items.map((item, index) => (
|
|
99
|
+
<div
|
|
100
|
+
key={`item-${index + 1}`}
|
|
101
|
+
className={`inline-block w-28 h-12 mx-2 text-center leading-[3rem] rounded ${
|
|
102
|
+
selected === `item-${index + 1}`
|
|
103
|
+
? "bg-secondary text-secondary-foreground"
|
|
104
|
+
: "bg-gray-200"
|
|
105
|
+
}`}
|
|
106
|
+
>
|
|
107
|
+
{item}
|
|
108
|
+
</div>
|
|
109
|
+
))}
|
|
110
|
+
</FocusedScrollView>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
},
|
|
114
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useRef,
|
|
3
|
+
useEffect,
|
|
4
|
+
cloneElement,
|
|
5
|
+
ReactNode,
|
|
6
|
+
isValidElement,
|
|
7
|
+
HTMLAttributes,
|
|
8
|
+
} from "react";
|
|
9
|
+
|
|
10
|
+
type ScrollAlign = "start" | "center" | "end";
|
|
11
|
+
|
|
12
|
+
type FocusedScrollViewProps = {
|
|
13
|
+
selectedKey?: string;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
direction?: "vertical" | "horizontal";
|
|
16
|
+
className?: string;
|
|
17
|
+
containerProps?: HTMLAttributes<HTMLDivElement>;
|
|
18
|
+
scrollAlign?: ScrollAlign;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const FocusedScrollView: React.FC<FocusedScrollViewProps> = ({
|
|
22
|
+
selectedKey,
|
|
23
|
+
children,
|
|
24
|
+
direction = "vertical",
|
|
25
|
+
className,
|
|
26
|
+
containerProps,
|
|
27
|
+
scrollAlign = "center",
|
|
28
|
+
}) => {
|
|
29
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
30
|
+
const itemRefs = useRef<Record<string, HTMLDivElement | null>>({});
|
|
31
|
+
|
|
32
|
+
const scrollToItem = (key: string) => {
|
|
33
|
+
const container = containerRef.current;
|
|
34
|
+
const item = itemRefs.current[key];
|
|
35
|
+
|
|
36
|
+
if (container && item) {
|
|
37
|
+
if (direction === "vertical") {
|
|
38
|
+
const containerTop = container.getBoundingClientRect().top;
|
|
39
|
+
const itemTop = item.getBoundingClientRect().top;
|
|
40
|
+
const offset = itemTop - containerTop + container.scrollTop;
|
|
41
|
+
|
|
42
|
+
const containerHeight = container.clientHeight;
|
|
43
|
+
const itemHeight = item.offsetHeight;
|
|
44
|
+
|
|
45
|
+
let targetTop = offset;
|
|
46
|
+
|
|
47
|
+
if (scrollAlign === "center") {
|
|
48
|
+
targetTop = offset - (containerHeight / 2 - itemHeight / 2);
|
|
49
|
+
} else if (scrollAlign === "end") {
|
|
50
|
+
targetTop = offset + (itemHeight - containerHeight);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
container.scrollTo({
|
|
54
|
+
top: targetTop,
|
|
55
|
+
behavior: "smooth",
|
|
56
|
+
});
|
|
57
|
+
} else if (direction === "horizontal") {
|
|
58
|
+
const containerLeft = container.getBoundingClientRect().left;
|
|
59
|
+
const itemLeft = item.getBoundingClientRect().left;
|
|
60
|
+
const offset = itemLeft - containerLeft + container.scrollLeft;
|
|
61
|
+
|
|
62
|
+
const containerWidth = container.clientWidth;
|
|
63
|
+
const itemWidth = item.offsetWidth;
|
|
64
|
+
|
|
65
|
+
let targetLeft = offset;
|
|
66
|
+
|
|
67
|
+
if (scrollAlign === "center") {
|
|
68
|
+
targetLeft = offset - (containerWidth / 2 - itemWidth / 2);
|
|
69
|
+
} else if (scrollAlign === "end") {
|
|
70
|
+
targetLeft = offset + (itemWidth - containerWidth);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
container.scrollTo({
|
|
74
|
+
left: targetLeft,
|
|
75
|
+
behavior: "smooth",
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (selectedKey) {
|
|
83
|
+
scrollToItem(selectedKey);
|
|
84
|
+
}
|
|
85
|
+
}, [selectedKey, direction, scrollAlign]);
|
|
86
|
+
|
|
87
|
+
const enhancedChildren = Array.isArray(children)
|
|
88
|
+
? children.map((child) => {
|
|
89
|
+
if (isValidElement(child) && child.key) {
|
|
90
|
+
return cloneElement(child, {
|
|
91
|
+
// @ts-ignore
|
|
92
|
+
ref: (el: HTMLDivElement) => {
|
|
93
|
+
itemRefs.current[String(child.key)] = el;
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return child;
|
|
98
|
+
})
|
|
99
|
+
: children;
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div
|
|
103
|
+
ref={containerRef}
|
|
104
|
+
className={`overflow-auto ${
|
|
105
|
+
direction === "vertical" ? "max-h-60" : "max-w-full whitespace-nowrap"
|
|
106
|
+
} ${className ?? ""}`}
|
|
107
|
+
{...containerProps}
|
|
108
|
+
>
|
|
109
|
+
{enhancedChildren}
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -40,6 +40,7 @@ export * from "./components/Toast/Toast";
|
|
|
40
40
|
export * from "./components/Toast/Toaster";
|
|
41
41
|
export * from "./components/Toast/useToast";
|
|
42
42
|
export * from "./components/Tree";
|
|
43
|
+
export * from "./components/FocusedScrollView/FocusedScrollView";
|
|
43
44
|
|
|
44
45
|
// Export component types
|
|
45
46
|
export type { ButtonProps } from "./components/Button/Button";
|