@tpzdsp/next-toolkit 1.14.3 → 1.15.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpzdsp/next-toolkit",
3
- "version": "1.14.3",
3
+ "version": "1.15.0",
4
4
  "description": "A reusable React component library for Next.js applications",
5
5
  "engines": {
6
6
  "node": ">= 24.12.0",
@@ -119,6 +119,7 @@
119
119
  "preview": "vite preview",
120
120
  "dev:publish": "yalc publish",
121
121
  "dev:push": "yalc push",
122
+ "tsc:check": "tsc -b --noEmit",
122
123
  "storybook": "storybook dev -p 6006",
123
124
  "release": "semantic-release"
124
125
  },
@@ -45,7 +45,7 @@
45
45
  /* Component-specific styles */
46
46
  @layer components {
47
47
  .focus-yellow {
48
- @apply focus:border-[#ffbf47] focus:outline focus:outline-[3px] focus:outline-[#ffbf47];
48
+ @apply focus:border-focus focus:outline focus:outline-2 focus:outline-focus;
49
49
  }
50
50
 
51
51
  .library-button {
@@ -83,6 +83,10 @@
83
83
  top: calc(calc(var(--border) + var(--shadow)) * -1);
84
84
  }
85
85
  }
86
+
87
+ .input-height {
88
+ @apply h-[38px];
89
+ }
86
90
  }
87
91
 
88
92
  /* Utilities */
