@toptal/picasso-tabs 5.0.15-alpha-allow-to-pass-class-names-in-modal-package-7c2827b69.2 → 5.0.15-alpha-ff-7-tabs-17eb872bb.13

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 (57) hide show
  1. package/dist-package/src/Tab/Tab.d.ts +5 -8
  2. package/dist-package/src/Tab/Tab.d.ts.map +1 -1
  3. package/dist-package/src/Tab/Tab.js +61 -16
  4. package/dist-package/src/Tab/Tab.js.map +1 -1
  5. package/dist-package/src/Tabs/Tabs.d.ts +12 -9
  6. package/dist-package/src/Tabs/Tabs.d.ts.map +1 -1
  7. package/dist-package/src/Tabs/Tabs.js +68 -16
  8. package/dist-package/src/Tabs/Tabs.js.map +1 -1
  9. package/dist-package/src/Tabs/TabsContext.d.ts +11 -0
  10. package/dist-package/src/Tabs/TabsContext.d.ts.map +1 -0
  11. package/dist-package/src/Tabs/TabsContext.js +15 -0
  12. package/dist-package/src/Tabs/TabsContext.js.map +1 -0
  13. package/dist-package/src/Tabs/index.d.ts +3 -2
  14. package/dist-package/src/Tabs/index.d.ts.map +1 -1
  15. package/dist-package/src/Tabs/index.js.map +1 -1
  16. package/dist-package/src/TabsCompound/index.d.ts +2 -4
  17. package/dist-package/src/TabsCompound/index.d.ts.map +1 -1
  18. package/dist-package/src/index.d.ts +0 -1
  19. package/dist-package/src/index.d.ts.map +1 -1
  20. package/dist-package/src/index.js +0 -1
  21. package/dist-package/src/index.js.map +1 -1
  22. package/package.json +13 -13
  23. package/src/Tab/Tab.tsx +121 -51
  24. package/src/Tab/__snapshots__/test.tsx.snap +29 -45
  25. package/src/Tab/story/IconOrBadge.example.tsx +8 -3
  26. package/src/Tabs/Tabs.tsx +125 -42
  27. package/src/Tabs/TabsContext.tsx +27 -0
  28. package/src/Tabs/__snapshots__/test.tsx.snap +45 -76
  29. package/src/Tabs/index.ts +3 -2
  30. package/src/Tabs/story/Default.example.tsx +4 -2
  31. package/src/Tabs/test.tsx +4 -4
  32. package/src/index.ts +0 -1
  33. package/dist-package/src/Tab/styles.d.ts +0 -4
  34. package/dist-package/src/Tab/styles.d.ts.map +0 -1
  35. package/dist-package/src/Tab/styles.js +0 -95
  36. package/dist-package/src/Tab/styles.js.map +0 -1
  37. package/dist-package/src/TabScrollButton/TabScrollButton.d.ts +0 -12
  38. package/dist-package/src/TabScrollButton/TabScrollButton.d.ts.map +0 -1
  39. package/dist-package/src/TabScrollButton/TabScrollButton.js +0 -30
  40. package/dist-package/src/TabScrollButton/TabScrollButton.js.map +0 -1
  41. package/dist-package/src/TabScrollButton/index.d.ts +0 -5
  42. package/dist-package/src/TabScrollButton/index.d.ts.map +0 -1
  43. package/dist-package/src/TabScrollButton/index.js +0 -2
  44. package/dist-package/src/TabScrollButton/index.js.map +0 -1
  45. package/dist-package/src/Tabs/styles.d.ts +0 -4
  46. package/dist-package/src/Tabs/styles.d.ts.map +0 -1
  47. package/dist-package/src/Tabs/styles.js +0 -41
  48. package/dist-package/src/Tabs/styles.js.map +0 -1
  49. package/dist-package/src/Tabs/use-tab-action.d.ts +0 -5
  50. package/dist-package/src/Tabs/use-tab-action.d.ts.map +0 -1
  51. package/dist-package/src/Tabs/use-tab-action.js +0 -21
  52. package/dist-package/src/Tabs/use-tab-action.js.map +0 -1
  53. package/src/Tab/styles.ts +0 -107
  54. package/src/TabScrollButton/TabScrollButton.tsx +0 -59
  55. package/src/TabScrollButton/index.ts +0 -6
  56. package/src/Tabs/styles.ts +0 -45
  57. package/src/Tabs/use-tab-action.ts +0 -27
