@tui-cruises/mein-schiff-web-react-component-library 3.1.14 → 3.1.15

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/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [3.1.15](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/compare/v3.1.14...v3.1.15) (2026-05-19)
6
+
7
+
8
+ ### Features
9
+
10
+ * **Chip:** Add a dismissable chip variant ([73d4189](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/commit/73d41894e86c52450ac42523923150f474e0e6f4))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **Checkbox:** Align hover effect with design system ([d824ffb](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/commit/d824ffbd067cc4d19284d70f7c860d50266a7379))
16
+
5
17
  ### [3.1.14](https://bitbucket.org/yours_truly/tuic-mein-schiff-web-react-component-library/compare/v3.1.13...v3.1.14) (2026-04-15)
6
18
 
7
19
 
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@tui-cruises/mein-schiff-web-react-component-library",
3
- "version": "3.1.14",
3
+ "version": "3.1.15",
4
4
  "main": "./index.tsx",
5
5
  "types": "./index.tsx",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "lint": "eslint \"**/*.ts*\"",
9
- "test": "echo \"Error: no test specified\" && exit 1",
9
+ "test": "vitest run",
10
10
  "prettier": "prettier --write ./src",
11
11
  "dev": "storybook dev -p 6006",
12
12
  "build-storybook": "storybook build",
@@ -79,6 +79,9 @@
79
79
  "@storybook/addon-docs": "10.0.1",
80
80
  "@storybook/nextjs": "10.0.1",
81
81
  "@svgr/cli": "^8.0.1",
82
+ "@testing-library/jest-dom": "^6.9.1",
83
+ "@testing-library/react": "^16.3.2",
84
+ "@testing-library/user-event": "^14.6.1",
82
85
  "@types/d3": "^7.4.3",
83
86
  "@types/lodash": "^4.17.13",
84
87
  "@types/luxon": "^3.4.2",
@@ -91,6 +94,7 @@
91
94
  "esbuild": "^0.25.11",
92
95
  "eslint": "^8.47.0",
93
96
  "eslint-plugin-storybook": "10.0.1",
97
+ "jsdom": "^29.1.1",
94
98
  "postcss": "^8.4.26",
95
99
  "prettier": "^3.0.3",
96
100
  "prettier-plugin-tailwindcss": "^0.4.1",
@@ -101,6 +105,7 @@
101
105
  "tailwindcss": "^3.4.17",
102
106
  "ts-loader": "^9.5.2",
103
107
  "tsconfig-paths-webpack-plugin": "^4.2.0",
104
- "typescript": "^5.7.3"
108
+ "typescript": "^5.7.3",
109
+ "vitest": "^4.1.6"
105
110
  }
106
111
  }
@@ -1,14 +1,15 @@
1
1
  'use client';
2
2
 
3
3
  import type { ForwardRefRenderFunction, InputHTMLAttributes } from 'react';
4
- import { Icon } from '../../core/Icon';
5
4
  import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
6
- import { twJoin } from 'tailwind-merge';
5
+ import { Icon } from '../../core/Icon';
6
+ import { twMerge } from 'tailwind-merge';
7
7
 
8
8
  type BaseProps = {
9
9
  checked?: boolean | 'indeterminate';
10
10
  defaultChecked?: boolean | 'indeterminate';
11
11
  invalid?: string;
12
+ on?: 'white' | 'gray';
12
13
  };
13
14
 
14
15
  export type CheckboxProps = BaseProps &
@@ -17,7 +18,10 @@ export type CheckboxProps = BaseProps &
17
18
  const CheckboxRenderFunction: ForwardRefRenderFunction<
18
19
  HTMLInputElement | null,
19
20
  CheckboxProps
