addon-ui 0.5.0 → 0.6.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.
Files changed (47) hide show
  1. package/dist-types/components/Avatar/types.d.ts +1 -0
  2. package/dist-types/components/Button/types.d.ts +3 -1
  3. package/dist-types/components/Tabs/Tabs.d.ts +8 -0
  4. package/dist-types/components/Tabs/TabsContent.d.ts +6 -0
  5. package/dist-types/components/Tabs/TabsList.d.ts +11 -0
  6. package/dist-types/components/Tabs/TabsTrigger.d.ts +11 -0
  7. package/dist-types/components/Tabs/index.d.ts +6 -0
  8. package/dist-types/components/Tabs/types.d.ts +6 -0
  9. package/dist-types/components/Toast/Toast.d.ts +3 -1
  10. package/dist-types/components/Toast/types.d.ts +4 -0
  11. package/dist-types/components/Truncate/Truncate.d.ts +9 -0
  12. package/dist-types/components/Truncate/index.d.ts +2 -0
  13. package/dist-types/components/TruncateList/TruncateList.d.ts +13 -0
  14. package/dist-types/components/TruncateList/index.d.ts +2 -0
  15. package/dist-types/components/index.d.ts +3 -0
  16. package/dist-types/components/types.d.ts +7 -1
  17. package/package.json +8 -6
  18. package/src/components/Avatar/avatar.module.scss +4 -0
  19. package/src/components/Avatar/types.ts +1 -0
  20. package/src/components/Button/button.module.scss +32 -4
  21. package/src/components/Button/types.ts +2 -0
  22. package/src/components/List/list.module.scss +2 -1
  23. package/src/components/ListItem/list-item.module.scss +1 -1
  24. package/src/components/ScrollArea/ScrollArea.tsx +4 -17
  25. package/src/components/ScrollArea/scroll-area.module.scss +10 -2
  26. package/src/components/Tabs/Tabs.tsx +32 -0
  27. package/src/components/Tabs/TabsContent.tsx +21 -0
  28. package/src/components/Tabs/TabsList.tsx +131 -0
  29. package/src/components/Tabs/TabsTrigger.tsx +43 -0
  30. package/src/components/Tabs/index.ts +5 -0
  31. package/src/components/Tabs/tabs.module.scss +166 -0
  32. package/src/components/Tabs/types.ts +5 -0
  33. package/src/components/Tag/tag.module.scss +10 -7
  34. package/src/components/TextField/text-field.module.scss +6 -2
  35. package/src/components/Toast/Toast.tsx +7 -1
  36. package/src/components/Toast/toast.module.scss +56 -13
  37. package/src/components/Toast/types.ts +5 -0
  38. package/src/components/Truncate/Truncate.tsx +112 -0
  39. package/src/components/Truncate/index.ts +1 -0
  40. package/src/components/Truncate/truncate.module.scss +20 -0
  41. package/src/components/TruncateList/TruncateList.tsx +47 -0
  42. package/src/components/TruncateList/index.ts +1 -0
  43. package/src/components/TruncateList/truncate-list.module.scss +20 -0
  44. package/src/components/Viewport/Provider.tsx +1 -1
  45. package/src/components/Viewport/viewport.module.scss +7 -6
  46. package/src/components/index.ts +3 -0
  47. package/src/components/types.ts +12 -0
@@ -4,6 +4,7 @@ export declare enum AvatarSize {
4
4
  Large = "large"
5
5
  }