package/src/Tab/Tab.tsx CHANGED
@@ -1,30 +1,26 @@
1
1
  import type { ReactNode, HTMLAttributes, ReactElement } from 'react'
2
- import React, { forwardRef, useContext } from 'react'
3
- import type { Theme } from '@material-ui/core/styles'
4
- import { makeStyles } from '@material-ui/core/styles'
5
- import type { TabProps } from '@material-ui/core'
6
- import { Tab as MUITab } from '@material-ui/core'
2
+ import React, { forwardRef } from 'react'
7
3
  import type { BaseProps, TextLabelProps } from '@toptal/picasso-shared'
8
4
  import { useTitleCase } from '@toptal/picasso-shared'
9
5
  import { UserBadge } from '@toptal/picasso-user-badge'
6
+ import { twJoin, twMerge } from '@toptal/picasso-tailwind-merge'
10
7
 
11
- import styles from './styles'
12
- import { TabsOrientationContext } from '../Tabs/Tabs'
8
+ import { useTabsContext, type TabsValueType } from '../Tabs/TabsContext'
13
9
  import { TabLabel } from '../TabLabel'
14
10
  import { TabDescription } from '../TabDescription'
15
11
 
16
12
  export interface Props
17
13
  extends BaseProps,
18
14
  TextLabelProps,
19
- Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
15
+ Omit<HTMLAttributes<HTMLButtonElement>, 'onChange'> {
20
16
  /**
21
17
  * If true, the tab will be disabled
22
18
  * @default false
23
19
  */
24
20
  disabled?: boolean
25
21
 
26
- /** You can provide your own value. Otherwise, we fallback to the child position index */
27
- value?: TabProps['value']
22
+ /** The value of the tab */
23
+ value?: TabsValueType
28
24
 
29
25
  /** The label element */
30
26
  label?: ReactNode
@@ -40,32 +36,97 @@ export interface Props
40
36
 
41
37
  // Properties below are managed by Tabs component
42
38
 
43
- selected?: boolean
44
- onChange?: TabProps['onChange']
45
- onClick?: TabProps['onClick']
39
+ // selected?: boolean
40
+ // onChange?: TabProps['onChange']
41
+ // onClick?: TabProps['onClick']
46
42
  }
47
43
 
48
- const useStyles = makeStyles<Theme>(styles, { name: 'PicassoTab' })
44
+ const getOpacityClass = (
45
+ selected: boolean,
46
+ disabled: boolean,
47
+ orientation: 'horizontal' | 'vertical'
48
+ ) => {
49
+ if (disabled) {
50
+ return 'opacity-50'
51
+ }
52
+ if (selected || orientation === 'vertical') {
53
+ return 'opacity-100 '
54
+ }
55
+
56
+ return 'opacity-70'
57
+ }
58
+
59
+ const wrapperClassesByOrientation = {
60
+ horizontal: 'inline-flex flex-row items-center justify-center',
61
+ vertical: 'block',
62
+ }
63
+
64
+ const rootClassesByOrientation = (selected: boolean) => ({
65
+ horizontal: [
66
+ 'm-0 [&:not(:last-child)]:mr-8 pt-0 pb-[0.4375rem] px-0',
67
+ 'text-center bg-transparent transition-shadow z-10 rounded-none',
68
+ 'text-black',
69
+ selected && 'shadow-blue-500 shadow-[inset_0_-2px_0]',
70
+ ],
71
+ vertical: [
72
+ ' first:mt-4 last:mb-4 my-1 mx-0 py-2 px-4',
73
+ 'text-left rounded-l-sm rounded-r-none transition-all',
74
+ 'w-full overflow-hidden',
75
+ selected && 'shadow-1',
76
+ selected && [
77
+ 'before:absolute',
78
+ 'before:content-[""]',
79
+ 'before:bottom-0',
80
+ 'before:left-0',
81
+ 'before:top-0',
82
+ 'before:w-[3px]',
83
+ 'before:bg-blue-500',
84
+ ],
85
+ selected
86
+ ? 'bg-gray-50 text-black'
87
+ : 'bg-gray-100 hover:bg-gray-200 text-graphite-700 hover:text-black',
88
+ ],
89
+ })
90
+
91
+ const classesByVariant = {
92
+ scrollable: 'shrink-0 max-w-[264px]',
93
+ fullWidth: 'shrink flex-grow basis-0',
94
+ }
49
95
 
