@qoretechnologies/reqore 0.40.3 → 0.40.5

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 (39) hide show
  1. package/__tests__/helpers/colors.test.tsx +16 -0
  2. package/build-storybook.log +119 -117
  3. package/dist/components/Button/index.d.ts.map +1 -1
  4. package/dist/components/Button/index.js +29 -3
  5. package/dist/components/Button/index.js.map +1 -1
  6. package/dist/components/Effect/index.d.ts +3 -0
  7. package/dist/components/Effect/index.d.ts.map +1 -1
  8. package/dist/components/Effect/index.js.map +1 -1
  9. package/dist/components/Tag/group.d.ts +2 -0
  10. package/dist/components/Tag/group.d.ts.map +1 -1
  11. package/dist/components/Tag/group.js +37 -3
  12. package/dist/components/Tag/group.js.map +1 -1
  13. package/dist/components/Tag/index.d.ts +1 -0
  14. package/dist/components/Tag/index.d.ts.map +1 -1
  15. package/dist/components/Tag/index.js +26 -15
  16. package/dist/components/Tag/index.js.map +1 -1
  17. package/dist/helpers/colors.d.ts +3 -2
  18. package/dist/helpers/colors.d.ts.map +1 -1
  19. package/dist/helpers/colors.js +25 -9
  20. package/dist/helpers/colors.js.map +1 -1
  21. package/dist/hooks/usePopover.d.ts.map +1 -1
  22. package/dist/hooks/usePopover.js +10 -9
  23. package/dist/hooks/usePopover.js.map +1 -1
  24. package/dist/hooks/useTooltip.js +1 -1
  25. package/dist/hooks/useTooltip.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/components/Button/index.tsx +57 -5
  28. package/src/components/Effect/index.tsx +3 -0
  29. package/src/components/Tag/group.tsx +20 -1
  30. package/src/components/Tag/index.tsx +21 -0
  31. package/src/helpers/colors.ts +35 -11
  32. package/src/hooks/usePopover.tsx +11 -9
  33. package/src/hooks/useTooltip.ts +1 -1
  34. package/src/stories/Button/Button.stories.tsx +21 -3
  35. package/src/stories/Message/Message.stories.tsx +9 -0
  36. package/src/stories/Popover/Popover.stories.tsx +35 -0
  37. package/src/stories/Tag/Tag.stories.tsx +275 -265
  38. package/src/stories/Tag/TagGroup.stories.tsx +13 -3
  39. package/tests.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { size } from 'lodash';
1
2
  import { rgba } from 'polished';
2
3
  import React, { forwardRef, memo, useCallback, useMemo, useState } from 'react';
3
4
  import styled, { css } from 'styled-components';
@@ -73,6 +74,7 @@ export interface IReqoreButtonProps
73
74
  customTheme?: IReqoreCustomTheme;
74
75
  wrap?: boolean;
75
76
  badge?: TReqoreBadge | TReqoreBadge[];
77
+
76
78
  description?: string | number;
77
79
  maxWidth?: string;
78
80
  textAlign?: 'left' | 'center' | 'right';