6
6
  export declare enum AvatarRadius {
7
+ None = "none",
7
8
  Small = "small",
8
9
  Medium = "medium",
9
10
  Large = "large"
@@ -6,7 +6,9 @@ export declare enum ButtonVariant {
6
6
  export declare enum ButtonColor {
7
7
  Primary = "primary",
8
8
  Secondary = "secondary",
9
- Accent = "accent"
9
+ Accent = "accent",
10
+ Error = "error",
11
+ Success = "success"
10
12
  }
11
13
  export declare enum ButtonSize {
12
14
  Small = "small",
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ import { TabsProps as TabsRadixProps } from "@radix-ui/react-tabs";
3
+ export interface TabsProps extends Omit<TabsRadixProps, "orientation"> {
4
+ reverse?: boolean;
5
+ }
6
+ declare const _default: React.NamedExoticComponent<TabsProps & React.RefAttributes<HTMLDivElement>>;
7
+ export default _default;
8
+ //# sourceMappingURL=Tabs.d.ts.map
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ import { TabsContentProps as TabsContentRadixProps } from "@radix-ui/react-tabs";
3
+ export type TabsContentProps = TabsContentRadixProps;
4
+ declare const _default: React.NamedExoticComponent<TabsContentRadixProps & React.RefAttributes<HTMLDivElement>>;
5
+ export default _default;
6
+ //# sourceMappingURL=TabsContent.d.ts.map
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { TabsListProps as TabsListRadixProps } from "@radix-ui/react-tabs";
3
+ export interface TabsListProps extends TabsListRadixProps {
4
+ separator?: boolean;
5
+ indicator?: boolean;
6
+ roundedEdges?: boolean;
7
+ indicatorClassname?: string;
8
+ }
9
+ declare const _default: React.NamedExoticComponent<TabsListProps & React.RefAttributes<HTMLDivElement>>;
10
+ export default _default;
11
+ //# sourceMappingURL=TabsList.d.ts.map
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { TabsTriggerProps as TabsTriggerRadixProps } from "@radix-ui/react-tabs";
3
+ export interface TabsTriggerProps extends TabsTriggerRadixProps {
4
+ before?: number | string;
5
+ after?: number | string;
6
+ afterClassname?: string;
7
+ beforeClassname?: string;
8
+ }
9
+ declare const _default: React.NamedExoticComponent<TabsTriggerProps & React.RefAttributes<HTMLButtonElement>>;
10
+ export default _default;
11
+ //# sourceMappingURL=TabsTrigger.d.ts.map
@@ -0,0 +1,6 @@
1
+ export * from "./types";
2
+ export { default as Tabs, type TabsProps } from "./Tabs";
3
+ export { default as TabsList, type TabsListProps } from "./TabsList";
4
+ export { default as TabsTrigger, type TabsTriggerProps } from "./TabsTrigger";
5
+ export { default as TabsContent, type TabsContentProps } from "./TabsContent";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,6 @@
1
+ export declare enum TabsColor {
2
+ Primary = "primary",
3
+ Secondary = "secondary",
4
+ Accent = "accent"
5
+ }
6
+ //# sourceMappingURL=types.d.ts.map
@@ -1,13 +1,15 @@
1
1
  import React, { ReactElement, ReactNode } from "react";
2
2
  import { ToastProps as ToastRootProps, ToastProviderProps } from "@radix-ui/react-toast";
3
3
  import { IconButtonProps } from "../IconButton";
4
- import { ToastSide, ToastRadius, ToastColor } from "./types";
4
+ import { ToastSide, ToastRadius, ToastColor, ToastAnimation } from "./types";
5
5
  export interface ToastProps extends Omit<ToastRootProps, "title">, Omit<ToastProviderProps, "children"> {
6
6
  side?: ToastSide;
7
7
  color?: ToastColor;
8
8
  radius?: ToastRadius;
9
9
  title?: ReactNode;
10
10
  action?: ReactNode;
11
+ animationIn?: ToastAnimation;
12
+ animationOut?: ToastAnimation;
11
13
  description?: ReactNode;
12
14
  closeIcon?: ReactElement;
13
15
  closeProps?: IconButtonProps;
@@ -16,4 +16,8 @@ export declare enum ToastColor {
16
16
  Error = "error",
17
17
  Success = "success"
18
18
  }
19
+ export declare enum ToastAnimation {
20
+ Slide = "slide",
21
+ Opacity = "opacity"
22
+ }
19
23
  //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,9 @@
1
+ import React, { ComponentProps } from "react";
2
+ export interface TruncateProps extends ComponentProps<"span"> {
3
+ text?: string;
4
+ middle?: boolean;
5
+ separator?: string;
6
+ }
7
+ declare const _default: React.NamedExoticComponent<Omit<TruncateProps, "ref"> & React.RefAttributes<HTMLSpanElement>>;
8
+ export default _default;
9
+ //# sourceMappingURL=Truncate.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { default as Truncate, type TruncateProps } from "./Truncate";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ import { OverflowListProps } from "react-responsive-overflow-list";
3
+ export type TruncateListProps<T = unknown> = OverflowListProps<T> & {
4
+ counterClassName?: string;
5
+ };
6
+ declare const TruncateList: {
7
+ <T>(props: TruncateListProps<T> & {
8
+ ref?: React.Ref<HTMLDivElement>;
9
+ }): React.ReactElement | null;
10
+ displayName?: string;
11
+ };
12
+ export default TruncateList;
13
+ //# sourceMappingURL=TruncateList.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { default as TruncateList, type TruncateListProps } from "./TruncateList";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -16,11 +16,14 @@ export * from "./Odometer";
16
16
  export * from "./ScrollArea";
17
17
  export * from "./SvgSprite";
18
18
  export * from "./Switch";
19
+ export * from "./Tabs";
19
20
  export * from "./Tag";
20
21
  export * from "./TextArea";
21
22
  export * from "./TextField";
22
23
  export * from "./Toast";
23
24
  export * from "./Tooltip";
25
+ export * from "./Truncate";
26
+ export * from "./TruncateList";
24
27
  export * from "./View";
25
28
  export * from "./ViewDrawer";
26
29
  export * from "./ViewModal";
@@ -1,4 +1,4 @@
1
- import type { AvatarProps, ButtonProps, CheckboxProps, DialogProps, DrawerProps, FooterProps, HeaderProps, HighlightProps, IconProps, IconButtonProps, ListProps, ListItemProps, ModalProps, OdometerProps, ScrollAreaProps, SwitchProps, TagProps, TextAreaProps, TextFieldProps, ToastProps, TooltipProps, ViewProps, ViewDrawerProps, ViewModalProps } from "../components";
1
+ import type { AvatarProps, ButtonProps, CheckboxProps, DialogProps, DrawerProps, FooterProps, HeaderProps, HighlightProps, IconProps, IconButtonProps, ListProps, ListItemProps, ModalProps, OdometerProps, ScrollAreaProps, SwitchProps, TabsProps, TabsContentProps, TabsListProps, TabsTriggerProps, TagProps, TextAreaProps, TextFieldProps, ToastProps, TooltipProps, TruncateProps, TruncateListProps, ViewProps, ViewDrawerProps, ViewModalProps } from "../components";
2
2
  export interface ComponentsProps {
3
3
  avatar?: Pick<AvatarProps, "size" | "radius" | "cursorPointer" | "delayMs">;
4
4
  button?: Pick<ButtonProps, "variant" | "color" | "size" | "radius">;
@@ -16,11 +16,17 @@ export interface ComponentsProps {
16
16
  odometer?: Pick<OdometerProps, "auto" | "format" | "duration">;
17
17
  scrollArea?: ScrollAreaProps;
18
18
  switch?: SwitchProps;
19
+ tabs?: TabsProps;
20
+ tabsContent?: TabsContentProps;
21
+ tabsList?: TabsListProps;
22
+ tabsTrigger?: TabsTriggerProps;
19
23
  tag?: Pick<TagProps, "variant" | "size" | "color" | "radius" | "clickable">;
20
24
  textArea?: TextAreaProps;
21
25
  textField?: TextFieldProps;
22
26
  toast?: Pick<ToastProps, "side" | "duration" | "swipeDirection" | "swipeThreshold" | "closeProps" | "closeIcon" | "fullWidth" | "sticky" | "radius" | "color">;
23
27
  tooltip?: Omit<TooltipProps, "content">;
28
+ truncate?: TruncateProps;
29
+ truncateList?: TruncateListProps;
24
30
  view?: ViewProps;
25
31
  viewDrawer?: ViewDrawerProps;
26
32
  viewModal?: ViewModalProps;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "addon-ui",
3
3
  "type": "module",
4
- "version": "0.5.0",
4
+ "version": "0.6.0",
5
5
  "description": "A comprehensive React UI component library designed exclusively for the AddonBone browser extension framework with customizable theming and consistent design patterns",
6
6
  "keywords": [
7
7
  "react",
@@ -83,18 +83,20 @@
83
83
  "odometer": "^0.4.8",
84
84
  "radix-ui": "^1.1.3",
85
85
  "react-highlight-words": "^0.21.0",
86
+ "react-responsive-overflow-list": "^0.2.1",
86
87
  "sass": "^1.85.1",
87
88
  "ts-deepmerge": "^7.0.3"
88
89
  },
89
90
  "devDependencies": {
90
91
  "@commitlint/cli": "^20.0.0",
91
92
  "@commitlint/config-conventional": "^20.0.0",
92
- "@release-it/conventional-changelog": "^10.0.1",
93
- "@types/chrome": "^0.1.12",
94
- "@types/jest": "^30.0.0",
95
93
  "@eslint/js": "^9.21.0",
94
+ "@release-it/conventional-changelog": "^10.0.1",
96
95
  "@rsbuild/plugin-sass": "^1.4.0",
97
96
  "@storybook/react": "^9.1.3",
97
+ "@types/chrome": "^0.1.12",
98
+ "@types/jest": "^30.0.0",
99
+ "@types/lodash": "^4.17.20",
98
100
  "@types/node": "^22.13.10",
99
101
  "@types/react": "^19.0.10",
100
102
  "@types/react-dom": "^19.0.4",
@@ -104,12 +106,12 @@
104
106
  "eslint-plugin-react-hooks": "^5.1.0",
105
107
  "eslint-plugin-react-refresh": "^0.4.19",
106
108
  "globals": "^15.15.0",
107
- "prettier": "^3.5.3",
108
109
  "husky": "^9.1.7",
109
110
  "jest": "^30.1.3",
110
- "release-it": "^19.0.5",
111
+ "prettier": "^3.5.3",
111
112
  "react": "^19.1.0",
112
113
  "react-dom": "^19.1.0",
114
+ "release-it": "^19.0.5",
113
115
  "rspack-plugin-virtual-module": "^1.0.0",
114
116
  "storybook": "^9.1.3",
115
117
  "storybook-react-rsbuild": "^2.1.0",
@@ -42,6 +42,10 @@ $root: avatar;
42
42
  }
43
43
 
44
44
  // Radius
45
+ &--none-radius {
46
+ border-radius: 0;
47
+ }
48
+
45
49
  &--small-radius {
46
50
  border-radius: var(--avatar-border-radius-sm, 20%);
47
51
  }
@@ -5,6 +5,7 @@ export enum AvatarSize {
5
5
  }
6
6
 
7
7
  export enum AvatarRadius {
8
+ None = "none",
8
9
  Small = "small",
9
10
  Medium = "medium",
10
11
  Large = "large",
@@ -5,7 +5,7 @@ $root: button;
5
5
  font-weight: var(--button-font-weight, 500);
6
6
  font-size: var(--button-font-size, var(--font-size, 14px));
7
7
  letter-spacing: var(--button-letter-spacing, 0.5px);
8
- line-height: var(--button-line-height, var(--line-height, 1 rem));
8
+ line-height: var(--button-line-height, var(--line-height, 1rem));
9
9
  height: var(--button-height, 34px);
10
10
  border-radius: var(--button-border-radius, 10px);
11
11
  padding: var(--button-padding, 0 16px);
@@ -38,6 +38,16 @@ $root: button;
38
38
  color: #fff;
39
39
  background: var(--accent-color);
40
40
  }
41
+
42
+ &.#{$root}--error-color {
43
+ color: #fff;
44
+ background: var(--error-color);
45
+ }
46
+
47
+ &.#{$root}--success-color {
48
+ color: #fff;
49
+ background: var(--success-color);
50
+ }
41
51
  }
42
52
 
43
53
  &--outlined {
@@ -55,17 +65,27 @@ $root: button;
55
65
 
56
66
  &.#{$root}--primary-color {
57
67
  color: var(--primary-color);
58
- border-color: var(--button-outlined-border-primary-color);
68
+ border-color: var(--button-outlined-border-primary-color, var(--primary-color));
59
69
  }
60
70
 
61
71
  &.#{$root}--secondary-color {
62
72
  color: var(--secondary-color);
63
- border-color: var(--button-outlined-border-secondary-color);
73
+ border-color: var(--button-outlined-border-secondary-color, var(--secondary-color));
64
74
  }
65
75
 
66
76
  &.#{$root}--accent-color {
67
77
  color: var(--accent-color);
68
- border-color: var(--button-outlined-border-accent-color);
78
+ border-color: var(--button-outlined-border-accent-color, var(--accent-color-color));
79
+ }
80
+
81
+ &.#{$root}--error-color {
82
+ color: var(--error-color);
83
+ border-color: var(--button-outlined-border-error-color, var(--error-color));
84
+ }
85
+
86
+ &.#{$root}--success-color {
87
+ color: var(--success-color);
88
+ border-color: var(--button-outlined-border-success-color, var(--success-color));
69
89
  }
