uikit-react-public 0.29.6 → 0.30.1

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 (143) hide show
  1. package/dist/components/Accordion/Accordion.stories.d.ts +1 -1
  2. package/dist/components/Alert/Alert.stories.d.ts +1 -1
  3. package/dist/components/AppHeader/AppHeader.stories.d.ts +1 -1
  4. package/dist/components/AppMenu/AppMenu.stories.d.ts +1 -1
  5. package/dist/components/Avatar/Avatar.stories.d.ts +1 -1
  6. package/dist/components/Badge/Badge.stories.d.ts +1 -1
  7. package/dist/components/BaseCheckbox/BaseCheckbox.stories.d.ts +1 -1
  8. package/dist/components/Blanket/Blanket.stories.d.ts +1 -1
  9. package/dist/components/Breadcrumbs/Breadcrumbs.stories.d.ts +1 -1
  10. package/dist/components/Button/Button.stories.d.ts +1 -1
  11. package/dist/components/Calendar/Calendar.stories.d.ts +1 -1
  12. package/dist/components/Calendar/subcomponents/Day.stories.d.ts +1 -1
  13. package/dist/components/Checkbox/Checkbox.stories.d.ts +1 -1
  14. package/dist/components/Chip/Chip.stories.d.ts +1 -1
  15. package/dist/components/Datepicker/Datepicker.stories.d.ts +1 -1
  16. package/dist/components/Dialog/BaseDialog.d.ts +1 -1
  17. package/dist/components/Dialog/Dialog.d.ts +1 -1
  18. package/dist/components/Dialog/Dialog.stories.d.ts +2 -2
  19. package/dist/components/Divider/Divider.stories.d.ts +1 -1
  20. package/dist/components/Dropdown/Dropdown.stories.d.ts +1 -1
  21. package/dist/components/FeedbackDialog/FeedbackDialog.stories.d.ts +1 -1
  22. package/dist/components/Field/Field.stories.d.ts +1 -1
  23. package/dist/components/FileInput/FileInput.stories.d.ts +1 -1
  24. package/dist/components/Footer/Footer.stories.d.ts +1 -1
  25. package/dist/components/Header/Header.stories.d.ts +1 -1
  26. package/dist/components/Heading/Heading.stories.d.ts +1 -1
  27. package/dist/components/Icon/Icon.stories.d.ts +1 -1
  28. package/dist/components/IconButton/IconButton.stories.d.ts +1 -1
  29. package/dist/components/Input/Input.stories.d.ts +1 -1
  30. package/dist/components/Label/Label.stories.d.ts +1 -1
  31. package/dist/components/Layout/Layout.stories.d.ts +1 -1
  32. package/dist/components/Link/Link.stories.d.ts +1 -1
  33. package/dist/components/Main/Main.stories.d.ts +1 -1
  34. package/dist/components/Modal/Modal.stories.d.ts +1 -1
  35. package/dist/components/NativeDatepicker/NativeDatepicker.stories.d.ts +1 -1
  36. package/dist/components/Overlay/Overlay.stories.d.ts +2 -2
  37. package/dist/components/Pagination/Pagination.stories.d.ts +1 -1
  38. package/dist/components/Paragraph/Paragraph.stories.d.ts +1 -1
  39. package/dist/components/Radio/Radio.stories.d.ts +1 -1
  40. package/dist/components/Search/Search.stories.d.ts +1 -1
  41. package/dist/components/Select/Select.stories.d.ts +1 -1
  42. package/dist/components/Snackbar/Snackbar.stories.d.ts +1 -1
  43. package/dist/components/Spinner/Spinner.stories.d.ts +1 -1
  44. package/dist/components/StandaloneLink/StandaloneLink.stories.d.ts +1 -1
  45. package/dist/components/Table/Table.stories.d.ts +1 -1
  46. package/dist/components/Table/subcomponents/Cell/Cell.stories.d.ts +2 -2
  47. package/dist/components/Table/subcomponents/HeadCell/HeadCell.stories.d.ts +2 -2
  48. package/dist/components/Tabs/Tab.d.ts +11 -5
  49. package/dist/components/Tabs/TabContext.d.ts +14 -8
  50. package/dist/components/Tabs/Tabs.d.ts +25 -8
  51. package/dist/components/Tabs/Tabs.stories.d.ts +5 -9
  52. package/dist/components/Tabs/TabsList.d.ts +9 -0
  53. package/dist/components/Tabs/TabsPanel.d.ts +10 -0
  54. package/dist/components/Tabs/index.d.ts +2 -1
  55. package/dist/components/Textarea/Textarea.stories.d.ts +1 -1
  56. package/dist/components/Timepicker/Timepicker.stories.d.ts +1 -1
  57. package/dist/components/Toggle/Toggle.stories.d.ts +1 -1
  58. package/dist/components/Tooltip/Tooltip.stories.d.ts +1 -1
  59. package/dist/components/UclLogo/UclLogo.stories.d.ts +1 -1
  60. package/dist/components/WeekPicker/WeekPicker.stories.d.ts +1 -1
  61. package/dist/components/index.d.ts +1 -1
  62. package/dist/index.js +4392 -4123
  63. package/dist/utils/announce.d.ts +2 -1
  64. package/lib/Welcome.mdx +1 -1
  65. package/lib/components/Accordion/Accordion.stories.tsx +1 -1
  66. package/lib/components/Alert/Alert.mdx +1 -1
  67. package/lib/components/Alert/Alert.stories.tsx +1 -1
  68. package/lib/components/AppHeader/AppHeader.stories.tsx +1 -1
  69. package/lib/components/AppMenu/AppMenu.stories.tsx +1 -1
  70. package/lib/components/Avatar/Avatar.mdx +1 -1
  71. package/lib/components/Avatar/Avatar.stories.tsx +1 -1
  72. package/lib/components/Badge/Badge.stories.tsx +1 -1
  73. package/lib/components/BaseCheckbox/BaseCheckbox.stories.tsx +2 -2
  74. package/lib/components/Blanket/Blanket.stories.tsx +1 -1
  75. package/lib/components/Breadcrumbs/Breadcrumbs.stories.tsx +1 -1
  76. package/lib/components/Button/Button.mdx +1 -1
  77. package/lib/components/Button/Button.stories.tsx +2 -2
  78. package/lib/components/Calendar/Calendar.stories.tsx +2 -2
  79. package/lib/components/Calendar/subcomponents/Day.stories.tsx +1 -1
  80. package/lib/components/Checkbox/Checkbox.stories.tsx +2 -2
  81. package/lib/components/Chip/Chip.stories.tsx +2 -2
  82. package/lib/components/Datepicker/Datepicker.stories.tsx +2 -2
  83. package/lib/components/Dialog/BaseDialog.tsx +180 -161
  84. package/lib/components/Dialog/Dialog.stories.tsx +1 -1
  85. package/lib/components/Dialog/Dialog.tsx +15 -11
  86. package/lib/components/Divider/Divider.stories.tsx +1 -1
  87. package/lib/components/Dropdown/Dropdown.stories.tsx +1 -1
  88. package/lib/components/FeedbackDialog/FeedbackDialog.stories.tsx +1 -1
  89. package/lib/components/Field/Field.stories.tsx +2 -2
  90. package/lib/components/FileInput/FileInput.stories.tsx +1 -1
  91. package/lib/components/Footer/Footer.stories.tsx +1 -1
  92. package/lib/components/Header/Header.mdx +1 -1
  93. package/lib/components/Header/Header.stories.tsx +1 -1
  94. package/lib/components/Heading/Documentation.mdx +1 -1
  95. package/lib/components/Heading/Heading.stories.tsx +1 -1
  96. package/lib/components/Icon/Icon.stories.tsx +1 -1
  97. package/lib/components/IconButton/IconButton.stories.tsx +1 -1
  98. package/lib/components/Input/Documentation.mdx +1 -1
  99. package/lib/components/Input/Input.stories.tsx +1 -1
  100. package/lib/components/Label/Label.stories.tsx +1 -1
  101. package/lib/components/Layout/Layout.stories.tsx +1 -1
  102. package/lib/components/Link/Link.stories.tsx +1 -1
  103. package/lib/components/Main/Main.stories.tsx +1 -1
  104. package/lib/components/Modal/Modal.stories.tsx +1 -1
  105. package/lib/components/NativeDatepicker/NativeDatepicker.stories.tsx +2 -2
  106. package/lib/components/Overlay/Overlay.stories.tsx +1 -1
  107. package/lib/components/Overlay/Overlay.tsx +1 -4
  108. package/lib/components/Pagination/Pagination.stories.tsx +1 -1
  109. package/lib/components/Paragraph/Paragraph.stories.tsx +1 -1
  110. package/lib/components/Radio/Radio.stories.tsx +2 -2
  111. package/lib/components/Search/Search.stories.tsx +1 -1
  112. package/lib/components/Select/Select.mdx +1 -1
  113. package/lib/components/Select/Select.stories.tsx +2 -2
  114. package/lib/components/Select/Select.types.ts +1 -2
  115. package/lib/components/Snackbar/Snackbar.stories.tsx +1 -1
  116. package/lib/components/Spinner/Spinner.stories.tsx +1 -1
  117. package/lib/components/StandaloneLink/StandaloneLink.stories.tsx +1 -1
  118. package/lib/components/Table/Table.stories.tsx +1 -1
  119. package/lib/components/Table/subcomponents/Cell/Cell.stories.tsx +2 -2
  120. package/lib/components/Table/subcomponents/HeadCell/HeadCell.stories.tsx +1 -1
  121. package/lib/components/Tabs/Tab.tsx +209 -36
  122. package/lib/components/Tabs/TabContext.tsx +20 -7
  123. package/lib/components/Tabs/Tabs.stories.tsx +87 -68
  124. package/lib/components/Tabs/Tabs.tsx +129 -37
  125. package/lib/components/Tabs/TabsList.tsx +134 -0
  126. package/lib/components/Tabs/TabsPanel.tsx +55 -0
  127. package/lib/components/Tabs/__tests__/Tabs.test.tsx +173 -105
  128. package/lib/components/Tabs/index.ts +8 -1
  129. package/lib/components/Textarea/Textarea.stories.tsx +1 -1
  130. package/lib/components/Timepicker/Timepicker.stories.tsx +1 -1
  131. package/lib/components/Toggle/Documentation.mdx +1 -1
  132. package/lib/components/Toggle/Toggle.stories.tsx +1 -1
  133. package/lib/components/Tooltip/Tooltip.stories.tsx +1 -1
  134. package/lib/components/UclLogo/UclLogo.stories.tsx +1 -1
  135. package/lib/components/WeekPicker/WeekPicker.stories.tsx +2 -2
  136. package/lib/components/common/Common.mdx +1 -1
  137. package/lib/components/index.ts +7 -1
  138. package/lib/theme/Icons.mdx +1 -1
  139. package/lib/theme/Typography.mdx +1 -1
  140. package/lib/utils/__tests__/announce.test.ts +53 -0
  141. package/lib/utils/announce.ts +33 -10
  142. package/package.json +8 -11
  143. package/lib/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap +0 -185