50
- export const Tab = forwardRef<HTMLDivElement, Props>(function Tab(props, ref) {
96
+ export const Tab = forwardRef<HTMLButtonElement, Props>(function Tab(
97
+ props,
98
+ ref
99
+ ) {
51
100
  const {
52
101
  disabled,
53
102
  value,
54
103
  label,
55
104
  icon,
56
- selected,
57
- onChange,
58
- onClick,
59
105
  titleCase: propsTitleCase,
60
106
  description,
61
107
  avatar,
108
+ className,
109
+ onClick,
62
110
  ...rest
63
111
  } = props
64
- const classes = useStyles()
65
112
  const titleCase = useTitleCase(propsTitleCase)
66
- const orientation = useContext(TabsOrientationContext)
113
+ const {
114
+ value: selectedValue,
115
+ onChange,
116
+ orientation,
117
+ variant,
118
+ } = useTabsContext()
119
+ const isHorizontal = orientation === 'horizontal'
120
+ const selected = value === selectedValue
121
+
122
+ const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
123
+ if (!disabled && onChange) {
124
+ onChange(event, value as TabsValueType)
125
+ }
126
+ onClick?.(event)
127
+ }
67
128
 
68
- const labelComponent = getLabelComponent({
129
+ const renderLabel = getLabelComponent({
69
130
  avatar,
70
131
  description,
71
132
  disabled,
@@ -75,39 +136,44 @@ export const Tab = forwardRef<HTMLDivElement, Props>(function Tab(props, ref) {
75
136
  })
76
137
 
77
138
  return (
78
- <MUITab
79
- className=''
80
- {...rest}
139
+ <button
140
+ className={twMerge(
141
+ getOpacityClass(selected, !!disabled, orientation),
142
+ rootClassesByOrientation(selected)[orientation],
143
+ classesByVariant[variant],
144
+ disabled ? 'cursor-default text-gray-500' : 'cursor-pointer',
145
+ disabled && 'pointer-events-none',
146
+ icon && isHorizontal && 'min-h-0 pt-0 pr-6',
147
+ 'min-w-0 sm:min-w-[160px] md:min-w-[auto]',
148
+ 'border-0 cursor-pointer inline-flex outline-none',
149
+ 'items-center select-none align-middle appearance-none',
150
+ 'justify-center no-underline [-webkit-tap-highlight-color:transparent]',
151
+ 'normal-case whitespace-normal leading-4',
152
+ 'relative ',
153
+ className
154
+ )}
81
155
  ref={ref}
82
- tabIndex={0}
156
+ tabIndex={disabled ? -1 : 0}
83
157
  disabled={disabled}
84
- label={labelComponent}
85
- icon={icon}
86
- value={value}
87
- selected={selected}
88
- onChange={onChange}
89
- onClick={onClick}
90
- classes={{
91
- root: classes[orientation],
92
- selected: classes.selected,
93
- wrapper: classes.wrapper,
94
- }}
95
- />
158
+ onClick={handleClick}
159
+ role='tab'
160
+ aria-selected={selected}
161
+ aria-disabled={disabled}
162
+ type='button'
163
+ {...rest}
164
+ >
165
+ <span
166
+ className={twJoin('w-full', wrapperClassesByOrientation[orientation])}
167
+ >
168
+ {renderLabel}
169
+ {icon && <span className='absolute right-0 mb-0'>{icon}</span>}
170
+ </span>
171
+ </button>
96
172
  )
97
173
  })
98
174
 
99
- Tab.defaultProps = {}
100
-
101
175
  Tab.displayName = 'Tab'
102
176
 
103
- type GetLabelComponentProps = {
104
- avatar?: string | null
105
- description?: string
106
- disabled?: boolean
107
- label?: React.ReactNode
108
- orientation: 'horizontal' | 'vertical'
109
- titleCase?: boolean
110
- }
111
177
  const getLabelComponent = ({
112
178
  avatar,
113
179
  description,
@@ -115,14 +181,19 @@ const getLabelComponent = ({
115
181
  label,
116
182
  orientation,
117
183
  titleCase,
118
- }: GetLabelComponentProps): React.ReactNode => {
184
+ }: {
185
+ avatar?: string | null
186
+ description?: string
187
+ disabled?: boolean
188
+ label?: React.ReactNode
189
+ orientation: 'horizontal' | 'vertical'
190
+ titleCase?: boolean
191
+ }): React.ReactNode => {
119
192
  if (!label) {
120
193
  return null
121
194
  }
122
-
123
195
  const isHorizontal = orientation === 'horizontal'
124
196
  const isCustomLabel = typeof label !== 'string'
125
-
126
197
  const Label = () => (
127
198
  <TabLabel titleCase={titleCase} label={label} orientation={orientation} />
128
199
  )
@@ -130,7 +201,6 @@ const getLabelComponent = ({
130
201
  if (isHorizontal || isCustomLabel) {
131
202
  return <Label />
132
203
  }
133
-
134
204
  if (typeof avatar === 'undefined') {
135
205
  return (
136
206
  <>
@@ -6,31 +6,29 @@ exports[`Tab Tab disabled tab 1`] = `
6
6
  class="Picasso-root"
7
7
  >
8
8
  <div
9
- class="MuiTabs-root Tabs-horizontal"
9
+ aria-orientation="horizontal"
10
+ class="relative min-h flex overflow-hidden overflow-x"
10
11
  data-component-type="tabs"
11
12
  >
12
13
  <div
13
- class="MuiTabs-scrollable"
14
- style="width: 99px; height: 99px; position: absolute; top: -9999px; overflow: scroll;"
15
- />
16
- <div
17
- class="MuiTabs-scroller MuiTabs-scrollable"
18
- style="margin-bottom: 0px;"
14
+ class="after:absolute after:content-[""] after:bottom-0 after:left-0 after:right-0 after:h-[1px] after:bg-gray after:z-0 flex-auto inline-block relative whitespace-nowrap"
19
15
  >
20
16
  <div
21
- class="MuiTabs-flexContainer"
17
+ class="flex"
22
18
  role="tablist"
19
+ tabindex="-1"
23
20
  >
24
21
  <button
22
+ aria-disabled="true"
25
23
  aria-selected="false"
26
- class="MuiButtonBase-root MuiTab-root PicassoTab-horizontal MuiTab-textColorInherit Mui-disabled Mui-disabled"
24
+ class="opacity-50 m-0 [&:not(:last-child)]:mr-8 pt-0 pb-[0.4375rem] px-0 text-center bg-transparent transition-shadow z-10 rounded-none shrink-0 max-w text-gray pointer-events min-w sm:min-w md:min-w border-0 cursor-pointer inline-flex outline-none items-center select-none align-middle appearance-none justify-center no-underline [-webkit-tap-highlight normal-case whitespace-normal leading-4 relative"
27
25
  disabled=""
28
26
  role="tab"
29
27
  tabindex="-1"
30
28
  type="button"
31
29
  >
32
30
  <span
33
- class="MuiTab-wrapper PicassoTab-wrapper"
31
+ class="w-full inline-flex flex-row items-center justify-center"
34
32
  >
35
33
  <div
36
34
  class="m-0 text-sm text-inherit font-semibold leading-[1.1rem]"
@@ -40,10 +38,6 @@ exports[`Tab Tab disabled tab 1`] = `
40
38
  </span>
41
39
  </button>
42
40
  </div>
43
- <span
44
- class="PrivateTabIndicator-root PrivateTabIndicator-colorSecondary MuiTabs-indicator"
45
- style="left: 0px; width: 0px;"
46
- />
47
41
  </div>
48
42
  </div>
49
43
  </div>
@@ -56,30 +50,27 @@ exports[`Tab Tab renders 1`] = `
56
50
  class="Picasso-root"
57
51
  >
58
52
  <div
59
- class="MuiTabs-root Tabs-horizontal"
53
+ aria-orientation="horizontal"
54
+ class="relative min-h flex overflow-hidden overflow-x"
60
55
  data-component-type="tabs"
61
56
  >
62
57
  <div
63
- class="MuiTabs-scrollable"
64
- style="width: 99px; height: 99px; position: absolute; top: -9999px; overflow: scroll;"
65
- />
66
- <div
67
- class="MuiTabs-scroller MuiTabs-scrollable"
68
- style="margin-bottom: 0px;"
58
+ class="after:absolute after:content-[""] after:bottom-0 after:left-0 after:right-0 after:h-[1px] after:bg-gray after:z-0 flex-auto inline-block relative whitespace-nowrap"
69
59
  >
70
60
  <div
71
- class="MuiTabs-flexContainer"
61
+ class="flex"
72
62
  role="tablist"
63
+ tabindex="-1"
73
64
  >
74
65
  <button
75
66
  aria-selected="false"
76
- class="MuiButtonBase-root MuiTab-root PicassoTab-horizontal MuiTab-textColorInherit"
67
+ class="opacity-70 m-0 [&:not(:last-child)]:mr-8 pt-0 pb-[0.4375rem] px-0 text-center bg-transparent transition-shadow z-10 rounded-none text-black shrink-0 max-w min-w sm:min-w md:min-w border-0 cursor-pointer inline-flex outline-none items-center select-none align-middle appearance-none justify-center no-underline [-webkit-tap-highlight normal-case whitespace-normal leading-4 relative"
77
68
  role="tab"
78
69
  tabindex="0"
79
70
  type="button"
80
71
  >
81
72
  <span
82
- class="MuiTab-wrapper PicassoTab-wrapper"
73
+ class="w-full inline-flex flex-row items-center justify-center"
83
74
  >
84
75
  <div
85
76
  class="m-0 text-sm text-inherit font-semibold leading-[1.1rem]"
@@ -89,10 +80,6 @@ exports[`Tab Tab renders 1`] = `
89
80
  </span>
90
81
  </button>
91
82
  </div>
92
- <span
93
- class="PrivateTabIndicator-root PrivateTabIndicator-colorSecondary MuiTabs-indicator"
94
- style="left: 0px; width: 0px;"
95
- />
96
83
  </div>
97
84
  </div>
98
85
  </div>
@@ -105,46 +92,43 @@ exports[`Tab Tab tab with icon 1`] = `
105
92
  class="Picasso-root"
106
93
  >
107
94
  <div
108
- class="MuiTabs-root Tabs-horizontal"
95
+ aria-orientation="horizontal"
96
+ class="relative min-h flex overflow-hidden overflow-x"
109
97
  data-component-type="tabs"
110
98
  >
111
99
  <div
112
- class="MuiTabs-scrollable"
113
- style="width: 99px; height: 99px; position: absolute; top: -9999px; overflow: scroll;"
114
- />
115
- <div
116
- class="MuiTabs-scroller MuiTabs-scrollable"
117
- style="margin-bottom: 0px;"
100
+ class="after:absolute after:content-[""] after:bottom-0 after:left-0 after:right-0 after:h-[1px] after:bg-gray after:z-0 flex-auto inline-block relative whitespace-nowrap"
118
101
  >
119
102
  <div
120
- class="MuiTabs-flexContainer"
103
+ class="flex"
121
104
  role="tablist"
105
+ tabindex="-1"
122
106
  >
123
107
  <button
124
108
  aria-selected="false"
125
- class="MuiButtonBase-root MuiTab-root PicassoTab-horizontal MuiTab-textColorInherit MuiTab-labelIcon"
109
+ class="opacity-70 m-0 [&:not(:last-child)]:mr-8 pb-[0.4375rem] px-0 text-center bg-transparent transition-shadow z-10 rounded-none text-black shrink-0 max-w min-h pt-0 pr-6 min-w sm:min-w md:min-w border-0 cursor-pointer inline-flex outline-none items-center select-none align-middle appearance-none justify-center no-underline [-webkit-tap-highlight normal-case whitespace-normal leading-4 relative"
126
110
  role="tab"
127
111
  tabindex="0"
128
112
  type="button"
129
113
  >
130
114
  <span
131
- class="MuiTab-wrapper PicassoTab-wrapper"
115
+ class="w-full inline-flex flex-row items-center justify-center"
132
116
  >
133
- <div
134
- id="Icon"
135
- />
136
117
  <div
137
118
  class="m-0 text-sm text-inherit font-semibold leading-[1.1rem]"
138
119
  >
139
120
  Tab Label
140
121
  </div>
122
+ <span
123
+ class="absolute right-0 mb-0"
124
+ >
125
+ <div
126
+ id="Icon"
127
+ />
128
+ </span>
141
129
  </span>
142
130
  </button>
143
131
  </div>
144
- <span
145
- class="PrivateTabIndicator-root PrivateTabIndicator-colorSecondary MuiTabs-indicator"
146
- style="left: 0px; width: 0px;"
147
- />
148
132
  </div>
149
133
  </div>
150
134
  </div>
@@ -3,10 +3,12 @@ import { Container, Tabs, Tooltip, Badge } from '@toptal/picasso'
3
3
  import { SPACING_4 } from '@toptal/picasso-utils'
4
4
  import { Exclamation16 } from '@toptal/picasso-icons'
5
5
 
6
+ import type { TabsValueType } from '../../Tabs/TabsContext'
7
+
6
8
  const Example = () => {
7
- const [value, setValue] = React.useState(0)
9
+ const [value, setValue] = React.useState<TabsValueType>(0)
8
10
 
9
- const handleChange = (_: React.ChangeEvent<{}>, newValue: number) => {
11
+ const handleChange = (_: React.ChangeEvent<{}>, newValue: TabsValueType) => {
10
12
  setValue(newValue)
11
13
  }
12
14
 
@@ -24,7 +26,10 @@ const Example = () => {
24
26
  }
25
27
  />
26
28
  <Tabs.Tab label='Label' />
27
- <Tabs.Tab label='Label' icon={<Badge content={10} variant='white' />} />
29
+ <Tabs.Tab
30
+ label='Label'
31
+ icon={<Badge content={10} variant='white' className='mt-[1px]' />}
32
+ />
28
33
  </Tabs>
29
34
 
30
35
  {value === 0 && (
package/src/Tabs/Tabs.tsx CHANGED
@@ -1,24 +1,21 @@
1
- import type { ForwardedRef, ReactNode } from 'react'
2
- import React, { forwardRef } from 'react'
3
- import type { Theme } from '@material-ui/core/styles'
4
- import { makeStyles } from '@material-ui/core/styles'
5
- import { Tabs as MUITabs } from '@material-ui/core'
1
+ import type { ReactNode, ChangeEvent } from 'react'
2
+ import React, { forwardRef, useMemo, useCallback } from 'react'
6
3
  import type { BaseProps } from '@toptal/picasso-shared'
4
+ import { twJoin, twMerge } from '@toptal/picasso-tailwind-merge'
7
5
 
8
- import { TabScrollButton } from '../TabScrollButton'
9
- import styles from './styles'
10
- import useTabAction from './use-tab-action'
11
-
12
- export type TabsValueType = string | number | false
6
+ import { TabsContext, type TabsValueType } from './TabsContext'
13
7
 
14
8
  export interface Props<V extends TabsValueType> extends BaseProps {
15
9
  /** Tabs content containing Tab components */
16
10
  children: ReactNode
17
11
 
18
12
  /** Callback fired when the value changes. */
19
- onChange?: (event: React.ChangeEvent<{}> | null, value: V) => void
13
+ onChange?: (event: ChangeEvent<{}>, value: V) => void
20
14
 
21
- /** The value of the currently selected Tab. If you don't want any selected Tab, you can set this property to false. */
15
+ /**
16
+ * The value of the currently selected Tab.
17
+ * If you don't want any selected Tab, you can set this property to null.
18
+ */
22
19
  value: V
23
20
 
24
21
  /** The tabs orientation (layout flow direction). */
@@ -26,54 +23,140 @@ export interface Props<V extends TabsValueType> extends BaseProps {
26
23
 
27
24
  /** Determines additional display behavior of the tabs */
28
25
  variant?: 'scrollable' | 'fullWidth'
26
+
27
+ /** The default value. Use when the component is not controlled. */
28
+ defaultValue?: V
29
+
30
+ /** The direction of the text. */
31
+ direction?: 'ltr' | 'rtl'
29
32
  }
30
33
 
31
- const useStyles = makeStyles<Theme>(styles, {
32
- name: 'Tabs',
33
- })
34
+ const indicatorClasses = [
35
+ 'after:absolute',
36
+ 'after:content-[""]',
37
+ 'after:bottom-0',
38
+ 'after:left-0',
39
+ 'after:right-0',
40
+ 'after:h-[1px]',
41
+ 'after:bg-gray-500',
42
+ 'after:z-0',
43
+ ]
44
+
45
+ const classesByOrientation = {
46
+ vertical: {
47
+ root: 'w-[200px] m-0 flex-col',
48
+ scroller: 'pl-2',
49
+ },
50
+ horizontal: {
51
+ root: '',
52
+ scroller: indicatorClasses,
53
+ },
54
+ }
34
55
 
35
- export const TabsOrientationContext = React.createContext<
36
- 'horizontal' | 'vertical'
37
- >('horizontal')
56
+ const classesByVariant = {
57
+ scrollable: {
58
+ root: 'overflow-x-auto',
59
+ scroller: '',
60
+ },
61
+ fullWidth: {
62
+ root: '',
63
+ scroller: 'w-full overflow-hidden',
64
+ },
65
+ }
38
66
 
39
- export const Tabs = forwardRef(
40
- <V extends TabsValueType = TabsValueType>(
41
- props: Props<V>,
42
- ref: ForwardedRef<HTMLButtonElement>
43
- ) => {
67
+ export const Tabs = forwardRef<HTMLDivElement, Props<TabsValueType>>(
68
+ function Tabs(props, ref) {
44
69
  const {
45
70
  children,
46
71
  orientation = 'horizontal',
47
72
  onChange,
48
- value,
73
+ value: valueProp,
74
+ defaultValue,
49
75
  variant = 'scrollable',
76
+ direction = 'ltr',
77
+ className,
50
78
  ...rest
51
79
  } = props
52
- const classes = useStyles(props)
53
- const action = useTabAction()
80
+
81
+ const [value, setValue] = React.useState<TabsValueType>(
82
+ defaultValue ?? null
83
+ )
84
+ const isControlled = valueProp !== undefined
85
+ const currentValue = isControlled ? valueProp : value
86
+
87
+ const handleChange = useCallback(
88
+ (event: ChangeEvent<{}>, newValue: TabsValueType) => {
89
+ if (!isControlled) {
90
+ setValue(newValue)
91
+ }
92
+ onChange?.(event, newValue as TabsValueType)
93
+ },
94
+ [isControlled, onChange]
95
+ )
96
+
97
+ const contextValue = useMemo(
98
+ () => ({
99
+ value: currentValue,
100
+ onChange: handleChange,
101
+ orientation,
102
+ variant,
103
+ direction,
104
+ }),
105
+ [currentValue, handleChange, orientation, variant, direction]
106
+ )
107
+
108
+ const isVertical = orientation === 'vertical'
109
+
110
+ const childrenWithIndex = React.Children.map(children, (child, idx) => {
111
+ if (
112
+ React.isValidElement(child) &&
113
+ // @ts-expect-error: type check for Picasso Tab
114
+ (child.type.displayName === 'Tab' || child.type.name === 'Tab') &&
115
+ child.props.value === undefined
116
+ ) {
117
+ return React.cloneElement(child as React.ReactElement<any>, {
118
+ value: idx,
119
+ })
120
+ }
121
+
122
+ return child
123
+ })
54
124
 
55
125
  return (
56
- <TabsOrientationContext.Provider value={orientation}>
57
- <MUITabs
126
+ <TabsContext.Provider value={contextValue}>
127
+ <div
58
128
  {...rest}
59
- classes={{ root: classes[orientation] }}
60
129
  ref={ref}
61
- onChange={onChange}
62
- value={value}
63
- action={action}
64
- scrollButtons='auto'
65
- ScrollButtonComponent={TabScrollButton}
66
- orientation={orientation}
67
- variant={variant}
68
130
  data-component-type='tabs'
131
+ className={twMerge(
132
+ 'relative min-h-0 flex overflow-hidden',
133
+ classesByOrientation[orientation].root,
134
+ classesByVariant[variant].root,
135
+ className
136
+ )}
137
+ aria-orientation={orientation}
69
138
  >
70
- {children}
71
- </MUITabs>
72
- </TabsOrientationContext.Provider>
139
+ <div
140
+ className={twJoin(
141
+ classesByVariant[variant].scroller,
142
+ classesByOrientation[orientation].scroller,
143
+ 'flex-auto inline-block relative whitespace-nowrap'
144
+ )}
145
+ >
146
+ <div
147
+ className={twJoin('flex', isVertical && 'flex-col')}
148
+ role='tablist'
149
+ tabIndex={-1}
150
+ >
151
+ {childrenWithIndex}
152
+ </div>
153
+ </div>
154
+ </div>
155
+ </TabsContext.Provider>
73
156
  )
74
157
  }
75
- ) as <V extends TabsValueType = TabsValueType>(
76
- props: Props<V> & { ref?: ForwardedRef<HTMLDivElement> }
77
- ) => ReturnType<typeof MUITabs>
158
+ )
159
+
160
+ Tabs.displayName = 'Tabs'
78
161
 
79
162
  export default Tabs
@@ -0,0 +1,27 @@
1
+ import React from 'react'
2
+
3
+ export type TabsValueType = string | number | null
4
+
5
+ export interface TabsContextValue {
6
+ value: TabsValueType
7
+ onChange: (event: React.ChangeEvent<{}>, value: TabsValueType) => void
8
+ orientation: 'horizontal' | 'vertical'
9
+ variant: 'scrollable' | 'fullWidth'
10
+ }
11
+
12
+ export const TabsContext = React.createContext<TabsContextValue>({
13
+ value: null,
14
+ onChange: () => {},
15
+ orientation: 'horizontal',
16
+ variant: 'scrollable',
17
+ })
18
+
19
+ export const useTabsContext = () => {
20
+ const context = React.useContext(TabsContext)
21
+
22
+ if (!context) {
23
+ throw new Error('useTabsContext must be used within a TabsProvider')
24
+ }
25
+
26
+ return context
27
+ }