20
- > = ({ checked, defaultChecked, invalid, ...rest }, forwardedRef) => {
21
+ > = (
22
+ { checked, defaultChecked, invalid, on = 'gray', ...rest },
23
+ forwardedRef,
24
+ ) => {
21
25
  const ref = useRef<HTMLInputElement | null>(null);
22
26
  const checkedProp = checked === 'indeterminate' ? false : checked;
23
27
  const defaultCheckedProp =
@@ -54,15 +58,18 @@ const CheckboxRenderFunction: ForwardRefRenderFunction<
54
58
  <span className="relative inline-block h-8 w-8 flex-shrink-0 md:h-6 md:w-6">
55
59
  <input
56
60
  {...rest}
57
- className={twJoin(
58
- 'peer absolute inset-0 h-8 w-8 appearance-none rounded-sm md:h-6 md:w-6',
59
- 'before:absolute before:inset-0 before:rounded-sm before:border before:border-stroke-primary-100',
61
+ className={twMerge(
62
+ 'peer absolute inset-0 h-8 w-8 appearance-none rounded-[2px] bg-surface-white md:h-6 md:w-6',
63
+ 'before:absolute before:inset-0 before:rounded-[2px] before:border before:border-stroke-primary-100',
60
64
  'checked:before:border-transparent checked:before:bg-surface-primary-100',
61
65
  'indeterminate:before:border-transparent indeterminate:before:bg-surface-primary-100',
62
66
  'invalid:before:border-stroke-error-100',
63
- 'hover:cursor-pointer hover:bg-surface-secondary-7',
67
+ 'hover:cursor-pointer hover:before:border-4',
64
68
  'focus:outline-none focus-visible:shadow-focus-state',
65
- 'disabled:before:border-stroke-secondary-40 disabled:hover:cursor-not-allowed disabled:hover:bg-transparent',
69
+ on === 'white' &&
70
+ 'disabled:before:border-stroke-secondary-40 disabled:hover:cursor-not-allowed disabled:hover:bg-transparent',
71
+ on === 'gray' &&
72
+ 'disabled:checked:before:bg-surface-white disabled:hover:before:border disabled:hover:cursor-not-allowed disabled:bg-surface-secondary-7 disabled:hover:bg-transparent',
66
73
  )}
67
74
  type="checkbox"
68
75
  defaultChecked={defaultCheckedProp}
@@ -0,0 +1,62 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { Chip } from './Chip';
5
+
6
+ describe('Chip', () => {
7
+ describe('dismissable', () => {
8
+ it('renders dismiss button when onDismiss is provided', () => {
9
+ render(<Chip onDismiss={() => {}}>Label</Chip>);
10
+ expect(screen.getByRole('button', { name: 'Dismiss' })).toBeInTheDocument();
11
+ });
12
+
13
+ it('does not render dismiss button when onDismiss is not provided', () => {
14
+ render(<Chip>Label</Chip>);
15
+ expect(screen.queryByRole('button', { name: 'Dismiss' })).not.toBeInTheDocument();
16
+ });
17
+
18
+ it('calls onDismiss when dismiss button is clicked', async () => {
19
+ const user = userEvent.setup();
20
+ const onDismiss = vi.fn();
21
+ render(<Chip onDismiss={onDismiss}>Label</Chip>);
22
+
23
+ await user.click(screen.getByRole('button', { name: 'Dismiss' }));
24
+ expect(onDismiss).toHaveBeenCalledTimes(1);
25
+ });
26
+
27
+ it('calls onDismiss on Enter key', async () => {
28
+ const user = userEvent.setup();
29
+ const onDismiss = vi.fn();
30
+ render(<Chip onDismiss={onDismiss}>Label</Chip>);
31
+
32
+ screen.getByRole('button', { name: 'Dismiss' }).focus();
33
+ await user.keyboard('{Enter}');
34
+ expect(onDismiss).toHaveBeenCalledTimes(1);
35
+ });
36
+
37
+ it('calls onDismiss on Space key', async () => {
38
+ const user = userEvent.setup();
39
+ const onDismiss = vi.fn();
40
+ render(<Chip onDismiss={onDismiss}>Label</Chip>);
41
+
42
+ screen.getByRole('button', { name: 'Dismiss' }).focus();
43
+ await user.keyboard(' ');
44
+ expect(onDismiss).toHaveBeenCalledTimes(1);
45
+ });
46
+
47
+ it('does not propagate click to parent chip button', async () => {
48
+ const user = userEvent.setup();
49
+ const onDismiss = vi.fn();
50
+ const onClick = vi.fn();
51
+ render(
52
+ <Chip onClick={onClick} onDismiss={onDismiss}>
53
+ Label
54
+ </Chip>,
55
+ );
56
+
57
+ await user.click(screen.getByRole('button', { name: 'Dismiss' }));
58
+ expect(onDismiss).toHaveBeenCalledTimes(1);
59
+ expect(onClick).not.toHaveBeenCalled();
60
+ });
61
+ });
62
+ });
@@ -1,7 +1,8 @@
1
- import type { ReactNode, RefObject } from 'react';
1
+ import type { ButtonHTMLAttributes, ReactNode, RefObject } from 'react';
2
2
  import { twJoin, twMerge } from 'tailwind-merge';
3
3
  import { Slot, Slottable } from '@radix-ui/react-slot';
4
4
  import { Pictogram, PictogramName } from '../Pictogram';
5
+ import { Icon } from '../Icon';
5
6
 
6
7
  /**
7
8
  * All allowed variants for the Chip component.
@@ -11,15 +12,13 @@ type Variant = 'text' | 'pictogram';
11
12
  /**
12
13
  * Props for the Chip component.
13
14
  */
14
- export type ChipProps = {
15
+ export type ChipProps = ButtonHTMLAttributes<HTMLButtonElement> & {
15
16
  asChild?: boolean;
16
- className?: string;
17
17
  variant?: Variant;
18
18
  pictogram?: PictogramName;
19
19
  on?: 'white' | 'gray';
20
- children?: ReactNode;
21
20
  active?: boolean;
22
- disabled?: boolean;
21
+ onDismiss?: () => void;
23
22
  ref?: RefObject<HTMLButtonElement>;
24
23
  };
25
24
 
@@ -34,6 +33,7 @@ const Chip = ({
34
33
  on = 'white',
35
34
  active = false,
36
35
  disabled = false,
36
+ onDismiss,
37
37
  children,
38
38
  ref,
39
39
  ...args
@@ -41,12 +41,17 @@ const Chip = ({
41
41
  const Element = asChild ? Slot : 'button';
42
42
 
43
43
  const borderRadius = variant === 'text' ? 'rounded-full' : 'rounded-md';
44
- const padding = variant === 'text' ? 'px-4 py-3' : 'p-3 lg:p-2';
44
+ const padding =
45
+ variant === 'text'
46
+ ? onDismiss
47
+ ? 'pl-4 pr-3 py-2'
48
+ : 'px-4 py-3'
49
+ : 'p-3 lg:p-2';
45
50
  const variantClasses = twMerge(
46
51
  'border-none text-center text-sm font-semibold transition-all',
47
52
 
48
53
  // Variants
49
- variant === 'text' && [
54
+ variant === 'text' && !onDismiss && [
50
55
  // Shared
51
56
  'text-marine-high-emphasis hover:bg-surface-primary-100',
52
57
 
@@ -63,6 +68,10 @@ const Chip = ({
63
68
  'bg-surface-white shadow-[inset_0_0_0_2px_theme(colors.stroke.secondary-20)]',
64
69
  ],
65
70
 
71
+ variant === 'text' && onDismiss && [
72
+ 'text-marine-high-emphasis bg-surface-white shadow-[inset_0_0_0_1px_theme(colors.stroke.secondary-20)] cursor-default',
73
+ ],
74
+
66
75
  variant === 'pictogram' && [
67
76
  // Shared
68
77
  'bg-surface-white shadow-[inset_0_0_0_2px_theme(colors.stroke.primary-100)] hover:shadow-[inset_0_0_0_4px_theme(colors.stroke.primary-100)]',
@@ -83,7 +92,7 @@ const Chip = ({
83
92
  ref={ref}
84
93
  {...args}
85
94
  className={twJoin(
86
- 'inline-flex items-center justify-center',
95
+ 'inline-flex items-center justify-center gap-2',
87
96
  borderRadius,
88
97
  padding,
89
98
  variantClasses,
@@ -92,6 +101,27 @@ const Chip = ({
92
101
  >
93
102
  <Slottable>{children ?? ''}</Slottable>
94
103
  {pictogram && <Pictogram name={pictogram} size="sm" />}
104
+ {onDismiss && (
105
+ <span
106
+ role="button"
107
+ aria-label="Dismiss"
108
+ tabIndex={0}
109
+ onClick={(e) => {
110
+ e.stopPropagation();
111
+ onDismiss();
112
+ }}
113
+ onKeyDown={(e) => {
114
+ if (e.key === 'Enter' || e.key === ' ') {
115
+ e.preventDefault();
116
+ e.stopPropagation();
117
+ onDismiss();
118
+ }
119
+ }}
120
+ className="inline-flex cursor-pointer"
121
+ >
122
+ <Icon name="cancel" size="xs" />
123
+ </span>
124
+ )}
95
125
  </Element>
96
126
  );
97
127
  };