@@ -7,7 +7,7 @@ import {
7
7
  Controls,
8
8
  Source,
9
9
  ArgTypes,
10
- } from '@storybook/blocks';
10
+ } from '@storybook/addon-docs/blocks';
11
11
 
12
12
  export const usage = {
13
13
  default: `<Heading>Default Heading</Heading>`,
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
 
3
3
  import Heading from './Heading';
4
4
 
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import Icon from './Icon';
3
3
  import { ComponentType } from 'react';
4
4
 
@@ -1,5 +1,5 @@
1
1
  import { ComponentType } from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react';
2
+ import type { Meta, StoryObj } from '@storybook/react-vite';
3
3
  import IconButton from './IconButton';
4
4
  import Icon from '../Icon/Icon';
5
5
 
@@ -7,7 +7,7 @@ import {
7
7
  Controls,
8
8
  Source,
9
9
  ArgTypes,
10
- } from '@storybook/blocks';
10
+ } from '@storybook/addon-docs/blocks';
11
11
 
12
12
  export const usage = {
13
13
  default: `<Input />`,
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { Input } from '../../../lib';
3
3
  import { Icon } from '../../../lib';
4
4
 
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
 
3
3
  import Label from './Label';
4
4
 
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { css } from '@emotion/css';
3
3
  import { useTheme } from '../../theme';
4
4
  import Layout from './Layout';
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
 
3
3
  import Link from './Link';
4
4
 
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import Main from './Main';
3
3
 
4
4
  const meta = {
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import Modal from './Modal';
3
3
  import Button from '../Button/Button';
4
4
  import Text from '../Paragraph/Paragraph';
@@ -1,5 +1,5 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
2
- import { useArgs } from '@storybook/preview-api';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { useArgs } from 'storybook/preview-api';
3
3
  import NativeDatepicker from './NativeDatepicker';
4
4
 
5
5
  const meta = {
@@ -1,5 +1,5 @@
1
1
  import { useRef } from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react';
2
+ import type { Meta, StoryObj } from '@storybook/react-vite';
3
3
  import { css } from '@emotion/css';
4
4
  import Overlay from './Overlay';
5
5
 
@@ -127,10 +127,7 @@ const Overlay = forwardRef<Ref, OverlayProps>(
127
127
  });
128
128
 
129
129
  const overlayPlacement = currentPlacement.split('-')[0] as
130
- | 'left'
131
- | 'right'
132
- | 'top'
133
- | 'bottom';
130
+ 'left' | 'right' | 'top' | 'bottom';
134
131
 
135
132
  useLayoutEffect(() => {
136
133
  refs.setReference(reference.current);
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import Pagination from './Pagination';
3
3
 
4
4
  const meta = {
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import Paragraph from './Paragraph';
3
3
  import { Link } from '../';
4
4
 
@@ -1,5 +1,5 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
2
- import { useArgs } from '@storybook/preview-api';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { useArgs } from 'storybook/preview-api';
3
3
 
4
4
  import Radio from './Radio';
5
5
 
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import Search from './Search';
3
3
 
4
4
  const meta = {
@@ -1,5 +1,5 @@
1
1
  import * as SelectStories from "./Select.stories";
2
- import { Meta, Title, Subtitle, Canvas, Controls, ArgTypes } from "@storybook/blocks";
2
+ import { Meta, Title, Subtitle, Canvas, Controls, ArgTypes } from "@storybook/addon-docs/blocks";
3
3
 
4
4
  export const usage = {
5
5
  default: `<Select
@@ -1,5 +1,5 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
2
- import { useArgs } from '@storybook/preview-api';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { useArgs } from 'storybook/preview-api';
3
3
  import Select from './Select';
4
4
  import { css } from '@emotion/css';
5
5
 
@@ -149,8 +149,7 @@ export type ClearableSelectProps<T = string | number> = SelectBaseProps<T> & {
149
149
  * Public props for <Select>, used by both custom and native render paths.
150
150
  */
151
151
  export type SelectProps<T = string | number> =
152
- | NonClearableSelectProps<T>
153
- | ClearableSelectProps<T>;
152
+ NonClearableSelectProps<T> | ClearableSelectProps<T>;
154
153
 
155
154
  export type InternalSelectProps<T = string | number> = SelectBaseProps<T> & {
156
155
  value?: T | null;
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { css } from '@emotion/css';
3
3
  import Snackbar from './Snackbar';
4
4
 
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { css } from '@emotion/css';
3
3
  import Spinner from './Spinner';
4
4
  import { theme } from '../../theme';
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
 
3
3
  import Link from './StandaloneLink';
4
4
 
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useState } from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react';
2
+ import type { Meta, StoryObj } from '@storybook/react-vite';
3
3
  import Table from './Table';
4
4
  import Badge from '../Badge/Badge';
5
5
  import Icon from '../Icon/Icon';
@@ -1,5 +1,5 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
2
- import { useArgs } from '@storybook/preview-api';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { useArgs } from 'storybook/preview-api';
3
3
  import { css } from '@emotion/css';
4
4
  import Row from '../Row';
5
5
  import Cell from './Cell';
@@ -1,5 +1,5 @@
1
1
  import { useState } from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react';
2
+ import type { Meta, StoryObj } from '@storybook/react-vite';
3
3
  import HeadCell from './HeadCell';
4
4
  import Head from '../Head';
5
5
  import { SortOrder } from '../../Table.types';
@@ -1,81 +1,254 @@
1
- import { memo, ButtonHTMLAttributes } from 'react';
1
+ import {
2
+ Children,
3
+ HTMLAttributes,
4
+ ReactElement,
5
+ ReactNode,
6
+ cloneElement,
7
+ isValidElement,
8
+ memo,
9
+ } from 'react';
2
10
  import { css, cx } from '@emotion/css';
3
11
  import { useTheme } from '../../theme';
4
- import { useTabContext } from './TabContext';
12
+ import Text from '../Text';
13
+ import { getTabId, getTabPanelId, useTabContext } from './TabContext';
5
14
 
6
15
  export const NAME = 'ucl-uikit-tabs__tab';
7
16
 
8
- export interface TabProps extends ButtonHTMLAttributes<HTMLButtonElement> {
17
+ type TabElementProps = HTMLAttributes<HTMLElement> & {
18
+ disabled?: boolean;
19
+ href?: string;
20
+ ref?: React.Ref<HTMLElement>;
21
+ type?: 'button' | 'submit' | 'reset';
22
+ 'data-state'?: string;
23
+ 'data-testid'?: string;
24
+ 'data-ucl-tabs-value'?: string;
25
+ };
26
+
27
+ export interface TabProps extends HTMLAttributes<HTMLElement> {
9
28
  testId?: string;
10
- label: string;
11
29
  value: string;
30
+ children?: ReactNode;
31
+ count?: number;
32
+ /** @deprecated Use count instead. */
12
33
  counter?: number;
13
- ref?: React.RefObject<HTMLButtonElement>;
34
+ disabled?: boolean;
35
+ asChild?: boolean;
36
+ ref?: React.Ref<HTMLElement>;
37
+ /** @deprecated Prefer children for the tab content. */
38
+ label?: ReactNode;
14
39
  }
15
40
 
41
+ const mergeEventHandlers =
42
+ <EventType,>(
43
+ userHandler: ((event: EventType) => void) | undefined,
44
+ childHandler: ((event: EventType) => void) | undefined
45
+ ) =>
46
+ (event: EventType) => {
47
+ userHandler?.(event);
48
+ childHandler?.(event);
49
+ };
50
+
16
51
  const Tab = ({
17
52
  testId = NAME,
18
53
  className,
54
+ children,
19
55
  label,
20
56
  value,
57
+ count,
21
58
  counter,
59
+ disabled = false,
60
+ asChild = false,
61
+ onClick,
22
62
  ref,
23
63
  ...props
24
64
  }: TabProps) => {
25
65
  const [theme] = useTheme();
26
- const { activeTab, setActiveTab, fullWidth, color } = useTabContext();
27
- const isActive = activeTab === value;
66
+ const {
67
+ value: selectedValue,
68
+ setValue,
69
+ baseId,
70
+ fullWidth,
71
+ panelValues,
72
+ } = useTabContext();
73
+ const isSelected = selectedValue === value;
74
+ const labelContent = children ?? label;
75
+ const resolvedCount = count ?? counter;
28
76
 
29
77
  const baseStyle = css`
30
- background: transparent;
31
- border: none;
32
- cursor: pointer;
33
- font-family: ${theme.font.family.primary};
34
- color: ${theme.colour.text.secondary};
35
- border-bottom: 4px solid transparent;
36
- text-align: center;
37
- font-style: normal;
38
- line-height: 140%;
39
- font-size: ${theme.font.size.f16};
78
+ all: unset;
79
+ box-sizing: border-box;
80
+ height: 40px;
81
+ display: inline-flex;
82
+ flex-direction: column;
40
83
  align-items: center;
41
84
  justify-content: center;
42
- padding: 0 16px 8px 16px;
43
- ${fullWidth ? 'flex: 1 0 0;' : 'width: auto;'}
85
+ gap: 0;
86
+ padding: 0 ${theme.padding.p16};
87
+ border-bottom: 4px solid transparent;
88
+ color: ${theme.colour.text.default};
89
+ background-color: transparent;
90
+ cursor: pointer;
91
+ text-align: center;
92
+ text-decoration: none;
93
+ white-space: nowrap;
94
+ position: relative;
95
+ outline: none;
96
+
97
+ &:visited {
98
+ color: ${theme.colour.text.default};
99
+ }
100
+
101
+ &:hover {
102
+ color: ${theme.colour.text.default};
103
+ background-color: ${theme.colour.fill.brandSubtleHover};
104
+ }
44
105
 
45
106
  &:focus-visible {
46
- outline: none;
47
107
  box-shadow: ${theme.boxShadow.focus};
108
+ z-index: 1;
48
109
  }
110
+ `;
111
+
112
+ const fullWidthStyle = css`
113
+ flex: 1 1 0;
114
+ `;
49
115
 
116
+ const autoWidthStyle = css`
117
+ flex: 0 0 auto;
118
+ `;
119
+
120
+ const selectedStyle = css`
121
+ color: ${theme.colour.text.default};
122
+ border-bottom-color: ${theme.colour.border.brand};
123
+
124
+ &:visited,
50
125
  &:hover {
51
- text-decoration: underline;
126
+ color: ${theme.colour.text.default};
52
127
  }
53
128
  `;
54
129
 
55
- const activeStyle = css`
56
- border-bottom: 4px solid ${color || theme.colour.border.brand};
57
- color: ${theme.colour.text.default};
58
- margin-bottom: -1px;
59
- font-weight: 700;
130
+ const disabledStyle = css`
131
+ color: ${theme.colour.text.disabled};
132
+ cursor: not-allowed;
60
133
 
134
+ &:visited,
61
135
  &:hover {
62
- text-decoration: none;
136
+ color: ${theme.colour.text.disabled};
137
+ background-color: transparent;
63
138
  }
64
139
  `;
65
140
 
66
- const style = cx(NAME, baseStyle, isActive && activeStyle, className);
141
+ const textStyle = css`
142
+ color: inherit;
143
+ `;
144
+
145
+ const hiddenTextStyle = css`
146
+ height: 0;
147
+ overflow: hidden;
148
+ visibility: hidden;
149
+ pointer-events: none;
150
+ `;
151
+
152
+ const style = cx(
153
+ NAME,
154
+ baseStyle,
155
+ fullWidth ? fullWidthStyle : autoWidthStyle,
156
+ isSelected && selectedStyle,
157
+ disabled && disabledStyle,
158
+ className
159
+ );
160
+
161
+ const handleClick = (event: React.MouseEvent<HTMLElement>) => {
162
+ onClick?.(event);
163
+
164
+ if (event.defaultPrevented || disabled) {
165
+ return;
166
+ }
167
+
168
+ setValue(value);
169
+ };
170
+
171
+ const commonProps: TabElementProps = {
172
+ ...props,
173
+ ref,
174
+ className: style,
175
+ role: 'tab',
176
+ id: getTabId(baseId, value),
177
+ ...(panelValues.has(value)
178
+ ? { 'aria-controls': getTabPanelId(baseId, value) }
179
+ : {}),
180
+ 'aria-selected': isSelected,
181
+ 'aria-disabled': disabled || undefined,
182
+ tabIndex: isSelected ? 0 : -1,
183
+ 'data-testid': testId,
184
+ 'data-state': isSelected ? 'active' : 'inactive',
185
+ 'data-ucl-tabs-value': value,
186
+ onClick: handleClick,
187
+ };
188
+
189
+ if (asChild) {
190
+ const child = Children.only(children) as ReactElement<TabElementProps>;
191
+
192
+ if (isValidElement(child)) {
193
+ return cloneElement(child, {
194
+ ...commonProps,
195
+ className: cx(style, child.props.className),
196
+ tabIndex: disabled ? -1 : (props.tabIndex ?? commonProps.tabIndex),
197
+ onClick: disabled
198
+ ? (event: React.MouseEvent<HTMLElement>) => {
199
+ event.preventDefault();
200
+ event.stopPropagation();
201
+ }
202
+ : mergeEventHandlers(handleClick, child.props.onClick),
203
+ children: (
204
+ <>
205
+ <Text
206
+ level={isSelected ? 'md-semibold' : 'md'}
207
+ className={textStyle}
208
+ >
209
+ {child.props.children}
210
+ {resolvedCount !== undefined ? ` ${resolvedCount}` : ''}
211
+ </Text>
212
+ <Text
213
+ aria-hidden
214
+ level='md-semibold'
215
+ className={cx(textStyle, hiddenTextStyle)}
216
+ >
217
+ {child.props.children}
218
+ {resolvedCount !== undefined ? ` ${resolvedCount}` : ''}
219
+ </Text>
220
+ </>
221
+ ),
222
+ });
223
+ }
224
+ }
225
+
226
+ const { ref: buttonRef, ...buttonProps } = commonProps;
67
227
 
68
228
  return (
69
229
  <button
70
- ref={ref}
71
- className={style}
72
- data-testid={testId}
73
- role='tab'
74
- aria-selected={isActive}
75
- onClick={() => setActiveTab(value)}
76
- {...props}
230
+ {...buttonProps}
231
+ ref={buttonRef as React.Ref<HTMLButtonElement>}
232
+ disabled={disabled}
233
+ type={
234
+ (props as { type?: 'button' | 'submit' | 'reset' }).type ?? 'button'
235
+ }
77
236
  >
78
- {label} {counter !== undefined ? `(${counter})` : ''}
237
+ <Text
238
+ level={isSelected ? 'md-semibold' : 'md'}
239
+ className={textStyle}
240
+ >
241
+ {labelContent}
242
+ {resolvedCount !== undefined ? ` ${resolvedCount}` : ''}
243
+ </Text>
244
+ <Text
245
+ aria-hidden
246
+ level='md-semibold'
247
+ className={cx(textStyle, hiddenTextStyle)}
248
+ >
249
+ {labelContent}
250
+ {resolvedCount !== undefined ? ` ${resolvedCount}` : ''}
251
+ </Text>
79
252
  </button>
80
253
  );
81
254
  };
@@ -1,20 +1,33 @@
1
1
  import { createContext, use } from 'react';
2
2
 
3
- interface TabContextType {
4
- activeTab: string;
5
- setActiveTab: (value: string) => void;
6
- onChange?: (value: string) => void;
7
- fullWidth?: boolean;
8
- color?: string;
3
+ export type TabsActivationMode = 'automatic' | 'manual';
4
+ export type TabsOrientation = 'horizontal' | 'vertical';
5
+
6
+ export interface TabsContextType {
7
+ value: string | undefined;
8
+ setValue: (value: string) => void;
9
+ baseId: string;
10
+ activationMode: TabsActivationMode;
11
+ orientation: TabsOrientation;
12
+ fullWidth: boolean;
13
+ panelValues: Set<string>;
9
14
  }
10
15
 
11
- const TabContext = createContext<TabContextType | undefined>(undefined);
16
+ const TabContext = createContext<TabsContextType | undefined>(undefined);
17
+
18
+ export const getTabId = (baseId: string, value: string) =>
19
+ `${baseId}-tab-${value}`.replace(/[^a-zA-Z0-9_-]/g, '-');
20
+
21
+ export const getTabPanelId = (baseId: string, value: string) =>
22
+ `${baseId}-panel-${value}`.replace(/[^a-zA-Z0-9_-]/g, '-');
12
23
 
13
24
  export const useTabContext = () => {
14
25
  const context = use(TabContext);
26
+
15
27
  if (!context) {
16
28
  throw new Error('Tab components must be used within a Tabs component');
17
29
  }
30
+
18
31
  return context;
19
32
  };
20
33