@apify/ui-library 1.132.1 → 1.134.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 (147) hide show
  1. package/dist/src/components/box.d.ts.map +1 -1
  2. package/dist/src/components/card_container.js.map +1 -1
  3. package/dist/src/components/chip.d.ts.map +1 -1
  4. package/dist/src/components/chip.js.map +1 -1
  5. package/dist/src/components/code/code_block/code_block.d.ts.map +1 -1
  6. package/dist/src/components/code/inline_code/inline_code.d.ts.map +1 -1
  7. package/dist/src/components/code/one_line_code/one_line_code.d.ts.map +1 -1
  8. package/dist/src/components/code/prism_highlighter.d.ts.map +1 -1
  9. package/dist/src/components/code/prism_highlighter.js.map +1 -1
  10. package/dist/src/components/floating/tooltip.d.ts.map +1 -1
  11. package/dist/src/components/floating/tooltip.js +8 -0
  12. package/dist/src/components/floating/tooltip.js.map +1 -1
  13. package/dist/src/components/icon_button.d.ts.map +1 -1
  14. package/dist/src/components/index.d.ts +1 -0
  15. package/dist/src/components/index.d.ts.map +1 -1
  16. package/dist/src/components/index.js +1 -0
  17. package/dist/src/components/index.js.map +1 -1
  18. package/dist/src/components/link.d.ts +3 -0
  19. package/dist/src/components/link.d.ts.map +1 -1
  20. package/dist/src/components/link.js +2 -2
  21. package/dist/src/components/link.js.map +1 -1
  22. package/dist/src/components/rating.d.ts.map +1 -1
  23. package/dist/src/components/simple_markdown/simple_markdown.d.ts.map +1 -1
  24. package/dist/src/components/spinner.d.ts.map +1 -1
  25. package/dist/src/components/spinner.js.map +1 -1
  26. package/dist/src/components/store/store_actor_header.d.ts.map +1 -1
  27. package/dist/src/components/store/store_actor_header.js.map +1 -1
  28. package/dist/src/components/table/index.d.ts +17 -0
  29. package/dist/src/components/table/index.d.ts.map +1 -0
  30. package/dist/src/components/table/index.js +16 -0
  31. package/dist/src/components/table/index.js.map +1 -0
  32. package/dist/src/components/table/table.context.d.ts +3 -0
  33. package/dist/src/components/table/table.context.d.ts.map +1 -0
  34. package/dist/src/components/table/table.context.js +3 -0
  35. package/dist/src/components/table/table.context.js.map +1 -0
  36. package/dist/src/components/table/table.d.ts +3 -0
  37. package/dist/src/components/table/table.d.ts.map +1 -0
  38. package/dist/src/components/table/table.js +6 -0
  39. package/dist/src/components/table/table.js.map +1 -0
  40. package/dist/src/components/table/table.styled.d.ts +30 -0
  41. package/dist/src/components/table/table.styled.d.ts.map +1 -0
  42. package/dist/src/components/table/table.styled.js +194 -0
  43. package/dist/src/components/table/table.styled.js.map +1 -0
  44. package/dist/src/components/table/table_body.d.ts +3 -0
  45. package/dist/src/components/table/table_body.d.ts.map +1 -0
  46. package/dist/src/components/table/table_body.js +6 -0
  47. package/dist/src/components/table/table_body.js.map +1 -0
  48. package/dist/src/components/table/table_cell.d.ts +10 -0
  49. package/dist/src/components/table/table_cell.d.ts.map +1 -0
  50. package/dist/src/components/table/table_cell.js +18 -0
  51. package/dist/src/components/table/table_cell.js.map +1 -0
  52. package/dist/src/components/table/table_empty_row.d.ts +6 -0
  53. package/dist/src/components/table/table_empty_row.d.ts.map +1 -0
  54. package/dist/src/components/table/table_empty_row.js +5 -0
  55. package/dist/src/components/table/table_empty_row.js.map +1 -0
  56. package/dist/src/components/table/table_error_row.d.ts +7 -0
  57. package/dist/src/components/table/table_error_row.d.ts.map +1 -0
  58. package/dist/src/components/table/table_error_row.js +6 -0
  59. package/dist/src/components/table/table_error_row.js.map +1 -0
  60. package/dist/src/components/table/table_expansion_row.d.ts +10 -0
  61. package/dist/src/components/table/table_expansion_row.d.ts.map +1 -0
  62. package/dist/src/components/table/table_expansion_row.js +10 -0
  63. package/dist/src/components/table/table_expansion_row.js.map +1 -0
  64. package/dist/src/components/table/table_foot.d.ts +3 -0
  65. package/dist/src/components/table/table_foot.d.ts.map +1 -0
  66. package/dist/src/components/table/table_foot.js +6 -0
  67. package/dist/src/components/table/table_foot.js.map +1 -0
  68. package/dist/src/components/table/table_head.d.ts +3 -0
  69. package/dist/src/components/table/table_head.d.ts.map +1 -0
  70. package/dist/src/components/table/table_head.js +6 -0
  71. package/dist/src/components/table/table_head.js.map +1 -0
  72. package/dist/src/components/table/table_head_cell.d.ts +3 -0
  73. package/dist/src/components/table/table_head_cell.d.ts.map +1 -0
  74. package/dist/src/components/table/table_head_cell.js +6 -0
  75. package/dist/src/components/table/table_head_cell.js.map +1 -0
  76. package/dist/src/components/table/table_head_row.d.ts +3 -0
  77. package/dist/src/components/table/table_head_row.d.ts.map +1 -0
  78. package/dist/src/components/table/table_head_row.js +6 -0
  79. package/dist/src/components/table/table_head_row.js.map +1 -0
  80. package/dist/src/components/table/table_loading_row.d.ts +6 -0
  81. package/dist/src/components/table/table_loading_row.d.ts.map +1 -0
  82. package/dist/src/components/table/table_loading_row.js +6 -0
  83. package/dist/src/components/table/table_loading_row.js.map +1 -0
  84. package/dist/src/components/table/table_row.d.ts +4 -0
  85. package/dist/src/components/table/table_row.d.ts.map +1 -0
  86. package/dist/src/components/table/table_row.js +12 -0
  87. package/dist/src/components/table/table_row.js.map +1 -0
  88. package/dist/src/components/table/table_test_ids.d.ts +16 -0
  89. package/dist/src/components/table/table_test_ids.d.ts.map +1 -0
  90. package/dist/src/components/table/table_test_ids.js +16 -0
  91. package/dist/src/components/table/table_test_ids.js.map +1 -0
  92. package/dist/src/components/table/table_wrapper.d.ts +4 -0
  93. package/dist/src/components/table/table_wrapper.d.ts.map +1 -0
  94. package/dist/src/components/table/table_wrapper.js +49 -0
  95. package/dist/src/components/table/table_wrapper.js.map +1 -0
  96. package/dist/src/components/table/types.d.ts +21 -0
  97. package/dist/src/components/table/types.d.ts.map +1 -0
  98. package/dist/src/components/table/types.js +2 -0
  99. package/dist/src/components/table/types.js.map +1 -0
  100. package/dist/src/components/tag.d.ts.map +1 -1
  101. package/dist/src/components/to_consolidate/pagination.d.ts.map +1 -1
  102. package/dist/src/design_system/theme.d.ts.map +1 -1
  103. package/dist/src/type_utils.d.ts.map +1 -1
  104. package/dist/src/ui_dependency_provider.d.ts.map +1 -1
  105. package/dist/tsconfig.build.tsbuildinfo +1 -1
  106. package/package.json +3 -3
  107. package/src/components/box.tsx +1 -1
  108. package/src/components/card_container.tsx +1 -1
  109. package/src/components/chip.tsx +3 -3
  110. package/src/components/code/code_block/code_block.tsx +3 -3
  111. package/src/components/code/inline_code/inline_code.tsx +1 -1
  112. package/src/components/code/one_line_code/one_line_code.tsx +2 -2
  113. package/src/components/code/prism_highlighter.tsx +1 -1
  114. package/src/components/floating/tooltip.tsx +8 -0
  115. package/src/components/image.tsx +1 -1
  116. package/src/components/index.ts +1 -0
  117. package/src/components/link.stories.tsx +12 -0
  118. package/src/components/link.tsx +9 -0
  119. package/src/components/rating.tsx +3 -3
  120. package/src/components/simple_markdown/simple_markdown.tsx +1 -1
  121. package/src/components/spinner.tsx +1 -1
  122. package/src/components/store/store_actor_header.tsx +2 -2
  123. package/src/components/table/index.ts +16 -0
  124. package/src/components/table/table.context.ts +5 -0
  125. package/src/components/table/table.stories.tsx +258 -0
  126. package/src/components/table/table.styled.ts +207 -0
  127. package/src/components/table/table.tsx +9 -0
  128. package/src/components/table/table_body.tsx +9 -0
  129. package/src/components/table/table_cell.tsx +28 -0
  130. package/src/components/table/table_empty_row.tsx +12 -0
  131. package/src/components/table/table_error_row.tsx +13 -0
  132. package/src/components/table/table_expansion_row.tsx +24 -0
  133. package/src/components/table/table_foot.tsx +9 -0
  134. package/src/components/table/table_head.tsx +9 -0
  135. package/src/components/table/table_head_cell.tsx +9 -0
  136. package/src/components/table/table_head_row.tsx +9 -0
  137. package/src/components/table/table_loading_row.tsx +13 -0
  138. package/src/components/table/table_row.tsx +30 -0
  139. package/src/components/table/table_test_ids.ts +15 -0
  140. package/src/components/table/table_wrapper.tsx +71 -0
  141. package/src/components/table/types.ts +24 -0
  142. package/src/components/tabs/tabs.tsx +1 -1
  143. package/src/components/tag.tsx +1 -1
  144. package/src/components/to_consolidate/pagination.tsx +1 -1
  145. package/src/design_system/theme.ts +1 -1
  146. package/src/type_utils.ts +1 -1
  147. package/src/ui_dependency_provider.tsx +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apify/ui-library",