@@ -14,7 +14,7 @@ const CustomFallback = ({ error, resetErrorBoundary }: FallbackProps) => (
14
14
  <div role="alert" className="p-4 border border-red-300 rounded-lg bg-red-50">
15
15
  <p className="font-medium text-red-700">Custom Fallback:</p>
16
16
 
17
- <p className="text-red-600">{error.message}</p>
17
+ <p className="text-red-600">{error instanceof Error ? error.message : 'Unknown'}</p>
18
18
 
19
19
  <button
20
20
  className="px-3 py-1 mt-2 text-white bg-red-600 rounded hover:bg-red-700"
@@ -12,7 +12,7 @@ const Bomb = () => {
12
12
  // Custom fallback with button for reset
13
13
  const CustomFallback = ({ error, resetErrorBoundary }: FallbackProps) => (
14
14
  <div role="alert">
15
- <p>Custom fallback: {error.message}</p>
15
+ <p>Custom fallback: {error instanceof Error ? error.message : 'Unknown'}</p>
16
16
 
17
17
  <button onClick={resetErrorBoundary}>Try again</button>
18
18
  </div>
@@ -12,7 +12,7 @@ type ErrorBoundaryProps = {
12
12
 
13
13
  export const ErrorBoundary = ({ children, fallback, onReset }: ErrorBoundaryProps) => {
14
14
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
15
- const logError = (error: unknown, info: ErrorInfo) => {
15
+ const logError = (_error: unknown, _info: ErrorInfo) => {
16
16
  // send error to third party api, etc
17
17
  };
18
18
 
@@ -98,8 +98,8 @@ export const InfoBox = ({
98
98
  const iconClasses = cn(
99
99
  // Icon size
100
100
  'w-5 h-5',
101
- // Icon color - yellow when open, black when closed
102
- isOpen ? 'text-focus' : 'text-black',
101
+ // Icon color - yellow when open, auto when closed
102
+ isOpen ? 'text-focus' : '',
103
103
  // Hover state - yellow
104
104
  'hover:text-focus',
105
105
  // Focus state - yellow
@@ -93,9 +93,9 @@ describe('RuleDivider', () => {
93
93
 
94
94
  const [firstHr, span, secondHr] = containerElement.children;
95
95
 
96
- expect(firstHr.tagName).toBe('HR');
97
- expect(span.tagName).toBe('SPAN');
98
- expect(secondHr.tagName).toBe('HR');
96
+ expect(firstHr?.tagName).toBe('HR');
97
+ expect(span?.tagName).toBe('SPAN');
98
+ expect(secondHr?.tagName).toBe('HR');
99
99
  });
100
100
 
101
101
  it('should have proper structure when no children provided', () => {
@@ -107,7 +107,7 @@ describe('RuleDivider', () => {
107
107
 
108
108
  const hrElement = containerElement.children[0];
109
109
 
110
- expect(hrElement.tagName).toBe('HR');
110
+ expect(hrElement?.tagName).toBe('HR');
111
111
  });
112
112
 
113
113
  it('should handle empty string as children', () => {
@@ -1,7 +1,6 @@
1
1
  import { Input } from './Input';
2
2
  import { render, screen } from '../../test/renderers';
3
3
 
4
- const ROUNDED_MD = 'rounded-md';
5
4
  const BORDER_ERROR = 'border-error';
6
5
 
7
6
  describe('Input', () => {
@@ -19,13 +18,7 @@ describe('Input', () => {
19
18
 
20
19
  const input = screen.getByRole('textbox');
21
20
 
22
- expect(input).toHaveClass(
23
- ROUNDED_MD,
24
- 'border',
25
- 'p-1',
26
- 'disabled:opacity-60',
27
- 'disabled:bg-gray-100',
28
- );
21
+ expect(input).toHaveClass('border', 'p-1', 'disabled:opacity-60', 'disabled:bg-gray-100');
29
22
  });
30
23
 
31
24
  it('applies error styling when hasError is true', () => {
@@ -57,7 +50,7 @@ describe('Input', () => {
57
50
 
58
51
  const input = screen.getByRole('textbox');
59
52
 
60
- expect(input).toHaveClass(ROUNDED_MD, 'border', 'p-1', 'custom-class');
53
+ expect(input).toHaveClass('border', 'p-1', 'custom-class');
61
54
  });
62
55
 
63
56
  it('allows custom className to override default classes', () => {
@@ -105,7 +98,7 @@ describe('Input', () => {
105
98
 
106
99
  const input = screen.getByRole('textbox');
107
100
 
108
- expect(input).toHaveClass(BORDER_ERROR, 'w-full', ROUNDED_MD, 'border', 'p-1');
101
+ expect(input).toHaveClass(BORDER_ERROR, 'w-full', 'border', 'p-1');
109
102
  });
110
103
 
111
104
  it('handles different input types', () => {
@@ -174,7 +167,6 @@ describe('Input', () => {
174
167
 
175
168
  // Custom classes should override defaults due to twMerge
176
169
  expect(input).toHaveClass('border-green-500', 'p-4');
177
- expect(input).toHaveClass(ROUNDED_MD); // Default class preserved
178
170
  expect(input).toHaveClass('border'); // Base border class is still present
179
171
  expect(input).not.toHaveClass('p-1'); // Overridden by p-4
180
172
  expect(input).not.toHaveClass(BORDER_ERROR); // Overridden by border-green-500
@@ -14,8 +14,8 @@ export const Input = ({ hasError, className, ...props }: InputProps) => {
14
14
  <input
15
15
  {...props}
16
16
  className={cn(
17
- 'rounded-md border p-1 disabled:opacity-60 disabled:bg-gray-100',
18
- hasError ? 'border-error' : '',
17
+ 'border p-1 disabled:opacity-60 disabled:bg-gray-100 input-height focus-yellow',
18
+ hasError ? 'border-error' : 'border-black',
19
19
  className,
20
20
  )}
21
21
  />
@@ -3,7 +3,6 @@ import { render, screen } from '@testing-library/react';
3
3
  import { TextArea } from './TextArea';
4
4
 
5
5
  // Constants for repeated class names
6
- const ROUNDED_MD = 'rounded-md';
7
6
  const BORDER = 'border';
8
7
  const P_1 = 'p-1';
9
8
  const DISABLED_OPACITY = 'disabled:opacity-60';
@@ -26,7 +25,7 @@ describe('TextArea', () => {
26
25
 
27
26
  const textarea = screen.getByRole('textbox');
28
27
 
29
- expect(textarea).toHaveClass(ROUNDED_MD, BORDER, P_1, DISABLED_OPACITY, DISABLED_BG);
28
+ expect(textarea).toHaveClass(BORDER, P_1, DISABLED_OPACITY, DISABLED_BG);
30
29
  });
31
30
 
32
31
  it('applies error styling when hasError is true', () => {
@@ -58,7 +57,7 @@ describe('TextArea', () => {
58
57
 
59
58
  const textarea = screen.getByRole('textbox');
60
59
 
61
- expect(textarea).toHaveClass(ROUNDED_MD, BORDER, P_1, 'custom-class');
60
+ expect(textarea).toHaveClass(BORDER, P_1, 'custom-class');
62
61
  });
63
62
 
64
63
  it('allows custom className to override default classes', () => {
@@ -109,7 +108,7 @@ describe('TextArea', () => {
109
108
 
110
109
  const textarea = screen.getByRole('textbox');
111
110
 
112
- expect(textarea).toHaveClass(BORDER_ERROR, 'w-full', ROUNDED_MD, BORDER, P_1);
111
+ expect(textarea).toHaveClass(BORDER_ERROR, 'w-full', BORDER, P_1);
113
112
  });
114
113
 
115
114
  it('supports required attribute', () => {
@@ -143,7 +142,6 @@ describe('TextArea', () => {
143
142
 
144
143
  // Custom classes should override defaults due to twMerge
145
144
  expect(textarea).toHaveClass('border-green-500', 'p-4');
146
- expect(textarea).toHaveClass(ROUNDED_MD); // Default class preserved
147
145
  expect(textarea).toHaveClass(BORDER); // Base border class is still present
148
146
  expect(textarea).not.toHaveClass(P_1); // Overridden by p-4
149
147
  expect(textarea).not.toHaveClass(BORDER_ERROR); // Overridden by border-green-500
@@ -14,8 +14,8 @@ export const TextArea = ({ hasError, className, ...props }: TextAreaProps) => {
14
14
  <textarea
15
15
  {...props}
16
16
  className={cn(
17
- 'rounded-md border p-1 disabled:opacity-60 disabled:bg-gray-100',
18
- hasError ? 'border-error' : '',
17
+ 'border p-1 disabled:opacity-60 disabled:bg-gray-100',
18
+ hasError ? 'border-error' : 'border-black',
19
19
  className,
20
20
  )}
21
21
  />
@@ -5,9 +5,9 @@ import type { Credentials } from '../../../types/auth';
5
5
  import type { NavLink } from '../../../types/navigation';
6
6
 
7
7
  const navLinks: NavLink[] = [
8
- { label: 'Home', url: '/', isExternal: false },
9
- { label: 'API', url: '/api-docs', isExternal: false },
10
- { label: 'Support', url: 'https://example.com/support', isExternal: true },
8
+ { label: 'Home', url: '/', openInNewTab: false },
9
+ { label: 'API', url: '/api-docs', openInNewTab: false },
10
+ { label: 'Support', url: 'https://example.com/support', openInNewTab: true },
11
11
  ];
12
12
 
13
13
  const authenticatedCredentials: Credentials = {
@@ -3,9 +3,9 @@ import { render, screen } from '../../../test/renderers';
3
3
  import type { NavLink } from '../../../types/navigation';
4
4
 
5
5
  const NAV_LINKS: NavLink[] = [
6
- { label: 'Home', url: '/', isExternal: false },
7
- { label: 'API', url: '/api-docs', isExternal: false },
8
- { label: 'Support', url: 'https://example.com/support', isExternal: true },
6
+ { label: 'Home', url: '/', openInNewTab: false },
7
+ { label: 'API', url: '/api-docs', openInNewTab: false },
8
+ { label: 'Support', url: 'https://example.com/support', openInNewTab: true },
9
9
  ];
10
10
 
11
11
  describe('Header', () => {
@@ -3,9 +3,9 @@ import { render, screen, userEvent } from '../../../test/renderers';
3
3
  import type { NavLink } from '../../../types/navigation';
4
4
 
5
5
  const NAV_LINKS: NavLink[] = [
6
- { label: 'Home', url: '/', isExternal: false },
7
- { label: 'API', url: '/api-docs', isExternal: false },
8
- { label: 'Support', url: 'https://example.com/support', isExternal: true },
6
+ { label: 'Home', url: '/', openInNewTab: false },
7
+ { label: 'API', url: '/api-docs', openInNewTab: false },
8
+ { label: 'Support', url: 'https://example.com/support', openInNewTab: true },
9
9
  ];
10
10
 
11
11
  describe('HeaderNavClient', () => {
@@ -27,7 +27,7 @@ const OPTIONS = [
27
27
  { value: 'vanilla', label: 'Vanilla' },
28
28
  { value: 'mint', label: 'Mint' },
29
29
  { value: 'cookies', label: 'Cookies & Cream' },
30
- ];
30
+ ] as const;
31
31
 
32
32
  const GROUPED_OPTIONS = [
33
33
  {
@@ -46,16 +46,16 @@ const GROUPED_OPTIONS = [
46
46
  { value: 'spinach', label: 'Spinach' },
47
47
  ],
48
48
  },
49
- ];
49
+ ] as const;
50
50
 
51
51
  const USERS = [
52
52
  { value: 'john', label: 'John Doe', email: 'john@example.com' },
53
53
  { value: 'jane', label: 'Jane Smith', email: 'jane@example.com' },
54
54
  { value: 'bob', label: 'Bob Johnson', email: 'bob@example.com' },
55
- ];
55
+ ] as const;
56
56
 
57
- const FLAVOUR_PLACEHOLDER_TEXT = 'Select a flavour...';
58
- const FLAVOUR_MULTI_PLACEHOLDER_TEXT = 'Select multiple flavours...';
57
+ const FLAVOUR_PLACEHOLDER_TEXT = 'Select a flavour...' as const;
58
+ const FLAVOUR_MULTI_PLACEHOLDER_TEXT = 'Select multiple flavours...' as const;
59
59
 
60
60
  export const Default: Story = {
61
61
  args: {
@@ -346,11 +346,11 @@ describe('Select', () => {
346
346
 
347
347
  // Check which option was actually selected
348
348
  const selectedCall = onChangeMock.mock.calls[0];
349
- const selectedOption = selectedCall[0];
349
+ const selectedOption = selectedCall?.[0];
350
350
 
351
351
  // Verify the selected option is displayed
352
352
  await waitFor(() => {
353
- expect(screen.getByText(selectedOption.label)).toBeInTheDocument();
353
+ expect(screen.getByText(selectedOption?.label)).toBeInTheDocument();
354
354
  });
355
355
  });
356
356
 
@@ -13,7 +13,7 @@ import type {
13
13
  } from 'react-select';
14
14
  import { components, default as ReactSelect } from 'react-select';
15
15
 
16
- import { SELECT_CONTAINER_CLASSES, SELECT_CONTROL_CLASSES, SELECT_MIN_HEIGHT } from './common';
16
+ import { SELECT_CONTAINER_CLASSES, SELECT_CONTROL_CLASSES } from './common';
17
17
  import { cn } from '../../utils';
18
18
 
19
19
  // extends the react-select props with some of our own
@@ -30,7 +30,6 @@ const getClassNames = <Option, IsMulti extends boolean, Group extends GroupBase<
30
30
  control: (props) =>
31
31
  cn(
32
32
  SELECT_CONTROL_CLASSES,
33
- SELECT_MIN_HEIGHT,
34
33
  props.isDisabled ? '!cursor-not-allowed bg-gray-100' : 'bg-white',
35
34
  props.isFocused
36
35
  ? 'shadow-[0px_0px_0px_theme(borderWidth.form)_theme(colors.focus)] border-focus'
@@ -41,7 +40,7 @@ const getClassNames = <Option, IsMulti extends boolean, Group extends GroupBase<
41
40
  placeholder: (props) => cn('text-text-secondary', userClassNames?.placeholder?.(props)),
42
41
  menu: (props) =>
43
42
  cn(
44
- 'bg-white rounded-md border mt-1 overflow-hidden shadow-sm shadow-[0px_0px_6px_0px_#00000044]',
43
+ 'bg-white border border-black mt-1 overflow-hidden shadow-sm shadow-[0px_0px_6px_0px_#00000044]',
45
44
  userClassNames?.menu?.(props),
46
45
  ),
47
46
  menuList: (props) => cn('flex flex-col', userClassNames?.menuList?.(props)),
@@ -64,7 +63,7 @@ const getClassNames = <Option, IsMulti extends boolean, Group extends GroupBase<
64
63
  ),
65
64
  multiValue: (props) =>
66
65
  cn(
67
- 'flex gap-2 items-center justify-center px-2 bg-brand text-white rounded-md m-[2px]',
66
+ 'flex gap-2 items-center justify-center px-2 bg-brand text-white m-[2px]',
68
67
  userClassNames?.multiValue?.(props),
69
68
  ),
70
69
  multiValueRemove: (props) => cn('w-3 h-3', userClassNames?.multiValueRemove?.(props)),
@@ -27,7 +27,6 @@ describe('SelectSkeleton', () => {
27
27
  'h-full',
28
28
  'bg-gray-100',
29
29
  'animate-pulse',
30
- 'rounded-md',
31
30
  'col-span-2',
32
31
  );
33
32
  });
@@ -70,7 +69,7 @@ describe('SelectSkeleton', () => {
70
69
  expect(containerElement.tagName).toBe('DIV');
71
70
  });
72
71
 
73
- it('should apply SELECT_CONTROL_CLASSES and SELECT_MIN_HEIGHT to control element', () => {
72
+ it('should apply SELECT_CONTROL_CLASSES to control element', () => {
74
73
  const { container } = render(<SelectSkeleton />);
75
74
 
76
75
  const controlElement = container.firstChild?.firstChild as HTMLElement;
@@ -1,4 +1,4 @@
1
- import { SELECT_CONTAINER_CLASSES, SELECT_CONTROL_CLASSES, SELECT_MIN_HEIGHT } from './common';
1
+ import { SELECT_CONTAINER_CLASSES, SELECT_CONTROL_CLASSES } from './common';
2
2
  import type { ExtendProps } from '../../types';
3
3
  import { cn } from '../../utils';
4
4
 
@@ -12,9 +12,9 @@ export type SelectSkeletonProps = ExtendProps<'div', Props>;
12
12
  export const SelectSkeleton = ({ className, ...props }: SelectSkeletonProps = {}) => {
13
13
  return (
14
14
  <div className={cn(SELECT_CONTAINER_CLASSES, className)} {...props}>
15
- <div className={cn(SELECT_CONTROL_CLASSES, SELECT_MIN_HEIGHT)}>
15
+ <div className={cn(SELECT_CONTROL_CLASSES, 'p-2')}>
16
16
  <div
17
- className="w-full h-full bg-gray-100 animate-pulse rounded-md col-span-2"
17
+ className="w-full h-full bg-gray-100 animate-pulse col-span-2"
18
18
  aria-label="Loading options"
19
19
  ></div>
20
20
  </div>
@@ -1,4 +1,3 @@
1
- export const SELECT_MIN_HEIGHT = '!min-h-[38px]';
2
- export const SELECT_CONTAINER_CLASSES = 'w-full h-max select-none !pointer-events-auto';
1
+ export const SELECT_CONTAINER_CLASSES = 'w-full select-none !pointer-events-auto input-height';
3
2
  export const SELECT_CONTROL_CLASSES =
4
- 'px-2 py-2 !grid gap-4 grid-cols-[1fr_min-content] w-full border h-full rounded-md';
3
+ 'p-1 !grid gap-4 grid-cols-[1fr_min-content] w-full border input-height border-black';
@@ -12,6 +12,7 @@ export const MimeType = {
12
12
  MultipartForm: 'multipart/form-data',
13
13
  Text: 'text/plain',
14
14
  NTriples: 'application/n-triples',
15
+ OctetStream: 'application/octet-stream',
15
16
  } as const;
16
17
 
17
18
  /**