@takaro/lib-components 0.4.0 → 0.4.4

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 (38) hide show
  1. package/package.json +1 -1
  2. package/src/components/actions/Button/Button.stories.tsx +49 -33
  3. package/src/components/actions/Button/__snapshots__/Button.test.tsx.snap +1 -1
  4. package/src/components/actions/ContextMenu/ContextMenu.stories.tsx +1 -0
  5. package/src/components/actions/ContextMenu/index.tsx +3 -2
  6. package/src/components/actions/DropdownButton/DropdownButton.stories.tsx +7 -7
  7. package/src/components/actions/IconButton/__snapshots__/IconButton.test.tsx.snap +1 -1
  8. package/src/components/data/Chip/__snapshots__/Chip.test.tsx.snap +4 -4
  9. package/src/components/data/Drawer/Drawer.stories.tsx +7 -3
  10. package/src/components/data/Stats/Stat.tsx +104 -25
  11. package/src/components/data/Stats/Stats.stories.tsx +135 -11
  12. package/src/components/data/Stats/context.tsx +10 -3
  13. package/src/components/data/Stats/index.tsx +16 -9
  14. package/src/components/dialogs/Dialog/Dialog.stories.tsx +8 -4
  15. package/src/components/feedback/Alert/Alert.stories.tsx +7 -0
  16. package/src/components/feedback/Alert/__snapshots__/Alert.test.tsx.snap +6 -4
  17. package/src/components/feedback/Alert/index.tsx +24 -14
  18. package/src/components/feedback/Alert/style.ts +23 -15
  19. package/src/components/feedback/Tooltip/Tooltip.stories.tsx +1 -1
  20. package/src/components/feedback/snacks/Default/default.stories.tsx +6 -5
  21. package/src/components/feedback/snacks/Drawer/Drawer.stories.tsx +1 -1
  22. package/src/components/inputs/CheckBox/CheckBox.stories.tsx +1 -1
  23. package/src/components/inputs/CodeField/CodeField.stories.tsx +1 -1
  24. package/src/components/inputs/Date/DatePicker/DatePicker.stories.tsx +6 -6
  25. package/src/components/inputs/FileField/FileField.stories.tsx +3 -4
  26. package/src/components/inputs/RadioGroup/RadioGroup.stories.tsx +1 -1
  27. package/src/components/inputs/Switch/Switch.stories.tsx +1 -1
  28. package/src/components/inputs/TagField/TagField.stories.tsx +1 -1
  29. package/src/components/inputs/TextAreaField/TextAreaField.stories.tsx +3 -1
  30. package/src/components/inputs/TextField/TextField.stories.tsx +3 -1
  31. package/src/components/inputs/ValueConfirmationField/ValueConfirmationField.stories.tsx +1 -1
  32. package/src/components/inputs/selects/SelectField/SelectField.stories.tsx +2 -2
  33. package/src/components/inputs/selects/SelectQueryField/SelectQueryField.stories.tsx +3 -3
  34. package/src/components/navigation/Steppers/SlimStepper/Stepper.stories.tsx +14 -14
  35. package/src/components/navigation/Steppers/Stepper/Stepper.stories.tsx +8 -8
  36. package/src/components/other/ActionMenu/ActionMenu.stories.tsx +0 -4
  37. package/src/components/other/Empty/Empty.stories.tsx +5 -1
  38. package/src/components/other/PermissionsGuard/PermissionsGuard.stories.tsx +3 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takaro/lib-components",
3
- "version": "0.4.0",
3
+ "version": "0.4.4",
4
4
  "private": false,
5
5
  "description": "Takaro UI is a simple and customizable component library to build React apps faster within the Takaro eco system",
6
6
  "license": "AGPL-3.0-or-later",