3
- "version": "1.132.1",
3
+ "version": "1.134.0",
4
4
  "description": "React UI library used by apify.com",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -23,7 +23,7 @@
23
23
  "postpublish": "npm run clean"
24
24
  },
25
25
  "//": [
26
- "Storybook for the components lives in a separate package components-storybook.",
26
+ "Storybook for the components lives in a separate package ui-storybook.",
27
27
  "It's not nice, but helps us to get around the problem of multiple react instances."
28
28
  ],
29
29
  "dependencies": {
@@ -70,5 +70,5 @@
70
70
  "src",
71
71
  "style"
72
72
  ],
73
- "gitHead": "10415388fd2ce4e24af29ddfe3090214c4a48b51"
73
+ "gitHead": "f8321843cceed834c46deb61af56d269efc3c7ae"
74
74
  }
@@ -34,7 +34,7 @@ type SharedBoxProps = {
34
34
  style?: React.CSSProperties
35
35
  onClick?: (e: React.MouseEvent) => void,
36
36
  id?: string,
37
- }
37
+ };
38
38
 
39
39
  // This should be renamed - these props are global for any element not just Boxes
40
40
  export type RegularBoxProps = SharedBoxProps & { as?: React.ElementType };
@@ -16,7 +16,7 @@ const HEADER_PLACEMENT = {
16
16
  BOTTOM: 'BOTTOM',
17
17
  } as const;