@@ -317,15 +319,51 @@ export const ButtonBadge = memo(({ wrapGroup, compact, ...props }: IReqoreButton
317
319
 
318
320
  // If the content is a list
319
321
  if (Array.isArray(props.content)) {
322
+ const leftBadges = props.content.filter(
323
+ (badge) => typeof badge === 'string' || typeof badge === 'number' || !badge?.align
324
+ );
325
+ const rightBadges = props.content.filter(
326
+ (badge) => typeof badge !== 'string' && typeof badge !== 'number' && badge?.align === 'right'
327
+ );
328
+ const middleBadges = props.content.filter(
329
+ (badge) => typeof badge !== 'string' && typeof badge !== 'number' && badge?.align === 'center'
330
+ );
331
+
332
+ const buildContent = (badge: TReqoreBadge) => {
333
+ if (typeof badge === 'string' || typeof badge === 'number') {
334
+ return { label: badge, align: undefined };
335
+ }
336
+
337
+ return { ...badge, align: undefined };
338
+ };
339
+
320
340
  return (
321
341
  <>
322
342
  <ReqoreSpacer
323
343
  width={props.wrap ? undefined : PADDING_FROM_SIZE[props.size] / (compact ? 2 : 1)}
324
344
  height={!props.wrap ? undefined : PADDING_FROM_SIZE[props.size] / 2}
325
345
  />
326
- <ReqoreTagGroup wrap={wrapGroup}>
327
- {props.content.map((badge, index) => renderTag({ ...props, content: badge, key: index }))}
328
- </ReqoreTagGroup>
346
+ {size(leftBadges) ? (
347
+ <ReqoreTagGroup wrap={wrapGroup} align='left'>
348
+ {leftBadges.map((badge, index) =>
349
+ renderTag({ ...props, content: buildContent(badge), key: index })
350
+ )}
351
+ </ReqoreTagGroup>
352
+ ) : null}
353
+ {size(middleBadges) ? (
354
+ <ReqoreTagGroup wrap={wrapGroup} fluid align='center'>
355
+ {middleBadges.map((badge, index) =>
356
+ renderTag({ ...props, content: buildContent(badge), key: index })
357
+ )}
358
+ </ReqoreTagGroup>
359
+ ) : null}
360
+ {size(rightBadges) ? (
361
+ <ReqoreTagGroup wrap={wrapGroup} align='right'>
362
+ {rightBadges.map((badge, index) =>
363
+ renderTag({ ...props, content: buildContent(badge), key: index })
364
+ )}
365
+ </ReqoreTagGroup>
366
+ ) : null}
329
367
  </>
330
368
  );
331
369
  }
@@ -336,7 +374,7 @@ export const ButtonBadge = memo(({ wrapGroup, compact, ...props }: IReqoreButton
336
374
  width={props.wrap ? undefined : PADDING_FROM_SIZE[props.size]}
337
375
  height={!props.wrap ? undefined : PADDING_FROM_SIZE[props.size] / 2}
338
376
  />
339
- {renderTag({ ...props, key: 0 })}
377
+ <ReqoreTagGroup>{renderTag({ ...props, key: 0 })}</ReqoreTagGroup>
340
378
  </>
341
379
  );
342
380
  });
@@ -402,6 +440,17 @@ const ReqoreButton = memo(
402
440
  const hasLeftIcon = icon || leftIconProps?.image;
403
441
  const hasRightIcon = rightIcon || rightIconProps?.image;
404
442
 
443
+ const hasRightAlignedBadge = useMemo(() => {
444
+ if (Array.isArray(badge)) {
445
+ return badge.some(
446
+ (badge) =>
447
+ typeof badge !== 'string' && typeof badge !== 'number' && badge?.align === 'right'
448
+ );
449
+ }
450
+
451
+ return typeof badge !== 'string' && typeof badge !== 'number' && badge?.align === 'right';
452
+ }, [badge]);
453
+
405
454
  return (
406
455
  <StyledButton
407
456
  {...rest}
@@ -515,7 +564,10 @@ const ReqoreButton = memo(
515
564
  style={
516
565
  textAlign !== 'right' || iconsAlign === 'center'
517
566
  ? {
518
- marginLeft: iconsAlign !== 'center' || !_children ? 'auto' : undefined,
567
+ marginLeft:
568
+ !hasRightAlignedBadge && (iconsAlign !== 'center' || !_children)
569
+ ? 'auto'
570
+ : undefined,
519
571
  marginRight:
520
572
  iconsAlign === 'center' || (textAlign === 'center' && !_children)
521
573
  ? 'auto'
@@ -28,6 +28,9 @@ export type TReqoreEffectColorManipulationAlpha =
28
28
  | 0.9
29
29
  | 1;
30
30
  export type TReqoreHexColor = `#${string}`;
31
+ export type TReqoreRgbColor = `rgb(${number}, ${number}, ${number})`;
32
+ export type TReqoreRgbaColor = `rgba(${number}, ${number}, ${number}, ${string})`;
33
+ export type TReqoreMultiTypeColor = TReqoreHexColor | TReqoreRgbColor | TReqoreRgbaColor;
31
34
  export type TReqoreColor = TReqoreHexColor | 'transparent';
32
35
  export type TReqoreEffectColor =
33
36
  | (TReqoreColor | TReqoreHexColor | 'main' | TReqoreIntent)
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import styled from 'styled-components';
2
+ import styled, { css } from 'styled-components';
3
3
  import { GAP_FROM_SIZE, TSizes } from '../../constants/sizes';
4
4
  import { IWithReqoreMinimal, IWithReqoreSize } from '../../types/global';
5
5
 
@@ -11,13 +11,32 @@ export interface IReqoreTagGroup
11
11
  gapSize?: TSizes;
12
12
  columns?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
13
13
  wrap?: boolean;
14
+ fluid?: boolean;
15
+ align?: 'left' | 'center' | 'right';
14
16
  }
15
17
 
16
18
  const StyledTagGroup = styled.div`
17
19
  flex-shrink: ${({ wrap }: IReqoreTagGroup) => (wrap ? 1 : 0)};
20
+ flex-grow: ${({ fluid }: IReqoreTagGroup) => (fluid ? 1 : undefined)};
18
21
  display: flex;
19
22
  flex-wrap: ${({ wrap }: IReqoreTagGroup) => (wrap ? 'wrap' : 'nowrap')};
20
23
  gap: ${({ gapSize }: IReqoreTagGroup) => GAP_FROM_SIZE[gapSize]}px;
24
+
25
+ ${({ align }) => {
26
+ if (align === 'right') {
27
+ return css`
28
+ margin-left: auto;
29
+ justify-content: flex-end;
30
+ `;
31
+ }
32
+
33
+ if (align === 'center') {
34
+ return css`
35
+ margin: 0 auto;
36
+ justify-content: center;
37
+ `;
38
+ }
39
+ }}
21
40
  `;
22
41
 
23
42
  const ReqoreTagGroup = ({
@@ -56,6 +56,7 @@ export interface IReqoreTagProps
56
56
  IWithReqoreEffect,
57
57
  IWithReqoreCustomTheme {
58
58
  fixed?: boolean | 'key' | 'label';
59
+ align?: 'left' | 'right' | 'center';
59
60
  size?: TSizes;
60
61
  label?: string | number;
61
62
  labelKey?: string | number;
@@ -105,6 +106,26 @@ export const StyledTag = styled(StyledEffect)<IReqoreTagStyle>`
105
106
  width: ${({ width }) => width || undefined};
106
107
  transition: all 0.2s ease-out;
107
108
 
109
+ ${({ align }) => {
110
+ if (align === 'left') {
111
+ return css`
112
+ margin-right: auto;
113
+ `;
114
+ }
115
+
116
+ if (align === 'right') {
117
+ return css`
118
+ margin-left: auto;
119
+ `;
120
+ }
121
+
122
+ if (align === 'center') {
123
+ return css`
124
+ margin: 0 auto;
125
+ `;
126
+ }
127
+ }}
128
+
108
129
  ${InactiveIconScale};
109
130
 
110
131
  ${({ wrap, hasWidth }) =>
@@ -6,6 +6,8 @@ import {
6
6
  TReqoreEffectGradientColors,
7
7
  TReqoreEffectGradientColorsObject,
8
8
  TReqoreHexColor,
9
+ TReqoreMultiTypeColor,
10
+ TReqoreRgbaColor,
9
11
  } from '../components/Effect';
10
12
  import { Colors } from '../constants/colors';
11
13
  import { DEFAULT_INTENTS, IReqoreTheme, TReqoreIntent } from '../constants/theme';
@@ -39,8 +41,9 @@ export const getReadableColorFrom = (
39
41
  ): TReqoreHexColor => {
40
42
  const returnIfLight = ifLight || lighten(dim ? 0.05 : 0, Colors.DARK);
41
43
  const returnIfDark = ifDark || darken(dim ? 0.05 : 0, Colors.LIGHT);
44
+ const fixedColor = hexAToRGBA(from);
42
45
 
43
- return readableColor(from, returnIfLight, returnIfDark, false) as TReqoreHexColor;
46
+ return readableColor(fixedColor, returnIfLight, returnIfDark, false) as TReqoreHexColor;
44
47
  };
45
48
 
46
49
  export const percentToHexAlpha = (p: number) => {
@@ -51,6 +54,22 @@ export const percentToHexAlpha = (p: number) => {
51
54
  return hexValue.padStart(2, '0').toUpperCase(); // format with leading 0 and upper case characters
52
55
  };
53
56
 
57
+ export const hexAToRGBA = (hex: TReqoreMultiTypeColor): TReqoreRgbaColor => {
58
+ // Check if this color is already in rgba format
59
+ if (hex.startsWith('rgb')) {
60
+ return hex as TReqoreRgbaColor;
61
+ }
62
+
63
+ const _hex = hex.length === 4 ? hex + hex.slice(1, 3) : hex;
64
+
65
+ const r = parseInt(_hex.slice(1, 3), 16);
66
+ const g = parseInt(_hex.slice(3, 5), 16);
67
+ const b = parseInt(_hex.slice(5, 7), 16);
68
+ const a = parseInt(_hex.slice(7, 9) || 'ff', 16) / 255;
69
+
70
+ return `rgba(${r}, ${g}, ${b}, ${a.toFixed(2)})`;
71
+ };
72
+
54
73
  export const RGBAToHexA = (rgba, forceRemoveAlpha = false): TReqoreHexColor => {
55
74
  if (isValidSixCharHex(rgba)) {
56
75
  return rgba as TReqoreHexColor;
@@ -68,8 +87,8 @@ export const RGBAToHexA = (rgba, forceRemoveAlpha = false): TReqoreHexColor => {
68
87
  .join('')) as TReqoreHexColor; // Puts the array to togehter to a string
69
88
  };
70
89
 
71
- export const shouldDarken = (mainColor: TReqoreHexColor): boolean => {
72
- return getLuminance(mainColor) > 0.2;
90
+ export const shouldDarken = (mainColor: TReqoreMultiTypeColor): boolean => {
91
+ return getLuminance(hexAToRGBA(mainColor)) > 0.2;
73
92
  };
74
93
 
75
94
  export const getMainColor: (theme: IReqoreTheme, component: string) => TReqoreHexColor = (
@@ -80,19 +99,24 @@ export const getMainColor: (theme: IReqoreTheme, component: string) => TReqoreHe
80
99
  //export const changeBasedOnContrast = (color: string, lightness?: number): string =>
81
100
 
82
101
  export const changeLightness = (color: TReqoreHexColor, lightness?: number): TReqoreHexColor => {
102
+ const fixedColor = hexAToRGBA(color);
103
+
83
104
  return lightness
84
- ? shouldDarken(color)
85
- ? (darken(lightness, color) as TReqoreHexColor)
86
- : (lighten(lightness, color) as TReqoreHexColor)
105
+ ? shouldDarken(fixedColor)
106
+ ? (darken(lightness, fixedColor) as TReqoreHexColor)
107
+ : (lighten(lightness, fixedColor) as TReqoreHexColor)
87
108
  : color;
88
109
  };
89
110
 
90
- export const changeDarkness = (color: TReqoreHexColor, lightness?: number): TReqoreHexColor =>
91
- lightness
92
- ? shouldDarken(color)
93
- ? (lighten(lightness, color) as TReqoreHexColor)
94
- : (darken(lightness, color) as TReqoreHexColor)
111
+ export const changeDarkness = (color: TReqoreHexColor, lightness?: number): TReqoreHexColor => {
112
+ const fixedColor = hexAToRGBA(color);
113
+
114
+ return lightness
115
+ ? shouldDarken(fixedColor)
116
+ ? (lighten(lightness, fixedColor) as TReqoreHexColor)
117
+ : (darken(lightness, fixedColor) as TReqoreHexColor)
95
118
  : color;
119
+ };
96
120
 
97
121
  export const getColorByBgColor = (bgColor) => {
98
122
  if (!bgColor) {
@@ -182,19 +182,21 @@ const usePopover = ({
182
182
  }
183
183
  }
184
184
 
185
+ if (!content) {
186
+ _removePopover();
187
+ }
188
+
185
189
  return () => {
186
- if (targetElement && content) {
187
- cancelTimeout();
190
+ cancelTimeout();
188
191
 
189
- targetElement.removeEventListener(startEvent, _addPopover);
192
+ targetElement?.removeEventListener(startEvent, _addPopover);
190
193
 
191
- if (endEvent) {
192
- targetElement.removeEventListener(endEvent, _removePopover);
193
- }
194
+ if (endEvent) {
195
+ targetElement?.removeEventListener(endEvent, _removePopover);
196
+ }
194
197
 
195
- if (handler === 'hoverStay') {
196
- targetElement.removeEventListener('mouseleave', cancelTimeout);
197
- }
198
+ if (handler === 'hoverStay') {
199
+ targetElement?.removeEventListener('mouseleave', cancelTimeout);
198
200
  }
199
201
  };
200
202
  }, [targetElement?.toString(), content, current, currentPopover]);
@@ -14,7 +14,7 @@ export const useTooltip = (
14
14
  );
15
15
 
16
16
  useEffect(() => {
17
- if (targetElement && content) {
17
+ if (targetElement) {
18
18
  setElement(targetElement);
19
19
  setData(tooltip);
20
20
  }
@@ -111,17 +111,34 @@ const Template: StoryFn<typeof ReqoreButton> = (buttonProps) => {
111
111
  </ReqoreButton>
112
112
  </ReqoreControlGroup>
113
113
  <ReqoreControlGroup fluid wrap>
114
- <ReqoreButton {...buttonProps} fluid>
115
- Fluid button
114
+ <ReqoreButton
115
+ {...buttonProps}
116
+ fluid
117
+ badge={[
118
+ 1,
119
+ { label: 'in da middle', align: 'center' },
120
+ { label: 'right align', align: 'right' },
121
+ 2,
122
+ ]}
123
+ >
124
+ Positioned badges
125
+ </ReqoreButton>
126
+ <ReqoreButton
127
+ {...buttonProps}
128
+ fluid
129
+ badge={[1, { label: 'right align', align: 'right' }, 2]}
130
+ >
131
+ Positioned badges
116
132
  </ReqoreButton>
117
133
  </ReqoreControlGroup>
134
+
118
135
  <ReqoreControlGroup fluid wrap>
119
136
  <ReqoreButton {...buttonProps} fluid textAlign='center'>
120
137
  Fluid button with centered text
121
138
  </ReqoreButton>
122
139
  </ReqoreControlGroup>
123
140
  <ReqoreControlGroup fluid wrap>
124
- <ReqoreButton {...buttonProps} fluid textAlign='center' iconsAlign='center'>
141
+ <ReqoreButton {...buttonProps} fluid textAlign='center' iconsAlign='center' badge='badge'>
125
142
  Fluid button with centered text & icons
126
143
  </ReqoreButton>
127
144
  <ReqoreButton
@@ -130,6 +147,7 @@ const Template: StoryFn<typeof ReqoreButton> = (buttonProps) => {
130
147
  fluid
131
148
  textAlign='center'
132
149
  iconsAlign='center'
150
+ badge={[{ label: 'test', align: 'right' }, 2]}
133
151
  >
134
152
  Fluid button with centered text & icons
135
153
  </ReqoreButton>
@@ -107,6 +107,15 @@ export const Pending: Story = {
107
107
  },
108
108
  };
109
109
 
110
+ export const Muted: Story = {
111
+ render: Template,
112
+
113
+ args: {
114
+ opaque: true,
115
+ intent: 'muted',
116
+ },
117
+ };
118
+
110
119
  export const CustomTheme: Story = {
111
120
  render: Template,
112
121
 
@@ -1,6 +1,8 @@
1
+ import { expect } from '@storybook/jest';
1
2
  import { StoryFn, StoryObj } from '@storybook/react';
2
3
  import { fireEvent } from '@storybook/testing-library';
3
4
  import { useState } from 'react';
5
+ import { useMount } from 'react-use';
4
6
  import { IReqorePopoverProps } from '../../components/Popover';
5
7
  import { sleep } from '../../helpers/utils';
6
8
  import usePopover from '../../hooks/usePopover';
@@ -16,6 +18,7 @@ import {
16
18
  ReqoreSpacer,
17
19
  ReqoreTextarea,
18
20
  } from '../../index';
21
+ import { IReqoreTooltip } from '../../types/global';
19
22
  import { StoryMeta } from '../utils';
20
23
  import { FlatArg, argManager } from '../utils/args';
21
24
 
@@ -434,3 +437,35 @@ export const ChangingElement: Story = {
434
437
  });
435
438
  },
436
439
  };
440
+
441
+ export const TooltipIsRemovedWhenContentIsEmpty: Story = {
442
+ render: (args) => {
443
+ const [tooltip, setTooltip] = useState<IReqoreTooltip>({
444
+ content: 'test',
445
+ handler: 'focus',
446
+ noArrow: true,
447
+ noWrapper: true,
448
+ useTargetWidth: true,
449
+ });
450
+
451
+ useMount(() => {
452
+ setTimeout(() => {
453
+ setTooltip(undefined);
454
+ }, 1500);
455
+ });
456
+
457
+ return <ReqoreTextarea tooltip={tooltip} />;
458
+ },
459
+
460
+ play: async ({ canvasElement }) => {
461
+ const textarea = canvasElement.querySelector('textarea');
462
+ await sleep(500);
463
+ fireEvent.focusIn(textarea);
464
+ await sleep(300);
465
+ await expect(document.querySelector('.reqore-popover-content')).toBeInTheDocument();
466
+ await sleep(1000);
467
+ fireEvent.focusIn(textarea);
468
+ await sleep(300);
469
+ await expect(document.querySelector('.reqore-popover-content')).not.toBeInTheDocument();
470
+ },
471
+ };