@popsure/dirty-swan 0.48.1 → 0.49.2

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 (53) hide show
  1. package/dist/cjs/index.js +79 -64
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/lib/components/comparisonTable/components/Row/index.d.ts +1 -0
  4. package/dist/cjs/lib/components/comparisonTable/components/TableRowHeader/index.d.ts +1 -1
  5. package/dist/cjs/lib/components/comparisonTable/hooks/useComparisonTable.d.ts +3 -2
  6. package/dist/cjs/lib/components/comparisonTable/index.d.ts +14 -4
  7. package/dist/cjs/lib/components/dateSelector/index.d.ts +2 -0
  8. package/dist/esm/components/comparisonTable/components/AccordionItem/AccordionItem.js +2 -2
  9. package/dist/esm/components/comparisonTable/components/AccordionItem/AccordionItem.js.map +1 -1
  10. package/dist/esm/components/comparisonTable/components/Row/index.js +11 -4
  11. package/dist/esm/components/comparisonTable/components/Row/index.js.map +1 -1
  12. package/dist/esm/components/comparisonTable/components/TableArrows/index.js +4 -2
  13. package/dist/esm/components/comparisonTable/components/TableArrows/index.js.map +1 -1
  14. package/dist/esm/components/comparisonTable/components/TableButton/index.js +9 -4
  15. package/dist/esm/components/comparisonTable/components/TableButton/index.js.map +1 -1
  16. package/dist/esm/components/comparisonTable/components/TableButton/index.test.js +13 -7
  17. package/dist/esm/components/comparisonTable/components/TableButton/index.test.js.map +1 -1
  18. package/dist/esm/components/comparisonTable/components/TableInfoButton/index.js +1 -1
  19. package/dist/esm/components/comparisonTable/components/TableInfoButton/index.js.map +1 -1
  20. package/dist/esm/components/comparisonTable/components/TableRowHeader/index.js +9 -2
  21. package/dist/esm/components/comparisonTable/components/TableRowHeader/index.js.map +1 -1
  22. package/dist/esm/components/comparisonTable/components/TableRowHeader/index.test.js +20 -17
  23. package/dist/esm/components/comparisonTable/components/TableRowHeader/index.test.js.map +1 -1
  24. package/dist/esm/components/comparisonTable/index.js +28 -33
  25. package/dist/esm/components/comparisonTable/index.js.map +1 -1
  26. package/dist/esm/components/dateSelector/index.js +23 -15
  27. package/dist/esm/components/dateSelector/index.js.map +1 -1
  28. package/dist/esm/components/dateSelector/index.test.js +36 -2
  29. package/dist/esm/components/dateSelector/index.test.js.map +1 -1
  30. package/dist/esm/lib/components/comparisonTable/components/Row/index.d.ts +1 -0
  31. package/dist/esm/lib/components/comparisonTable/components/TableRowHeader/index.d.ts +1 -1
  32. package/dist/esm/lib/components/comparisonTable/hooks/useComparisonTable.d.ts +3 -2
  33. package/dist/esm/lib/components/comparisonTable/index.d.ts +14 -4
  34. package/dist/esm/lib/components/dateSelector/index.d.ts +2 -0
  35. package/package.json +3 -3
  36. package/src/lib/components/comparisonTable/components/AccordionItem/AccordionItem.module.scss +3 -5
  37. package/src/lib/components/comparisonTable/components/AccordionItem/AccordionItem.tsx +2 -2
  38. package/src/lib/components/comparisonTable/components/Row/index.tsx +16 -13
  39. package/src/lib/components/comparisonTable/components/Row/style.module.scss +13 -9
  40. package/src/lib/components/comparisonTable/components/TableArrows/index.tsx +2 -0
  41. package/src/lib/components/comparisonTable/components/TableArrows/style.module.scss +4 -0
  42. package/src/lib/components/comparisonTable/components/TableButton/index.test.tsx +7 -8
  43. package/src/lib/components/comparisonTable/components/TableButton/index.tsx +9 -9
  44. package/src/lib/components/comparisonTable/components/TableButton/style.module.scss +7 -24
  45. package/src/lib/components/comparisonTable/components/TableInfoButton/index.tsx +1 -0
  46. package/src/lib/components/comparisonTable/components/TableRowHeader/index.test.tsx +18 -22
  47. package/src/lib/components/comparisonTable/components/TableRowHeader/index.tsx +16 -9
  48. package/src/lib/components/comparisonTable/hooks/useComparisonTable.ts +8 -18
  49. package/src/lib/components/comparisonTable/index.stories.mdx +55 -25
  50. package/src/lib/components/comparisonTable/index.tsx +64 -25
  51. package/src/lib/components/comparisonTable/style.module.scss +23 -8
  52. package/src/lib/components/dateSelector/index.test.tsx +32 -2
  53. package/src/lib/components/dateSelector/index.tsx +40 -21