18
18
 
19
- const Wrapper = styled(Box)<{$headerPlacement: ValueOf<typeof HEADER_PLACEMENT>}>`
19
+ const Wrapper = styled(Box)<{ $headerPlacement: ValueOf<typeof HEADER_PLACEMENT> }>`
20
20
  background-color: ${theme.color.neutral.backgroundSubtle};
21
21
  border-radius: ${theme.radius.radius12};
22
22
 
@@ -80,7 +80,7 @@ const chipTypeStyle = {
80
80
  `,
81
81
  } satisfies Record<CHIP_TYPES, unknown>;
82
82
 
83
- const StyledChip = styled.span<{size: CHIP_SIZES, type: CHIP_TYPES, clickable: boolean}>`
83
+ const StyledChip = styled.span<{ size: CHIP_SIZES, type: CHIP_TYPES, clickable: boolean }>`
84
84
  ${({ size }) => chipSizeStyle[size]};
85
85
  ${({ type }) => chipTypeStyle[type]};
86
86
  /* Static styles */
@@ -100,7 +100,7 @@ export type ChipProps = RegularBoxProps & {
100
100
  size?: CHIP_SIZES
101
101
  icon?: React.ReactNode
102
102
  clickable?: boolean
103
- }
103
+ };
104
104
 
105
105
  /**
106
106
  * Component for displaying status and for labelling other components.
@@ -121,7 +121,7 @@ export const Chip = forwardRef(({
121
121
 
122
122
  Chip.displayName = 'Chip';
123
123
 
124
- type SpecificChipProps = Omit<ChipProps, 'type'>
124
+ type SpecificChipProps = Omit<ChipProps, 'type'>;
125
125
 
126
126
  export const PrimaryChip: FC<SpecificChipProps> = (props) => <Chip type={CHIP_TYPES.PRIMARY} {...props} />;
127
127
  export const NeutralChip: FC<SpecificChipProps> = (props) => <Chip type={CHIP_TYPES.DEFAULT} {...props} />;
@@ -21,7 +21,7 @@ type CodeTab = {
21
21
  language?: string;
22
22
  bashCommandsStart?: number[];
23
23
  to?: string;
24
- }
24
+ };
25
25
 
26
26
  type HeaderProps = {
27
27
  tabs: CodeTab[] | null;
@@ -29,7 +29,7 @@ type HeaderProps = {
29
29
  onTabChange: (tab: CodeTab, e: React.MouseEvent) => void;
30
30
  title?: string;
31
31
  showBashHeader?: boolean;
32
- }
32
+ };
33
33
 
34
34
  const LANGUAGES_WITHOUT_LINE_NUMBERS = ['json', 'jsonp', 'jsonp', 'rss', 'yaml', 'xml', 'html', 'bash', 'text', 'dockerfile', 'http'];
35
35
 
@@ -111,7 +111,7 @@ export type CodeBlockProps = RegularBoxProps & {
111
111
  bashCommandsStart?: number[];
112
112
  hideBashPromptPrefixes?: boolean;
113
113
  hideLineNumbers?: boolean | undefined;
114
- }
114
+ };
115
115
 
116
116
  export const CodeBlock = ({
117
117
  content,
@@ -42,7 +42,7 @@ export type InlineCodeProps = RegularBoxProps & {
42
42
  children: React.ReactNode,
43
43
  size?: SharedTextSize,
44
44
  withCopyButton?: boolean,
45
- }
45
+ };
46
46
 
47
47
  // This might be just a Chip component
48
48
  export const InlineCode: React.FC<InlineCodeProps> = ({
@@ -51,7 +51,7 @@ type OneLineCodeWrapperProps = SyntaxHighlighterBaseStylesWrapperProps & {
51
51
  $fitContent?: boolean;
52
52
  $showBashPrefixes?: boolean;
53
53
  $disabled?: boolean;
54
- }
54
+ };
55
55
 
56
56
  const OneLineCodeWrapper = styled(SyntaxHighlighterBaseStylesWrapper)<OneLineCodeWrapperProps>`
57
57
  display: flex;
@@ -160,7 +160,7 @@ export type OneLineCodeProps = Omit<BoxProps, 'children'> & {
160
160
  disabled?: boolean;
161
161
  hideCopyButton?: boolean;
162
162
  trackingBundle?: OneLineCodeTrackingBundle;
163
- }
163
+ };
164
164
 