@@ -25,7 +25,7 @@ export default {
25
25
  decorators: [(story) => <Wrapper>{story()}</Wrapper>],
26
26
  args: {
27
27
  size: 'medium',
28
- text: 'basic button',
28
+ children: 'basic button',
29
29
  variant: 'default',
30
30
  type: 'button',
31
31
  disabled: false,
@@ -44,127 +44,143 @@ export const Examples = () => (
44
44
  onClick={() => {
45
45
  /* */
46
46
  }}
47
- text="Default Button"
48
- />
47
+ >
48
+ Default Button
49
+ </Button>
49
50
  <Button
50
51
  icon={<Icon size={20} />}
51
52
  onClick={() => {
52
53
  /* */
53
54
  }}
54
- text="Icon Button"
55
- />
55
+ >
56
+ Icon Button
57
+ </Button>
56
58
  <Button
57
59
  disabled
58
60
  onClick={() => {
59
61
  /* */
60
62
  }}
61
- text="Disabled Button"
62
- />
63
+ >
64
+ Disabled Button
65
+ </Button>
63
66
  <Button
64
67
  isLoading
65
68
  onClick={() => {
66
69
  /* */
67
70
  }}
68
- text="Loading Button"
69
- />
71
+ >
72
+ Loading Button
73
+ </Button>
70
74
 
71
75
  {/* Outline */}
72
76
  <Button
73
77
  onClick={() => {
74
78
  /* */
75
79
  }}
76
- text="Outlined Button"
77
80
  variant="outline"
78
- />
81
+ >
82
+ Outlined Button
83
+ </Button>
79
84
  <Button
80
85
  icon={<Icon size={20} />}
81
86
  onClick={() => {
82
87
  /* */
83
88
  }}
84
- text="Icon Button"
85
89
  variant="outline"
86
- />
90
+ >
91
+ Icon Button
92
+ </Button>
87
93
  <Button
88
94
  disabled
89
95
  onClick={() => {
90
96
  /* */
91
97
  }}
92
- text="Disabled Button"
93
98
  variant="outline"
94
- />
99
+ >
100
+ Disabled Button
101
+ </Button>
95
102
  <Button
96
103
  isLoading
97
104
  onClick={() => {
98
105
  /* */
99
106
  }}
100
- text="Loading Button"
101
107
  variant="outline"
102
- />
108
+ >
109
+ Loading Button
110
+ </Button>
103
111
 
104
112
  {/* Clear */}
105
113
  <Button
106
114
  onClick={() => {
107
115
  /* */
108
116
  }}
109
- text="Clear Button"
110
117
  variant="clear"
111
- />
118
+ >
119
+ Clear Button
120
+ </Button>
112
121
  <Button
113
122
  icon={<Icon size={20} />}
114
123
  onClick={() => {
115
124
  /* */
116
125
  }}
117
- text="Icon Button"
118
126
  variant="clear"
119
- />
127
+ >
128
+ Icon Button
129
+ </Button>
120
130
  <Button
121
131
  disabled
122
132
  onClick={() => {
123
133
  /* */
124
134
  }}
125
- text="Disabled Button"
126
135
  variant="clear"
127
- />
136
+ >
137
+ Disabled Button
138
+ </Button>
128
139
  <Button
129
140
  isLoading
130
141
  onClick={() => {
131
142
  /* */
132
143
  }}
133
- text="Loading Button"
134
144
  variant="clear"
135
- />
145
+ >
146
+ Loading Button
147
+ </Button>
136
148
 
137
149
  {/* white */}
138
150
  <Button
139
151
  onClick={() => {
140
152
  /* */
141
153
  }}
142
- text="White Button"
143
154
  variant="white"
144
- />
155
+ >
156
+ White Button
157
+ </Button>
145
158
  <Button
146
159
  icon={<Icon size={20} />}
147
160
  onClick={() => {
148
161
  /* */
149
162
  }}
150
- text="Icon Button"
151
163
  variant="white"
152
- />
164
+ >
165
+ Icon Button
166
+ </Button>
153
167
  <Button
154
168
  disabled
155
169
  onClick={() => {
156
170
  /* */
157
171
  }}
158
- text="Disabled Button"
159
172
  variant="white"
160
- />
173
+ >
174
+ Disabled Button
175
+ </Button>
161
176
  <Button
162
177
  isLoading
163
178
  onClick={() => {
164
179
  /* */
165
180
  }}
166
- text="Loading Button"
167
181
  variant="white"
168
- />
182
+ >
183
+ Loading Button
184
+ </Button>
169
185
  </>
170
186
  );
@@ -3,7 +3,7 @@
3
3
  exports[`Should render <Button/> 1`] = `
4
4
  <div>
5
5
  <button
6
- class="sc-djTQaJ ikqgQo"
6
+ class="sc-gvPdwL csCrvu"
7
7
  color="primary"
8
8
  tabindex="0"
9
9
  type="button"
@@ -30,6 +30,7 @@ export const Target: StoryFn<ContextMenuProps> = () => {
30
30
 
31
31
  return (
32
32
  <Container ref={targetRef}>
33
+ The context menu has a targetRef prop, so it is only triggered on the orange box.
33
34
  <ContextMenu targetRef={targetRef}>
34
35
  <ContextMenu.Item label="Item 1" />
35
36
  <ContextMenu.Item label="Item 2" />
@@ -95,7 +95,8 @@ export const ContextMenu: FC<ContextMenuProps> & SubComponentTypes = ({ children
95
95
  let timeout: number;
96
96
 
97
97
  function onContextMenu(e: MouseEvent) {
98
- if (targetRef?.current && targetRef?.current.contains(e.target as Node)) {
98
+ const shouldShowMenu = !targetRef?.current || targetRef.current.contains(e.target as Node);
99
+ if (shouldShowMenu) {
99
100
  e.preventDefault();
100
101
  refs.setPositionReference({
101
102
  getBoundingClientRect() {
@@ -146,7 +147,7 @@ export const ContextMenu: FC<ContextMenuProps> & SubComponentTypes = ({ children
146
147
  }
147
148
  clearTimeout(timeout);
148
149
  };
149
- }, [refs]);
150
+ }, [refs, targetRef]);
150
151
 
151
152
  return (
152
153
  <FloatingPortal>
@@ -26,34 +26,34 @@ export const Default: StoryFn<DropdownButtonProps> = () => {
26
26
  <>
27
27
  <DropdownButton>
28
28
  <Action
29
+ text="Save changes"
29
30
  onClick={() => {
30
31
  setMessage('save changes event fired');
31
32
  }}
32
- text="Save changes"
33
33
  >
34
34
  Save changes
35
35
  </Action>
36
36
  <Action
37
+ text="Save and Schedule"
37
38
  onClick={() => {
38
39
  setMessage('Save and schedule event fired');
39
40
  }}
40
- text="Save and schedule"
41
41
  >
42
42
  Save and schedule
43
43
  </Action>
44
44
  <Action
45
+ text="Save and publish documen"
45
46
  onClick={() => {
46
47
  setMessage('save and published fired');
47
48
  }}
48
- text="Save and publish"
49
49
  >
50
50
  Save and publish
51
51
  </Action>
52
52
  <Action
53
+ text="Export PDF"
53
54
  onClick={() => {
54
55
  setMessage('Export PDF event fired');
55
56
  }}
56
- text="Export PDF"
57
57
  >
58
58
  Export PDF
59
59
  </Action>
@@ -69,15 +69,15 @@ export const Description: StoryFn<DropdownButtonProps> = () => {
69
69
  return (
70
70
  <>
71
71
  <DropdownButton>
72
- <Action onClick={() => setMessage('Merge pull request is fired')} text="Merge pull request">
72
+ <Action text="Merge pull request" onClick={() => setMessage('Merge pull request is fired')}>
73
73
  <h4>merge pull request</h4>
74
74
  <p>All commits from this branch will be added to the base branchh via a merge commit.</p>
75
75
  </Action>
76
- <Action onClick={() => setMessage('Squash and merge is fired')} text="Squash and merge">
76
+ <Action text="Squash and merge" onClick={() => setMessage('Squash and merge is fired')}>
77
77
  <h4>Squash and merge</h4>
78
78
  <p>The 4 commits from this branch will be combined into one commit in the base branch.</p>
79
79
  </Action>
80
- <Action onClick={() => setMessage('Rebase and merge is fired.')} text="Rebase and merge">
80
+ <Action text="Rebase and merge" onClick={() => setMessage('Rebase and merge is fired.')}>
81
81
  <h4>Rebase and merge</h4>
82
82
  <p>the 4 commits from this branch will be rebased and added to the base branch.</p>
83
83
  </Action>
@@ -4,7 +4,7 @@ exports[`Should render <IconButton /> 1`] = `
4
4
  <div>
5
5
  <button
6
6
  aria-label="test"
7
- class="sc-dBFDNq dwHZij"
7
+ class="sc-djVXDX cRGHRs"
8
8
  color="primary"
9
9
  type="button"
10
10
  >
@@ -3,7 +3,7 @@
3
3
  exports[`Should render <Chip/> 1`] = `
4
4
  <div>
5
5
  <div
6
- style="padding: .5rem; max-width: 100%;"
6
+ style="padding: 0.5rem; max-width: 100%;"
7
7
  >
8
8
  <div
9
9
  style="display: flex; align-items: center; gap: .5rem;"
@@ -14,17 +14,17 @@ exports[`Should render <Chip/> 1`] = `
14
14
  Something went wrong!
15
15
  </strong>
16
16
  <button
17
- style="appearance: none; font-size: .6em; border: 1px solid currentcolor; padding: .1rem .2rem; font-weight: bold; border-radius: .25rem;"
17
+ style="appearance: none; font-size: 0.6em; border: 1px solid currentcolor; padding: 0.1rem 0.2rem; font-weight: bold; border-radius: .25rem;"
18
18
  >
19
19
  Hide Error
20
20
  </button>
21
21
  </div>
22
22
  <div
23
- style="height: .25rem;"
23
+ style="height: 0.25rem;"
24
24
  />
25
25
  <div>
26
26
  <pre
27
- style="font-size: .7em; border: 1px solid red; border-radius: .25rem; padding: .3rem; color: red; overflow: auto;"
27
+ style="font-size: 0.7em; border: 1px solid red; border-radius: .25rem; padding: 0.3rem; color: red; overflow: auto;"
28
28
  >
29
29
  <code>
30
30
  Passed an incorrect argument to a color function, please pass a string representation of a color.
@@ -60,7 +60,7 @@ export const Default: StoryFn<DrawerOptions> = ({ promptCloseConfirmation }) =>
60
60
 
61
61
  return (
62
62
  <>
63
- <Button onClick={() => setOpen(true)} text="Open drawer" />
63
+ <Button onClick={() => setOpen(true)}>Open drawer</Button>
64
64
  <Drawer open={open} onOpenChange={setOpen} promptCloseConfirmation={promptCloseConfirmation}>
65
65
  <Drawer.Content>
66
66
  <Drawer.Heading>Product Details</Drawer.Heading>
@@ -140,8 +140,12 @@ export const Default: StoryFn<DrawerOptions> = ({ promptCloseConfirmation }) =>
140
140
  </Drawer.Body>
141
141
  <Drawer.Footer>
142
142
  <ButtonContainer>
143
- <Button text="Cancel" onClick={() => setOpen(false)} color="background" />
144
- <Button fullWidth text="Save changes" type="submit" form="myform" />
143
+ <Button onClick={() => setOpen(false)} color="background">
144
+ Cancel
145
+ </Button>
146
+ <Button fullWidth type="submit" form="myform">
147
+ Save changes
148
+ </Button>
145
149
  </ButtonContainer>
146
150
  </Drawer.Footer>
147
151
  </Drawer.Content>
@@ -1,13 +1,23 @@
1
- import { FC, useContext } from 'react';
1
+ import { FC, useContext, ReactNode, cloneElement, isValidElement } from 'react';
2
2
  import { styled } from '../../../styled';
3
- import { StatContext, Direction } from './context';
3
+ import { StatContext, Direction, Size } from './context';
4
+ import { AiOutlineArrowUp, AiOutlineArrowDown } from 'react-icons/ai';
4
5
 
5
- const Container = styled.div<{ hasBorder: boolean; direction: Direction }>`
6
+ const Container = styled.div<{ isGrouped: boolean; direction: Direction; size: Size }>`
6
7
  background-color: ${({ theme }) => theme.colors.backgroundAlt};
7
- padding: ${({ theme }) => theme.spacing['2']};
8
+ padding: ${({ theme, size }) => {
9
+ switch (size) {
10
+ case 'small':
11
+ return theme.spacing['1'];
12
+ case 'medium':
13
+ return theme.spacing['2'];
14
+ case 'large':
15
+ return theme.spacing['3'];
16
+ }
17
+ }};
8
18
 
9
- ${({ hasBorder, direction, theme }) => {
10
- if (hasBorder) {
19
+ ${({ isGrouped, direction, theme }) => {
20
+ if (isGrouped) {
11
21
  if (direction === 'vertical') {
12
22
  return `
13
23
  &:not(:last-child) {
@@ -22,7 +32,7 @@ const Container = styled.div<{ hasBorder: boolean; direction: Direction }>`
22
32
  border-bottom-right-radius: ${theme.borderRadius.medium};
23
33
  }
24
34
  `;
25
- } else if (direction === 'horizontal') {
35
+ } else {
26
36
  return `
27
37
  &:not(:last-child) {
28
38
  border-right: 1px solid ${theme.colors.secondary};
@@ -46,18 +56,39 @@ const Container = styled.div<{ hasBorder: boolean; direction: Direction }>`
46
56
  }};
47
57
 
48
58
  dt {
49
- font-size: ${({ theme }) => theme.fontSize.medium};
50
- font-color: ${({ theme }) => theme.colors.secondary};
59
+ font-size: ${({ theme, size }) => {
60
+ switch (size) {
61
+ case 'small':
62
+ return theme.fontSize.small;
63
+ case 'medium':
64
+ return theme.fontSize.medium;
65
+ case 'large':
66
+ return theme.fontSize.mediumLarge;
67
+ }
68
+ }};
69
+ color: ${({ theme }) => theme.colors.secondary};
51
70
  margin-bottom: ${({ theme }) => theme.spacing['0_5']};
52
71
  }
53
72
 
54
73
  dd {
55
74
  font-weight: bold;
56
75
  color: ${({ theme }) => theme.colors.white};
57
- font-size: ${({ theme }) => theme.fontSize.mediumLarge};
76
+ font-size: ${({ theme, size }) => {
77
+ switch (size) {
78
+ case 'small':
79
+ return theme.fontSize.medium;
80
+ case 'medium':
81
+ return theme.fontSize.mediumLarge;
82
+ case 'large':
83
+ return theme.fontSize.large;
84
+ }
85
+ }};
58
86
  letter-spacing: 1px;
59
87
  margin: 0;
60
88
  padding: 0;
89
+ display: flex;
90
+ align-items: center;
91
+ gap: ${({ theme }) => theme.spacing['1']};
61
92
 
62
93
  &.placeholder {
63
94
  min-width: 80%;
@@ -66,34 +97,82 @@ const Container = styled.div<{ hasBorder: boolean; direction: Direction }>`
66
97
  }
67
98
  `;
68
99
 
100
+ const TrendContainer = styled.span<{ direction: 'up' | 'down' }>`
101
+ display: inline-flex;
102
+ align-items: center;
103
+ gap: ${({ theme }) => theme.spacing['0_5']};
104
+ font-size: ${({ theme }) => theme.fontSize.small};
105
+ font-weight: normal;
106
+ color: ${({ theme, direction }) => (direction === 'up' ? theme.colors.success : theme.colors.error)};
107
+ `;
108
+
109
+ const IconWrapper = styled.span`
110
+ display: inline-flex;
111
+ align-items: center;
112
+ margin-right: ${({ theme }) => theme.spacing['0_5']};
113
+ `;
114
+
115
+ export interface TrendConfig {
116
+ direction: 'up' | 'down';
117
+ value: string | number;
118
+ }
119
+
69
120
  export interface StatProps {
70
121
  description: string;
71
- value: string;
72
- // TODO: Add icon support when needed
73
- // icon?: ReactNode
122
+ value: string | number | ReactNode;
123
+ icon?: ReactNode;
124
+ trend?: TrendConfig;
74
125
  isLoading?: boolean;
126
+ size?: Size;
75
127
  }
76
128
 
77
- export const Stat: FC<StatProps> = ({ description, value, isLoading }) => {
78
- const { border, direction } = useContext(StatContext);
129
+ export const Stat: FC<StatProps> = ({ description, value, icon, trend, isLoading, size: propSize }) => {
130
+ const { grouped, direction, size: contextSize } = useContext(StatContext);
131
+ const size = propSize ?? contextSize;
132
+
133
+ const renderValue = () => {
134
+ if (typeof value === 'string' || typeof value === 'number') {
135
+ return value.toString();
136
+ }
137
+ return value;
138
+ };
139
+
140
+ const renderIcon = () => {
141
+ if (!icon) return null;
142
+ if (isValidElement(icon)) {
143
+ return <IconWrapper>{cloneElement(icon, { size: 20 } as any)}</IconWrapper>;
144
+ }
145
+ return <IconWrapper>{icon}</IconWrapper>;
146
+ };
147
+
148
+ const renderTrend = () => {
149
+ if (!trend) return null;
150
+ const TrendIcon = trend.direction === 'up' ? AiOutlineArrowUp : AiOutlineArrowDown;
151
+ return (
152
+ <TrendContainer direction={trend.direction}>
153
+ <TrendIcon size={14} />
154
+ {trend.value}
155
+ </TrendContainer>
156
+ );
157
+ };
79
158
 
80
159
  if (isLoading) {
81
160
  return (
82
- <Container hasBorder={border} direction={direction}>
83
- <div>
84
- <dt>{description}</dt>
85
- <dd className="placeholder"></dd>
86
- </div>
161
+ <Container isGrouped={grouped} direction={direction} size={size} aria-busy="true" aria-label="Loading">
162
+ <dt>{description}</dt>
163
+ <dd className="placeholder"></dd>
87
164
  </Container>
88
165
  );
89
166
  }
90
167
 
91
168
  return (
92
- <Container hasBorder={border} direction={direction}>
93
- <div>
94
- <dt>{description}</dt>
95
- <dd>{value}</dd>
96
- </div>
169
+ <Container isGrouped={grouped} direction={direction} size={size}>
170
+ <dt>{description}</dt>
171
+ <dd>
172
+ {renderIcon()}
173
+ {renderValue()}
174
+ {renderTrend()}
175
+ </dd>
97
176
  </Container>
98
177
  );
99
178
  };