70
90
  }
71
91
 
@@ -92,6 +112,14 @@ $root: button;
92
112
  &.#{$root}--accent-color {
93
113
  color: var(--accent-color);
94
114
  }
115
+
116
+ &.#{$root}--error-color {
117
+ color: var(--error-color);
118
+ }
119
+
120
+ &.#{$root}--success-color {
121
+ color: var(--success-color);
122
+ }
95
123
  }
96
124
 
97
125
  // Sizes
@@ -8,6 +8,8 @@ export enum ButtonColor {
8
8
  Primary = "primary",
9
9
  Secondary = "secondary",
10
10
  Accent = "accent",
11
+ Error = "error",
12
+ Success = "success",
11
13
  }
12
14
 
13
15
  export enum ButtonSize {
@@ -1,7 +1,8 @@
1
1
  .list {
2
2
  display: flex;
3
3
  flex-direction: column;
4
- gap: 10px;
4
+ gap: var(--list-gap, 10px);
5
+ width: 100%;
5
6
  padding: 0;
6
7
  margin: 0;
7
8
  list-style: none;
@@ -2,7 +2,7 @@
2
2
  display: flex;
3
3
  flex-direction: row;
4
4
  align-items: center;
5
- gap: 10px;
5
+ gap: var(--list-item-gap, 10px);
6
6
  flex-wrap: nowrap;
7
7
  box-sizing: border-box;
8
8
  line-height: var(--list-item-line-height, var(--line-height, 1 rem));
@@ -40,21 +40,8 @@ const ScrollArea: ForwardRefRenderFunction<HTMLDivElement, ScrollAreaProps> = (p
40
40
  const rootRef = React.useRef<HTMLDivElement | null>(null);
41
41
  const viewportRef = React.useRef<HTMLDivElement | null>(null);
42
42
 
43
- useImperativeHandle(
44
- ref,
45
- () => ({
46
- ...rootRef.current!,
47
- scrollTo: ((optionsOrX?: ScrollToOptions | number, y?: number) => {
48
- if (typeof optionsOrX === 'number') {
49
- viewportRef.current?.scrollTo(optionsOrX, y!);
50
- } else {
51
- viewportRef.current?.scrollTo(optionsOrX);
52
- }
53
- }) as HTMLElement['scrollTo'],
43
+ useImperativeHandle(ref, () => viewportRef.current!, []);
54
44
 
55
- }),
56
- []
57
- );
58
45
  return (
59
46
  <Root ref={rootRef} className={classnames(styles["scroll-area"], className)} {...other}>
60
47
  <Viewport
@@ -76,7 +63,7 @@ const ScrollArea: ForwardRefRenderFunction<HTMLDivElement, ScrollAreaProps> = (p
76
63
  style={{padding: `0 ${xOffset}px`}}
77
64
  className={classnames(styles["scroll-area__scrollbar"], scrollbarClassName)}
78
65
  >
79
- <Thumb className={classnames(styles["scroll-area__thumb"], thumbClassName)}/>
66
+ <Thumb className={classnames(styles["scroll-area__thumb"], thumbClassName)} />
80
67
  </Scrollbar>
81
68
 
82
69
  <Scrollbar
@@ -84,10 +71,10 @@ const ScrollArea: ForwardRefRenderFunction<HTMLDivElement, ScrollAreaProps> = (p
84
71
  style={{padding: `${yOffset}px 0`}}
85
72
  className={classnames(styles["scroll-area__scrollbar"], scrollbarClassName)}
86
73
  >
87
- <Thumb className={classnames(styles["scroll-area__thumb"], thumbClassName)}/>
74
+ <Thumb className={classnames(styles["scroll-area__thumb"], thumbClassName)} />
88
75
  </Scrollbar>
89
76
 
90
- <Corner className={classnames(styles["scroll-area__corner"], cornerClassName)}/>
77
+ <Corner className={classnames(styles["scroll-area__corner"], cornerClassName)} />
91
78
  </Root>
92
79
  );
93
80
  };
@@ -3,13 +3,21 @@ $root: scroll-area;
3
3
  .#{$root} {
4
4
  box-sizing: border-box;
5
5
  overflow: hidden;
6
+ display: flex;
7
+ flex-direction: column;
8
+ flex: 1;
6
9
 
7
10
  &__viewport {
8
11
  width: 100%;
9
12
  height: 100%;
13
+ flex: 1;
14
+ display: flex;
15
+ flex-direction: column;
10
16
 
11
- &--horizontal > div {
12
- display: inherit !important;
17
+ & > div {
18
+ flex: 1;
19
+ display: flex !important;
20
+ flex-direction: column;
13
21
  }
14
22
  }
15
23
 
@@ -0,0 +1,32 @@
1
+ import React, {forwardRef, ForwardRefRenderFunction, memo} from "react";
2
+ import classnames from "classnames";
3
+
4
+ import {Root, TabsProps as TabsRadixProps} from "@radix-ui/react-tabs";
5
+ import {useComponentProps} from "../../providers";
6
+
7
+ import styles from "./tabs.module.scss";
8
+
9
+ export interface TabsProps extends Omit<TabsRadixProps, "orientation"> {
10
+ reverse?: boolean;
11
+ }
12
+ const Tabs: ForwardRefRenderFunction<HTMLDivElement, TabsProps> = (props, ref) => {
13
+ const {reverse, className, children, ...other} = {...useComponentProps("tabs"), ...props};
14
+
15
+ return (
16
+ <Root
17
+ ref={ref}
18
+ className={classnames(
19
+ styles["tabs"],
20
+ {
21
+ [styles["tabs--reverse"]]: reverse,
22
+ },
23
+ className
24
+ )}
25
+ {...other}
26
+ >
27
+ {children}
28
+ </Root>
29
+ );
30
+ };
31
+
32
+ export default memo(forwardRef(Tabs));
@@ -0,0 +1,21 @@
1
+ import React, {forwardRef, ForwardRefRenderFunction, memo} from "react";
2
+ import classnames from "classnames";
3
+
4
+ import {Content, TabsContentProps as TabsContentRadixProps} from "@radix-ui/react-tabs";
5
+ import {useComponentProps} from "../../providers";
6
+
7
+ import styles from "./tabs.module.scss";
8
+
9
+ export type TabsContentProps = TabsContentRadixProps;
10
+
11
+ const TabsList: ForwardRefRenderFunction<HTMLDivElement, TabsContentProps> = (props, ref) => {
12
+ const {className, children, ...other} = {...useComponentProps("tabsContent"), ...props};
13
+
14
+ return (
15
+ <Content ref={ref} className={classnames(styles["tabs__content"], className)} {...other}>
16
+ {children}
17
+ </Content>
18
+ );
19
+ };
20
+
21
+ export default memo(forwardRef(TabsList));
@@ -0,0 +1,131 @@
1
+ import React, {
2
+ forwardRef,
3
+ ForwardRefRenderFunction,
4
+ memo,
5
+ useCallback,
6
+ useImperativeHandle,
7
+ useLayoutEffect,
8
+ useRef,
9
+ useState,
10
+ } from "react";
11
+ import classnames from "classnames";
12
+ import _debounce from "lodash/debounce";
13
+
14
+ import {List, TabsListProps as TabsListRadixProps} from "@radix-ui/react-tabs";
15
+ import {useComponentProps} from "../../providers";
16
+
17
+ import styles from "./tabs.module.scss";
18
+
19
+ export interface TabsListProps extends TabsListRadixProps {
20
+ separator?: boolean;
21
+ indicator?: boolean;
22
+ roundedEdges?: boolean;
23
+ indicatorClassname?: string;
24
+ }
25
+
26
+ const TabsList: ForwardRefRenderFunction<HTMLDivElement, TabsListProps> = (props, ref) => {
27
+ const {
28
+ separator = true,
29
+ indicator = true,
30
+ roundedEdges,
31
+ className,
32
+ indicatorClassname,
33
+ children,
34
+ ...other
35
+ } = {...useComponentProps("tabsList"), ...props};
36
+
37
+ const listRef = useRef<HTMLDivElement | null>(null);
38
+ const indicatorRef = useRef<HTMLSpanElement | null>(null);
39
+
40
+ const [mounted, setMounted] = useState(false);
41
+ const [modificators, setModificators] = useState<{first?: boolean; last?: boolean}>({});
42
+
43
+ useImperativeHandle(ref, () => listRef.current!, []);
44
+
45
+ const updateIndicatorImmediate = useCallback(() => {
46
+ const list = listRef.current;
47
+ const indicator = indicatorRef.current;
48
+ const activeTrigger = list?.querySelector("[data-state='active']") as HTMLElement | null;
49
+
50
+ if (!list || !indicator || !activeTrigger) return;
51
+
52
+ const triggers = Array.from(list.querySelectorAll("[data-state]")) as HTMLElement[];
53
+ const first = triggers[0];
54
+ const last = triggers[triggers.length - 1];
55
+
56
+ const listRect = list.getBoundingClientRect();
57
+ const triggerRect = activeTrigger.getBoundingClientRect();
58
+ indicator.style.left = `${triggerRect.left - listRect.left + list.scrollLeft}px`;
59
+ indicator.style.width = `${triggerRect.width}px`;
60
+
61
+ setModificators({
62
+ first: activeTrigger === first,
63
+ last: activeTrigger === last,
64
+ });
65
+ }, []);
66
+
67
+ const updateIndicator = useCallback(_debounce(updateIndicatorImmediate, 50), [updateIndicatorImmediate]);
68
+
69
+ useLayoutEffect(() => {
70
+ updateIndicatorImmediate();
71
+
72
+ setMounted(true);
73
+
74
+ const resizeObserver = new ResizeObserver(updateIndicator);
75
+ const mutationObserver = new MutationObserver(updateIndicator);
76
+
77
+ if (listRef.current) {
78
+ resizeObserver.observe(listRef.current);
79
+
80
+ mutationObserver.observe(listRef.current, {
81
+ attributes: true,
82
+ attributeFilter: ["data-state"],
83
+ subtree: true,
84
+ childList: true,
85
+ });
86
+ }
87
+
88
+ window.addEventListener("resize", updateIndicator);
89
+
90
+ return () => {
91
+ window.removeEventListener("resize", updateIndicator);
92
+ resizeObserver.disconnect();
93
+ mutationObserver.disconnect();
94
+ updateIndicator.cancel();
95
+ };
96
+ }, []);
97
+
98
+ return (
99
+ <List
100
+ ref={listRef}
101
+ className={classnames(
102
+ styles["tabs__list"],
103
+ {
104
+ [styles["tabs__list--separator"]]: separator,
105
+ },
106
+ className
107
+ )}
108
+ {...other}
109
+ >
110
+ {children}
111
+ {mounted && (
112
+ <span
113
+ ref={indicatorRef}
114
+ aria-hidden={!mounted || !indicator}
115
+ className={classnames(
116
+ styles["tabs__indicator"],
117
+ {
118
+ [styles["tabs__indicator--show"]]: indicator,
119
+ [styles["tabs__indicator--rounded_edges"]]: roundedEdges,
120
+ [styles["tabs__indicator--first"]]: modificators.first,
121
+ [styles["tabs__indicator--last"]]: modificators.last,
122
+ },
123
+ indicatorClassname
124
+ )}
125
+ />
126
+ )}
127
+ </List>
128
+ );
129
+ };
130
+
131
+ export default memo(forwardRef<HTMLDivElement, TabsListProps>(TabsList));
@@ -0,0 +1,43 @@
1
+ import React, {forwardRef, ForwardRefRenderFunction, memo} from "react";
2
+ import classnames from "classnames";
3
+
4
+ import {TabsTriggerProps as TabsTriggerRadixProps, Trigger} from "@radix-ui/react-tabs";
5
+ import {useComponentProps} from "../../providers";
6
+
7
+ import styles from "./tabs.module.scss";
8
+
9
+ export interface TabsTriggerProps extends TabsTriggerRadixProps {
10
+ before?: number | string;
11
+ after?: number | string;
12
+ afterClassname?: string;
13
+ beforeClassname?: string;
14
+ }
15
+
16
+ const TabsTrigger: ForwardRefRenderFunction<HTMLButtonElement, TabsTriggerProps> = (props, ref) => {
17
+ const {
18
+ after,
19
+ before,
20
+ className,
21
+ afterClassname,
22
+ beforeClassname,
23
+ children,
24
+ asChild = true,
25
+ ...other
26
+ } = {...useComponentProps("tabsTrigger"), ...props};
27
+
28
+ return (
29
+ <Trigger ref={ref} asChild={asChild} className={classnames(styles["tabs__trigger"], className)} {...other}>
30
+ <div className={classnames(styles["tabs__trigger-wrapper"])}>
31
+ {before && (
32
+ <span className={classnames(styles["tabs__trigger__before"], beforeClassname)}>{before}</span>
33
+ )}
34
+
35
+ {typeof children === "string" ? <span>{children}</span> : children}
36
+
37
+ {after && <span className={classnames(styles["tabs__trigger__after"], afterClassname)}>{after}</span>}
38
+ </div>
39
+ </Trigger>
40
+ );
41
+ };
42
+
43
+ export default memo(forwardRef(TabsTrigger));
@@ -0,0 +1,5 @@
1
+ export * from "./types";
2
+ export {default as Tabs, type TabsProps} from "./Tabs";
3
+ export {default as TabsList, type TabsListProps} from "./TabsList";
4
+ export {default as TabsTrigger, type TabsTriggerProps} from "./TabsTrigger";
5
+ export {default as TabsContent, type TabsContentProps} from "./TabsContent";