@@ -47,16 +47,27 @@ export interface ComparisonTableProps<T> {
47
47
  headers: Array<TableHeader<T>>;
48
48
  data: Array<T>;
49
49
  hideDetails?: boolean;
50
+ hideDetailsCaption?: string;
51
+ showDetailsCaption?: string;
50
52
  hideScrollBars?: boolean;
53
+ hideScrollBarsMobile?: boolean;
51
54
  collapsibleSections?: boolean;
52
55
  cellWidth?: number;
53
56
  firstColumnWidth?: number;
54
57
  stickyHeaderTopOffset?: number;
55
58
  growContent?: boolean;
56
- styles?: {
57
- header?: string;
58
- container?: string;
59
- };
59
+ classNameOverrides?: ClassNameOverrides;
60
+ onSelectionChanged?: (selectedIndex: number) => void;
61
+ }
62
+
63
+ export interface ClassNameOverrides {
64
+ header?: string;
65
+ container?: string;
66
+ cell?: string;
67
+ headerCell?: string;
68
+ collapsibleSection?: string;
69
+ section?: string;
70
+ hideDetailsButton?: string;
60
71
  }
61
72
 
62
73
  const ComparisonTable = <T extends { id: number }>(
@@ -66,13 +77,17 @@ const ComparisonTable = <T extends { id: number }>(
66
77
  headers,
67
78
  data,
68
79
  hideDetails,
69
- styles,
80
+ hideDetailsCaption = 'Hide details',
81
+ showDetailsCaption = 'Show details',
82
+ classNameOverrides,
70
83
  hideScrollBars,
84
+ hideScrollBarsMobile,
71
85
  collapsibleSections,
72
86
  cellWidth,
73
87
  firstColumnWidth,
74
88
  stickyHeaderTopOffset,
75
89
  growContent,
90
+ onSelectionChanged,
76
91
  } = props;
77
92
 
78
93
  const {
@@ -85,8 +100,7 @@ const ComparisonTable = <T extends { id: number }>(
85
100
  handleArrowsClick,
86
101
  toggleMoreRows,
87
102
  showMore,
88
- headerId,
89
- } = useComparisonTable();
103
+ } = useComparisonTable({ onSelectionChanged });
90
104
 
91
105
  const cssVariablesStyle = {
92
106
  '--tableWidth': `${headerWidth}px`,
@@ -101,16 +115,19 @@ const ComparisonTable = <T extends { id: number }>(
101
115
  } as React.CSSProperties;
102
116
 
103
117
  return (
104
- <ScrollSync onSync={scrollContainerCallbackRef}>
105
- <div style={cssVariablesStyle}>
106
- <div className={classNames(baseStyles.header, styles?.header)}>
107
- <ScrollSyncPane>
108
- <div
109
- id={headerId}
110
- className={classNames(baseStyles.container, {
111
- [baseStyles.noScrollBars]: hideScrollBars,
112
- })}
113
- >
118
+ <ScrollSync>
119
+ <div
120
+ style={cssVariablesStyle}
121
+ className={classNames({
122
+ [baseStyles.noScrollBars]: hideScrollBars,
123
+ [baseStyles.noScrollBarsMobile]: hideScrollBarsMobile,
124
+ })}
125
+ >
126
+ <div
127
+ className={classNames(baseStyles.header, classNameOverrides?.header)}
128
+ >
129
+ <ScrollSyncPane innerRef={scrollContainerCallbackRef}>
130
+ <div className={classNames(baseStyles.container)}>
114
131
  <div className={classNames(baseStyles['overflow-container'])}>
115
132
  <div className={baseStyles['group-container']}>
116
133
  <TableArrows
@@ -126,6 +143,7 @@ const ComparisonTable = <T extends { id: number }>(
126
143
  cell={headers[0].cells[0]}
127
144
  data={data}
128
145
  isRowHeader
146
+ cellClassName={classNameOverrides?.headerCell}
129
147
  />
130
148
  </div>
131
149
  </div>
@@ -148,7 +166,13 @@ const ComparisonTable = <T extends { id: number }>(
148
166
  if (index === 0 && headerGroupIndex === 0) return null;
149
167
 
150
168
  return (
151
- <Row<T> key={rowId} rowId={rowId} cell={cell} data={data} />
169
+ <Row<T>
170
+ key={rowId}
171
+ rowId={rowId}
172
+ cell={cell}
173
+ data={data}
174
+ cellClassName={classNameOverrides?.cell}
175
+ />
152
176
  );
153
177
  });
154
178
 
@@ -158,7 +182,10 @@ const ComparisonTable = <T extends { id: number }>(
158
182
  <Fragment key={idString}>
159
183
  {headerGroup.label && collapsibleSections ? (
160
184
  <AccordionItem
161
- className="mt8"
185
+ className={classNames(
186
+ 'mt16',
187
+ classNameOverrides?.collapsibleSection
188
+ )}
162
189
  label={headerGroup.label}
163
190
  headerClassName="p24 br8"
164
191
  isOpen={selectedSection === idString}
@@ -169,7 +196,8 @@ const ComparisonTable = <T extends { id: number }>(
169
196
  <div
170
197
  className={classNames(
171
198
  baseStyles.container,
172
- styles?.container,
199
+ 'pb16',
200
+ classNameOverrides?.container,
173
201
  {
174
202
  [baseStyles.noScrollBars]: hideScrollBars,
175
203
  }
@@ -188,12 +216,19 @@ const ComparisonTable = <T extends { id: number }>(
188
216
  </ScrollSyncPane>
189
217
  </AccordionItem>
190
218
  ) : (
191
- <div key={idString}>
219
+ <section
220
+ key={idString}
221
+ className={classNames(
222
+ baseStyles.section,
223
+ classNameOverrides?.section
224
+ )}
225
+ >
192
226
  <ScrollSyncPane>
193
227
  <div
194
228
  className={classNames(
195
229
  baseStyles.container,
196
- styles?.container,
230
+ 'pb16',
231
+ classNameOverrides?.container,
197
232
  {
198
233
  [baseStyles.noScrollBars]: hideScrollBars,
199
234
  }
@@ -224,7 +259,7 @@ const ComparisonTable = <T extends { id: number }>(
224
259
  </div>
225
260
  </div>
226
261
  </ScrollSyncPane>
227
- </div>
262
+ </section>
228
263
  )}
229
264
  </Fragment>
230
265
  );
@@ -239,11 +274,15 @@ const ComparisonTable = <T extends { id: number }>(
239
274
  >
240
275
  <div>
241
276
  <button
242
- className={`w100 d-flex p-a p-h4 c-pointer ${baseStyles['show-details-button']}`}
277
+ className={classNames(
278
+ 'w100 d-flex p-a p-h4 c-pointer',
279
+ baseStyles['show-details-button'],
280
+ classNameOverrides?.hideDetailsButton
281
+ )}
243
282
  onClick={toggleMoreRows}
244
283
  type="button"
245
284
  >
246
- {showMore ? 'Hide details' : 'Show details'}
285
+ {showMore ? hideDetailsCaption : showDetailsCaption}
247
286
  <Chevron
248
287
  className={showMore ? '' : baseStyles['icon-inverted']}
249
288
  />
@@ -21,9 +21,30 @@
21
21
  width: 0;
22
22
  height: 0;
23
23
  }
24
+ }
25
+
26
+ .noScrollBarsMobile {
27
+ @include p-size-mobile {
28
+ * {
29
+ // Disable the scrollbar in all browsers
30
+ scrollbar-width: none; /* Firefox */
31
+ -ms-overflow-style: none; /* Internet Explorer 10+ */
32
+ -webkit-scrollbar {
33
+ /* WebKit */
34
+ width: 0;
35
+ height: 0;
36
+ display: none;
37
+ }
38
+ }
39
+ }
40
+ }
41
+
42
+ .section + .section {
43
+ margin-top: 48px;
24
44
 
25
- // Disable snapping since it's not needed without scrollbars
26
- scroll-snap-type: unset;
45
+ @include p-size-tablet {
46
+ margin-top: 72px;
47
+ }
27
48
  }
28
49
 
29
50
  .overflow-container {
@@ -44,16 +65,10 @@
44
65
  background-color: $ds-grey-100;
45
66
  border-radius: 8px;
46
67
 
47
- margin-top: 48px;
48
-
49
68
  & > h4 {
50
69
  padding: 24px;
51
70
  display: inline-block;
52
71
  }
53
-
54
- @include p-size-tablet {
55
- margin-top: 72px;
56
- }
57
72
  }
58
73
 
59
74
  .sticky {
@@ -52,14 +52,44 @@ describe('DateSelector component', () => {
52
52
  expect(callback).toHaveBeenCalledWith('2023-07-03');
53
53
  });
54
54
 
55
- it('should call onChange empty when invalid date', async () => {
55
+ it('should not call onChange when there is an invalid date that has not been completely filled', async () => {
56
56
  const callback = jest.fn();
57
57
  const { getByLabelText, user } = setup(undefined, callback);
58
58
 
59
59
  await user.type(getByLabelText('Day'), '5');
60
60
 
61
61
  expect(getByLabelText('Day')).toHaveValue('5');
62
- expect(callback).toHaveBeenCalledWith('');
62
+ expect(callback).not.toHaveBeenCalled();
63
+
64
+ await user.type(getByLabelText('Month'), '4');
65
+
66
+ expect(getByLabelText('Month')).toHaveValue('4');
67
+ expect(callback).not.toHaveBeenCalled();
68
+
69
+ await user.type(getByLabelText('Year'), '2020');
70
+
71
+ expect(getByLabelText('Year')).toHaveValue('2020');
72
+ expect(callback).toHaveBeenCalledWith("2020-04-05");
73
+ });
74
+
75
+ it('should show error boundaries error for min date onChange empty when year out of boundaries', async () => {
76
+ const callback = jest.fn();
77
+ const date = '2010-01-01';
78
+ const { getByTestId } = setup(date, callback);
79
+
80
+ expect(getByTestId('date-error-message')).toBeVisible();
81
+ expect(getByTestId('date-error-message')).toHaveTextContent('Please choose a date after 2019');
82
+ expect(callback).not.toHaveBeenCalled();
83
+ });
84
+
85
+ it('should show error boundaries error for ,ax date onChange empty when year out of boundaries', async () => {
86
+ const callback = jest.fn();
87
+ const date = '2100-01-01';
88
+ const { getByTestId } = setup(date, callback);
89
+
90
+ expect(getByTestId('date-error-message')).toBeVisible();
91
+ expect(getByTestId('date-error-message')).toHaveTextContent('Please choose a date before 2026');
92
+ expect(callback).not.toHaveBeenCalled();
63
93
  });
64
94
 
65
95
  it('should call onChange empty when year out of boundaries', async () => {
@@ -37,6 +37,8 @@ export interface DateSelectorProps {
37
37
  year?: string;
38
38
  yearFormat?: string;
39
39
  error?: string;
40
+ errorBeforeMinYear?: string;
41
+ errorAfterMaxYear?: string;
40
42
  };
41
43
  firstDayOfWeek?: number;
42
44
  inputProps?: (key: keyof CalendarDate) => Partial<DateSelectorInputProps>;
@@ -52,34 +54,37 @@ const defaultPlaceholders: DateSelectorProps["placeholders"] = {
52
54
  error: "Please enter a valid date"
53
55
  }
54
56
 
55
- type ErrorField = 'all' | 'day' | 'month' | 'year' | undefined;
57
+ type ErrorType = 'afterMax' | 'beforeMin' | 'default';
56
58
 
57
59
  const isDateValid = (
58
60
  date: string | undefined,
59
- yearBoundaries: DateSelectorProps["yearBoundaries"]
61
+ yearBoundaries: DateSelectorProps["yearBoundaries"],
62
+ dateObject: Partial<CalendarDate>,
60
63
  ): {
61
64
  isValid: boolean;
62
- field?: ErrorField;
65
+ errorType?: ErrorType;
63
66
  } => {
64
67
  const { min = 0, max = 0 } = yearBoundaries;
65
-
68
+
66
69
  if (!date) {
67
- return { isValid: false, field: 'all' };
70
+ return { isValid: false, errorType: 'default' };
68
71
  }
69
72
 
73
+ const isValidYear = dateObject?.year && String(dateObject?.year).length === 4;
74
+
70
75
  if (max && dayjs(date).isAfter(`${max}-01-01`, 'year')) {
71
- return { isValid: false, field: 'year' };
76
+ return { isValid: false, errorType: isValidYear ? 'afterMax' : 'default' };
72
77
  }
73
78
 
74
79
  if (min && dayjs(date).isBefore(`${min}-01-01`, 'year')) {
75
- return { isValid: false, field: 'year' };
80
+ return { isValid: false, errorType: isValidYear ? 'beforeMin' : 'default' };
76
81
  }
77
82
 
78
- const isDateValid = dayjs(date, COLLECTABLE_DATE_FORMAT, true).isValid();
83
+ const isValidDate = dayjs(date, COLLECTABLE_DATE_FORMAT, true).isValid();
79
84
 
80
85
  return {
81
- isValid: isDateValid,
82
- field: isDateValid ? undefined : 'all',
86
+ isValid: isValidDate,
87
+ errorType: 'default'
83
88
  };
84
89
  }
85
90
 
@@ -100,7 +105,7 @@ export const DateSelector = ({
100
105
 
101
106
  const itemsRef = useRef<HTMLInputElement[]>([]);
102
107
  const [isDirty, setIsDirty] = useState(false);
103
- const [hasError, setHasError] = useState<ErrorField>();
108
+ const [hasError, setHasError] = useState<ErrorType>();
104
109
  const [isCalendarOpen, setIsCalendarOpen] = useState(false);
105
110
  const [internalValue, setInternalValue] = useState<Partial<CalendarDate>>({});
106
111
 
@@ -108,9 +113,9 @@ export const DateSelector = ({
108
113
  const calendarDateValue = value ? isoStringtoCalendarDate(value) : undefined;
109
114
 
110
115
  if(value !== calendarDateValue && calendarDateValue?.day && calendarDateValue?.month && calendarDateValue?.year) {
111
- const { isValid, field } = isDateValid(value, yearBoundaries)
116
+ const { isValid, errorType } = isDateValid(value, yearBoundaries, calendarDateValue)
112
117
  setInternalValue(calendarDateValue)
113
- setHasError(isValid ? undefined : field);
118
+ setHasError(isValid ? undefined : errorType);
114
119
  setIsDirty(true);
115
120
  }
116
121
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -127,15 +132,19 @@ export const DateSelector = ({
127
132
  year: tempValue.year || 0,
128
133
  })
129
134
 
130
- const { isValid, field } = isDateValid(formattedDate, yearBoundaries);
131
-
132
- if (dayjs(formattedDate, COLLECTABLE_DATE_FORMAT, true).isValid()) {
135
+ const { isValid, errorType } = isDateValid(formattedDate, yearBoundaries, tempValue);
136
+ const isDateInValidFormat = dayjs(formattedDate, COLLECTABLE_DATE_FORMAT, true).isValid();
137
+
138
+ if (isDateInValidFormat) {
133
139
  setIsDirty(true);
134
140
  }
135
141
 
136
- setHasError(isValid ? undefined : field);
137
- onChange(isValid ? formattedDate : "");
142
+ setHasError(isValid ? undefined : errorType);
138
143
  setIsCalendarOpen(false);
144
+
145
+ if (isDateInValidFormat || isDirty) {
146
+ onChange(isValid ? formattedDate : "");
147
+ }
139
148
  };
140
149
 
141
150
  const handleOnKeyDown = (event: KeyboardEvent<HTMLInputElement>, index: number) => {
@@ -212,7 +221,7 @@ export const DateSelector = ({
212
221
  placeholder: placeholders?.[`${key}Format` as FormatPlaceholder] ?? "",
213
222
  labelInsideInput: true,
214
223
  value: internalValue[key] ?? '',
215
- error: (hasError && [key, 'all'].includes(hasError)) && isDirty,
224
+ error: hasError && isDirty,
216
225
  type: "text",
217
226
  inputMode: "numeric",
218
227
  ref: (el: HTMLInputElement) => { itemsRef.current[index] = el },
@@ -249,8 +258,18 @@ export const DateSelector = ({
249
258
  </div>
250
259
 
251
260
  {hasError && isDirty && (
252
- <p className="p-p--small tc-red-500 w100 mt8">
253
- {placeholders.error}
261
+ <p
262
+ className={classNames(
263
+ hasError && isDirty ? 'd-block' : 'd-none',
264
+ "p-p--small tc-red-500 w100 mt8"
265
+ )}
266
+ data-testid="date-error-message"
267
+ >
268
+ {{
269
+ default: placeholders.error,
270
+ afterMax: placeholders.errorAfterMaxYear || `Please choose a date before ${yearBoundaries.max + 1}`,
271
+ beforeMin: placeholders.errorBeforeMinYear || `Please choose a date after ${yearBoundaries.min - 1}`,
272
+ }[hasError || "default"]}
254
273
  </p>
255
274
  )}
256
275
  </div>