165
165
  export function OneLineCode({
166
166
  children,
@@ -128,7 +128,7 @@ const PreWrapper = styled.pre<PreWrapperProps>`
128
128
  padding-left: ${({ $hasLinePrefixes }) => ($hasLinePrefixes ? '0.2' : '1')}rem;
129
129
  }
130
130
 
131
- ${({ $isSingleLine }) => !$isSingleLine && css<{$hasLinePrefixes: boolean}>`
131
+ ${({ $isSingleLine }) => !$isSingleLine && css<{ $hasLinePrefixes: boolean }>`
132
132
  &:hover {
133
133
  background-color: ${HIGHLIGHT_BACKGROUND_COLOR} !important;
134
134
  border-left: 6px solid ${theme.color.neutral.border} !important;
@@ -52,6 +52,14 @@ const StyledFloatingComponentBase = styled(FloatingComponentBase)<{ $isDarkTheme
52
52
  background-color: ${theme.colorPalette.dark.neutral900};
53
53
  padding: ${theme.space.space8};
54
54
 
55
+ a {
56
+ color: ${theme.colorPalette.dark.blue100};
57
+
58
+ &:hover {
59
+ color: ${theme.colorPalette.dark.blue75};
60
+ }
61
+ }
62
+
55
63
  ${({ $isDarkTheme }) => $isDarkTheme && css`
56
64
  box-shadow: ${theme.shadow.shadow2};
57
65
  border: 1px solid ${theme.color.neutral.smallTooltipBorder};
@@ -10,7 +10,7 @@ type ImageProps = {
10
10
  width?: number,
11
11
  height?: number,
12
12
  loading?: 'eager' | 'lazy' | undefined;
13
- } & ImageProxyOptions
13
+ } & ImageProxyOptions;
14
14
 
15
15
  export const Image = forwardRef<HTMLImageElement, ImageProps & Omit<BoxProps, 'as'>>((props, ref) => {
16
16
  const { InternalImage } = useSharedUiDependencies();
@@ -30,3 +30,4 @@ export * from './checkbox/index.js';
30
30
  export * from './collapsible_card/index.js';
31
31
  export * from './select/index.js';
32
32
  export * from './switch/index.js';
33
+ export * from './table/index.js';
@@ -69,6 +69,18 @@ export default {
69
69
  control: 'text',
70
70
  description: 'The link text or content to display',
71
71
  },
72
+ ariaHidden: {
73
+ control: 'boolean',
74
+ description: 'Whether the link should be hidden from screen readers (useful for decorative links)',
75
+ },
76
+ ariaLabel: {
77
+ control: 'text',
78
+ description: 'Accessible label for screen readers when the link text is not descriptive',
79
+ },
80
+ tabIndex: {
81
+ control: 'number',
82
+ description: 'Tab index for keyboard navigation (default is 0)',
83
+ },
72
84
  },
73
85
  parameters: {
74
86
  design: {
@@ -19,6 +19,9 @@ export interface RegularLinkProps {
19
19
  target?: string,
20
20
  trackingId?: string,
21
21
  trackingData?: object,
22
+ tabIndex?: number,
23
+ ariaHidden?: boolean,
24
+ ariaLabel?: string,
22
25
  }
23
26
 
24
27
  /**
@@ -104,6 +107,9 @@ export const Link = forwardRef<HTMLElement, LinkProps>(({
104
107
  onClick,
105
108
  trackingId,
106
109
  trackingData,
110
+ tabIndex,
111
+ ariaHidden,
112
+ ariaLabel,
107
113
  ...rest
108
114
  }, ref) => {
109
115
  const {
@@ -139,6 +145,9 @@ export const Link = forwardRef<HTMLElement, LinkProps>(({
139
145
  target={target || (isExternal ? '_blank' : '_self')}
140
146
  onClick={trackedOnClick}
141
147
  ref={ref}
148
+ tabIndex={tabIndex}
149
+ aria-hidden={ariaHidden}
150
+ aria-label={ariaLabel}
142
151
  {...rest}
143
152
  >
144
153
  {children}
@@ -13,7 +13,7 @@ import { Text } from './text/index.js';
13
13
 
14
14
  type RatingStatsProps = {
15
15
  ratingStats: Record<ReviewRating, number>
16
- }
16
+ };
17
17
 
18
18
  const StyledRating = styled(Box)`
19
19
  display: flex;
@@ -57,7 +57,7 @@ const StyledRatingBar = styled(Box)<{ $widthPercent: number }>`
57
57
 
58
58
  type SingleStarRatingProps = BoxProps & {
59
59
  color?: string;
60
- }
60
+ };
61
61
 
62
62
  export const SingleStarRating: FC<SingleStarRatingProps> = ({
63
63
  color = theme.color.neutral.icon,
@@ -74,7 +74,7 @@ export const SingleStarRating: FC<SingleStarRatingProps> = ({
74
74
  type RatingProps = BoxProps & {
75
75
  rating: number | undefined;
76
76
  color?: string;
77
- }
77
+ };
78
78
 
79
79
  export const Rating: FC<RatingProps> = ({
80
80
  rating = 0,
@@ -190,7 +190,7 @@ export type SimpleMarkdownProps = Omit<ReactMarkdownOptions, 'urlTransform' | 'c
190
190
  size?: MarkdownSize,
191
191
  children: string,
192
192
  className?: string,
193
- }
193
+ };
194
194
 
195
195
  export const useDefaultUrlTransform = () => {
196
196
  const { windowLocationHost } = useSharedUiDependencies();
@@ -102,7 +102,7 @@ export const BlockSpinner = styled(Spinner)`
102
102
  /**
103
103
  * Use the inline variant within buttons, texts etc. The color of the spinner is set to current color of text.
104
104
  */
105
- export const InlineSpinner = styled(Spinner)<{size?: string}>`
105
+ export const InlineSpinner = styled(Spinner)<{ size?: string }>`
106
106
  display: inline-block;
107
107
  position: relative;
108
108
  height: ${({ size }) => size || theme.space.space12};
@@ -12,7 +12,7 @@ const storeActorHeaderClassNames = {
12
12
  badge: 'ActorStoreItem-badge',
13
13
  };
14
14
 
15
- const StyledStoreActorHeader = styled(Box)<{ $compact: boolean}>`
15
+ const StyledStoreActorHeader = styled(Box)<{ $compact: boolean }>`
16
16
  display: flex;
17
17
  gap: ${({ $compact }) => ($compact ? theme.space.space4 : theme.space.space12)};
18
18
  align-items: ${({ $compact }) => ($compact ? 'flex-start' : 'center')};
@@ -79,7 +79,7 @@ export type StoreActorHeaderProps = {
79
79
  hasRisingStarBadge?: boolean,
80
80
  avatarSize?: number,
81
81
  compact?: boolean;
82
- }
82
+ };
83
83
 
84
84
  export const StoreActorHeader: React.FC<StoreActorHeaderProps & BoxProps> = ({
85
85
  name,
@@ -0,0 +1,16 @@
1
+ export { Table } from './table.js';
2
+ export { TableHead } from './table_head.js';
3
+ export { TableBody } from './table_body.js';
4
+ export { TableFoot } from './table_foot.js';
5
+ export { TableHeadRow } from './table_head_row.js';
6
+ export { TableHeadCell } from './table_head_cell.js';
7
+ export { TableRow } from './table_row.js';
8
+ export { TableCell, TableCellLink } from './table_cell.js';
9
+ export { HorizontallyScrollableTableWrapper } from './table_wrapper.js';
10
+ export { TableExpansionRow } from './table_expansion_row.js';
11
+ export { TableEmptyRow } from './table_empty_row.js';
12
+ export { TableLoadingRow } from './table_loading_row.js';
13
+ export { TableErrorRow } from './table_error_row.js';
14
+ export { tableClassNames } from './table.styled.js';
15
+ export { tableTestIds } from './table_test_ids.js';
16
+ export type { HorizontallyScrollableTableWrapperProps, TableExpansion, TableRowProps } from './types.js';
@@ -0,0 +1,5 @@
1
+ import { createContext } from 'react';
2
+
3
+ import type { To } from '../link.js';
4
+
5
+ export const TableRowLinkContext = createContext<To | undefined>(undefined);
@@ -0,0 +1,258 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { expect, within } from 'storybook/test';
3
+
4
+ import { Text } from '../text/index.js';
5
+ import { Table } from './table.js';
6
+ import { TableBody } from './table_body.js';
7
+ import { TableCell, TableCellLink } from './table_cell.js';
8
+ import { TableEmptyRow } from './table_empty_row.js';
9
+ import { TableErrorRow } from './table_error_row.js';
10
+ import { TableHead } from './table_head.js';
11
+ import { TableHeadCell } from './table_head_cell.js';
12
+ import { TableHeadRow } from './table_head_row.js';
13
+ import { TableLoadingRow } from './table_loading_row.js';
14
+ import { TableRow } from './table_row.js';
15
+ import { HorizontallyScrollableTableWrapper } from './table_wrapper.js';
16
+ import { tableTestIds } from './table_test_ids.js';
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Mock data
20
+ // ---------------------------------------------------------------------------
21
+
22
+ type MockItem = {
23
+ _id: string;
24
+ name: string;
25
+ status: string;
26
+ email: string;
27
+ };
28
+
29
+ const MOCK_DATA: MockItem[] = [
30
+ { _id: '1', name: 'Web Scraper', status: 'active', email: 'alice@example.com' },
31
+ { _id: '2', name: 'Data Extractor', status: 'inactive', email: 'bob@example.com' },
32
+ { _id: '3', name: 'Email Sender', status: 'pending', email: 'carol@example.com' },
33
+ { _id: '4', name: 'PDF Parser', status: 'active', email: 'dave@example.com' },
34
+ ];
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Storybook meta
38
+ // ---------------------------------------------------------------------------
39
+
40
+ const meta: Meta = {
41
+ title: 'UI-Library/Table',
42
+ tags: ['new'],
43
+ component: Table,
44
+ subcomponents: {
45
+ HorizontallyScrollableTableWrapper,
46
+ TableHead,
47
+ TableHeadRow,
48
+ TableHeadCell,
49
+ TableBody,
50
+ TableRow,
51
+ TableCell,
52
+ TableCellLink,
53
+ TableEmptyRow,
54
+ TableLoadingRow,
55
+ TableErrorRow,
56
+ },
57
+ parameters: {
58
+ docs: {
59
+ description: {
60
+ component: 'Compositional table primitives. Compose together to build any table layout. See subcomponents below.',
61
+ },
62
+ },
63
+ },
64
+ };
65
+
66
+ export default meta;
67
+
68
+ type Story = StoryObj;
69
+
70
+ // ---------------------------------------------------------------------------
71
+ // Stories
72
+ // ---------------------------------------------------------------------------
73
+
74
+ /** Basic table with primitives only. */
75
+ export const Default: Story = {
76
+ render: () => (
77
+ <HorizontallyScrollableTableWrapper>
78
+ <Table>
79
+ <TableHead>
80
+ <TableHeadRow>
81
+ <TableHeadCell>Name</TableHeadCell>
82
+ <TableHeadCell>Email</TableHeadCell>
83
+ <TableHeadCell>Status</TableHeadCell>
84
+ </TableHeadRow>
85
+ </TableHead>
86
+ <TableBody>
87
+ {MOCK_DATA.map((item) => (
88
+ <TableRow key={item._id}>
89
+ <TableCell>{item.name}</TableCell>
90
+ <TableCell>{item.email}</TableCell>
91
+ <TableCell>{item.status}</TableCell>
92
+ </TableRow>
93
+ ))}
94
+ </TableBody>
95
+ </Table>
96
+ </HorizontallyScrollableTableWrapper>
97
+ ),
98
+ play: async ({ canvasElement }) => {
99
+ const canvas = within(canvasElement);
100
+
101
+ await expect(canvas.getByTestId(tableTestIds.WRAPPER)).toBeInTheDocument();
102
+ await expect(canvas.getByTestId(tableTestIds.TABLE)).toBeInTheDocument();
103
+ await expect(canvas.getAllByTestId(tableTestIds.ROW).length).toBe(MOCK_DATA.length);
104
+ },
105
+ };
106
+
107
+ /** Empty state with TableEmptyRow. */
108
+ export const Empty: Story = {
109
+ render: () => (
110
+ <HorizontallyScrollableTableWrapper>
111
+ <Table>
112
+ <TableHead>
113
+ <TableHeadRow>
114
+ <TableHeadCell>Name</TableHeadCell>
115
+ <TableHeadCell>Email</TableHeadCell>
116
+ <TableHeadCell>Status</TableHeadCell>
117
+ </TableHeadRow>
118
+ </TableHead>
119
+ <TableBody>
120
+ <TableEmptyRow colSpan={3}>No data available</TableEmptyRow>
121
+ </TableBody>
122
+ </Table>
123
+ </HorizontallyScrollableTableWrapper>
124
+ ),
125
+ play: async ({ canvasElement }) => {
126
+ const canvas = within(canvasElement);
127
+
128
+ await expect(canvas.getByTestId(tableTestIds.EMPTY_ROW)).toBeInTheDocument();
129
+ await expect(canvas.queryAllByTestId(tableTestIds.ROW).length).toBe(0);
130
+ },
131
+ };
132
+
133
+ /** Loading state with TableLoadingRow. */
134
+ export const Loading: Story = {
135
+ render: () => (
136
+ <HorizontallyScrollableTableWrapper>
137
+ <Table>
138
+ <TableHead>
139
+ <TableHeadRow>
140
+ <TableHeadCell>Name</TableHeadCell>
141
+ <TableHeadCell>Email</TableHeadCell>
142
+ <TableHeadCell>Status</TableHeadCell>
143
+ </TableHeadRow>
144
+ </TableHead>
145
+ <TableBody>
146
+ <TableLoadingRow colSpan={3} />
147
+ </TableBody>
148
+ </Table>
149
+ </HorizontallyScrollableTableWrapper>
150
+ ),
151
+ play: async ({ canvasElement }) => {
152
+ const canvas = within(canvasElement);
153
+
154
+ await expect(canvas.getByTestId(tableTestIds.LOADING_ROW)).toBeInTheDocument();
155
+ },
156
+ };
157
+
158
+ /** Error state with TableErrorRow. */
159
+ export const WithError: Story = {
160
+ render: () => (
161
+ <HorizontallyScrollableTableWrapper>
162
+ <Table>
163
+ <TableHead>
164
+ <TableHeadRow>
165
+ <TableHeadCell>Name</TableHeadCell>
166
+ <TableHeadCell>Email</TableHeadCell>
167
+ <TableHeadCell>Status</TableHeadCell>
168
+ </TableHeadRow>
169
+ </TableHead>
170
+ <TableBody>
171
+ <TableErrorRow colSpan={3} error={new globalThis.Error('Connection timed out')} />
172
+ </TableBody>
173
+ </Table>
174
+ </HorizontallyScrollableTableWrapper>
175
+ ),
176
+ play: async ({ canvasElement }) => {
177
+ const canvas = within(canvasElement);
178
+
179
+ await expect(canvas.getByTestId(tableTestIds.ERROR_ROW)).toBeInTheDocument();
180
+ },
181
+ };
182
+
183
+ /** Nested links — row-level link via `to` prop + inner TableCellLink. */
184
+ export const WithNestedLinks: Story = {
185
+ render: () => (
186
+ <HorizontallyScrollableTableWrapper>
187
+ <Table>
188
+ <TableHead>
189
+ <TableHeadRow>
190
+ <TableHeadCell>Name</TableHeadCell>
191
+ <TableHeadCell>Email</TableHeadCell>
192
+ <TableHeadCell>Status</TableHeadCell>
193
+ </TableHeadRow>
194
+ </TableHead>
195
+ <TableBody>
196
+ {MOCK_DATA.map((item) => (
197
+ <TableRow key={item._id} to={`/items/${item._id}`}>
198
+ <TableCell>
199
+ <TableCellLink to={`/actors/${item._id}`}>{item.name}</TableCellLink>
200
+ </TableCell>
201
+ <TableCell>{item.email}</TableCell>
202
+ <TableCell>{item.status}</TableCell>
203
+ </TableRow>
204
+ ))}
205
+ </TableBody>
206
+ </Table>
207
+ </HorizontallyScrollableTableWrapper>
208
+ ),
209
+ play: async ({ canvasElement }) => {
210
+ const canvas = within(canvasElement);
211
+
212
+ await expect(canvas.getAllByTestId(tableTestIds.ROW).length).toBe(MOCK_DATA.length);
213
+
214
+ // Each cell in a row with `to` should have an overlay link + first cell has inner link
215
+ const cells = canvas.getAllByTestId(tableTestIds.CELL);
216
+ const firstCellLinks = cells[0].querySelectorAll('a');
217
+ await expect(firstCellLinks.length).toBe(2);
218
+ },
219
+ };
220
+
221
+ /** Direct inline data — no hooks, no data fetching. */
222
+ export const DirectData: Story = {
223
+ render: () => {
224
+ const items = [
225
+ { _id: '1', key: 'API_TOKEN', value: '••••••••', updatedAt: '2025-04-01' },
226
+ { _id: '2', key: 'BASE_URL', value: 'https://api.example.com', updatedAt: '2025-03-15' },
227
+ { _id: '3', key: 'TIMEOUT_MS', value: '30000', updatedAt: '2025-02-20' },
228
+ ];
229
+
230
+ return (
231
+ <HorizontallyScrollableTableWrapper>
232
+ <Table>
233
+ <TableHead>
234
+ <TableHeadRow>
235
+ <TableHeadCell>Key</TableHeadCell>
236
+ <TableHeadCell>Value</TableHeadCell>
237
+ <TableHeadCell>Updated</TableHeadCell>
238
+ </TableHeadRow>
239
+ </TableHead>
240
+ <TableBody>
241
+ {items.map((item) => (
242
+ <TableRow key={item._id}>
243
+ <TableCell><Text weight="bold">{item.key}</Text></TableCell>
244
+ <TableCell>{item.value}</TableCell>
245
+ <TableCell>{item.updatedAt}</TableCell>
246
+ </TableRow>
247
+ ))}
248
+ </TableBody>
249
+ </Table>
250
+ </HorizontallyScrollableTableWrapper>
251
+ );
252
+ },
253
+ play: async ({ canvasElement }) => {
254
+ const canvas = within(canvasElement);
255
+
256
+ await expect(canvas.getAllByTestId(tableTestIds.ROW).length).toBe(3);
257
+ },
258
+ };