@helpwave/hightide 0.0.1
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/.storybook/main.ts +24 -0
- package/.storybook/preview.tsx +67 -0
- package/LICENSE +373 -0
- package/README.md +8 -0
- package/coloring/shading.ts +46 -0
- package/coloring/types.ts +13 -0
- package/components/Avatar.tsx +58 -0
- package/components/AvatarGroup.tsx +48 -0
- package/components/BreadCrumb.tsx +35 -0
- package/components/Button.tsx +236 -0
- package/components/ChipList.tsx +89 -0
- package/components/Circle.tsx +27 -0
- package/components/ErrorComponent.tsx +40 -0
- package/components/Expandable.tsx +61 -0
- package/components/HelpwaveBadge.tsx +35 -0
- package/components/HideableContentSection.tsx +43 -0
- package/components/InputGroup.tsx +72 -0
- package/components/LoadingAndErrorComponent.tsx +47 -0
- package/components/LoadingAnimation.tsx +40 -0
- package/components/LoadingButton.tsx +27 -0
- package/components/MarkdownInterpreter.tsx +278 -0
- package/components/Pagination.tsx +65 -0
- package/components/Profile.tsx +124 -0
- package/components/ProgressIndicator.tsx +58 -0
- package/components/Ring.tsx +286 -0
- package/components/SearchableList.tsx +69 -0
- package/components/SortButton.tsx +33 -0
- package/components/Span.tsx +0 -0
- package/components/StepperBar.tsx +124 -0
- package/components/Table.tsx +330 -0
- package/components/TechRadar.tsx +247 -0
- package/components/TextImage.tsx +86 -0
- package/components/TimeDisplay.tsx +121 -0
- package/components/Tooltip.tsx +92 -0
- package/components/VerticalDivider.tsx +51 -0
- package/components/date/DatePicker.tsx +164 -0
- package/components/date/DayPicker.tsx +95 -0
- package/components/date/TimePicker.tsx +167 -0
- package/components/date/YearMonthPicker.tsx +130 -0
- package/components/examples/InputGroupExample.tsx +58 -0
- package/components/examples/MultiSelectExample.tsx +57 -0
- package/components/examples/SearchableSelectExample.tsx +34 -0
- package/components/examples/SelectExample.tsx +28 -0
- package/components/examples/StackingModals.tsx +54 -0
- package/components/examples/TableExample.tsx +159 -0
- package/components/examples/TextareaExample.tsx +23 -0
- package/components/examples/TileExample.tsx +25 -0
- package/components/examples/Title.tsx +0 -0
- package/components/examples/date/DateTimePickerExample.tsx +53 -0
- package/components/examples/properties/CheckboxPropertyExample.tsx +29 -0
- package/components/examples/properties/DatePropertyExample.tsx +44 -0
- package/components/examples/properties/MultiSelectPropertyExample.tsx +39 -0
- package/components/examples/properties/NumberPropertyExample.tsx +28 -0
- package/components/examples/properties/SelectPropertyExample.tsx +39 -0
- package/components/examples/properties/TextPropertyExample.tsx +30 -0
- package/components/icons/Helpwave.tsx +51 -0
- package/components/icons/Tag.tsx +29 -0
- package/components/layout/Carousel.tsx +396 -0
- package/components/layout/DividerInserter.tsx +37 -0
- package/components/layout/FAQSection.tsx +57 -0
- package/components/layout/Tile.tsx +67 -0
- package/components/modals/ConfirmDialog.tsx +105 -0
- package/components/modals/DiscardChangesDialog.tsx +71 -0
- package/components/modals/InputModal.tsx +26 -0
- package/components/modals/LanguageModal.tsx +76 -0
- package/components/modals/Modal.tsx +149 -0
- package/components/modals/ModalRegister.tsx +45 -0
- package/components/properties/CheckboxProperty.tsx +62 -0
- package/components/properties/DateProperty.tsx +58 -0
- package/components/properties/MultiSelectProperty.tsx +82 -0
- package/components/properties/NumberProperty.tsx +86 -0
- package/components/properties/PropertyBase.tsx +84 -0
- package/components/properties/SelectProperty.tsx +67 -0
- package/components/properties/TextProperty.tsx +81 -0
- package/components/user-input/Checkbox.tsx +139 -0
- package/components/user-input/DateAndTimePicker.tsx +156 -0
- package/components/user-input/Input.tsx +192 -0
- package/components/user-input/Label.tsx +32 -0
- package/components/user-input/Menu.tsx +75 -0
- package/components/user-input/MultiSelect.tsx +158 -0
- package/components/user-input/ScrollPicker.tsx +240 -0
- package/components/user-input/SearchableSelect.tsx +36 -0
- package/components/user-input/Select.tsx +132 -0
- package/components/user-input/Textarea.tsx +86 -0
- package/components/user-input/ToggleableInput.tsx +115 -0
- package/eslint.config.js +3 -0
- package/globals.css +488 -0
- package/hooks/useHoverState.ts +88 -0
- package/hooks/useLanguage.tsx +78 -0
- package/hooks/useLocalStorage.tsx +33 -0
- package/hooks/useOutsideClick.ts +25 -0
- package/hooks/useSaveDelay.ts +46 -0
- package/hooks/useTheme.tsx +57 -0
- package/hooks/useTranslation.ts +43 -0
- package/index.ts +0 -0
- package/package.json +71 -0
- package/postcss.config.mjs +7 -0
- package/stories/README.md +23 -0
- package/stories/coloring/shading.stories.tsx +54 -0
- package/stories/geometry/Circle.stories.tsx +16 -0
- package/stories/geometry/rings/AnimatedRing.stories.tsx +18 -0
- package/stories/geometry/rings/RadialRings.stories.tsx +19 -0
- package/stories/geometry/rings/Ring.stories.tsx +17 -0
- package/stories/geometry/rings/RingWave.stories.tsx +20 -0
- package/stories/layout/FAQSection.stories.tsx +49 -0
- package/stories/layout/InputGroup.stories.tsx +19 -0
- package/stories/layout/Table.stories.tsx +19 -0
- package/stories/layout/TextImage.stories.tsx +24 -0
- package/stories/layout/chip/Chip.stories.tsx +19 -0
- package/stories/layout/chip/ChipList.stories.tsx +27 -0
- package/stories/layout/tile/Tile.stories.ts +20 -0
- package/stories/layout/tile/TileWithImage.stories.tsx +27 -0
- package/stories/other/BreadCrumbs.stories.tsx +21 -0
- package/stories/other/HelpwaveBadge.stories.tsx +18 -0
- package/stories/other/HelpwaveSpinner.stories.tsx +19 -0
- package/stories/other/MarkdownInterpreter.stories.tsx +18 -0
- package/stories/other/Profile.stories.tsx +52 -0
- package/stories/other/SearchableList.stories.tsx +21 -0
- package/stories/other/StackingModals.stories.tsx +16 -0
- package/stories/other/TechRadar.stories.tsx +14 -0
- package/stories/other/Translation.stories.tsx +56 -0
- package/stories/other/VerticalDivider.stories.tsx +20 -0
- package/stories/other/avatar/Avatar.stories.tsx +19 -0
- package/stories/other/avatar/AvatarGroup.stories.tsx +26 -0
- package/stories/other/tooltip/Tooltip.stories.tsx +30 -0
- package/stories/other/tooltip/TooltipStack.stories.tsx +39 -0
- package/stories/user-action/button/LoadingButton.stories.tsx +21 -0
- package/stories/user-action/button/OutlineButton.stories.tsx +22 -0
- package/stories/user-action/button/SolidButton.stories.tsx +22 -0
- package/stories/user-action/button/TextButton.stories.tsx +22 -0
- package/stories/user-action/input/Checkbox.stories.tsx +20 -0
- package/stories/user-action/input/Label.stories.tsx +18 -0
- package/stories/user-action/input/ScrollPicker.stories.tsx +20 -0
- package/stories/user-action/input/Textarea.stories.tsx +22 -0
- package/stories/user-action/input/date/DatePicker.stories.tsx +23 -0
- package/stories/user-action/input/date/DateTimePicker.stories.tsx +26 -0
- package/stories/user-action/input/date/DayPicker.stories.tsx +20 -0
- package/stories/user-action/input/date/TimePicker.stories.tsx +20 -0
- package/stories/user-action/input/date/YearMonthPicker.stories.tsx +21 -0
- package/stories/user-action/input/select/MultiSelect.stories.tsx +39 -0
- package/stories/user-action/input/select/SearchableSelect.stories.tsx +32 -0
- package/stories/user-action/input/select/Select.stories.tsx +30 -0
- package/stories/user-action/properties/CheckboxProperty.stories.tsx +20 -0
- package/stories/user-action/properties/DateProperty.stories.tsx +21 -0
- package/stories/user-action/properties/MultiSelectProperty.stories.tsx +33 -0
- package/stories/user-action/properties/NumberProperty.stories.tsx +21 -0
- package/stories/user-action/properties/PropertyBase.stories.tsx +28 -0
- package/stories/user-action/properties/SingleSelectProperty.stories.tsx +35 -0
- package/stories/user-action/properties/TextProperty.stories.tsx +20 -0
- package/tsconfig.json +20 -0
- package/util/array.ts +115 -0
- package/util/builder.ts +9 -0
- package/util/date.ts +180 -0
- package/util/easeFunctions.ts +37 -0
- package/util/emailValidation.ts +3 -0
- package/util/loopingArray.ts +94 -0
- package/util/math.ts +3 -0
- package/util/news.ts +43 -0
- package/util/noop.ts +1 -0
- package/util/simpleSearch.ts +65 -0
- package/util/storage.ts +37 -0
- package/util/types.ts +4 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import type { PropsWithChildren, ButtonHTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
import clsx from 'clsx'
|
|
3
|
+
|
|
4
|
+
export type SolidButtonColor = 'primary' | 'secondary' | 'tertiary' | 'positive' | 'warning'| 'negative'
|
|
5
|
+
export type OutlineButtonColor = 'primary'
|
|
6
|
+
export type TextButtonColor = 'negative' | 'neutral'
|
|
7
|
+
|
|
8
|
+
type ButtonSizes = 'small' | 'medium' | 'large'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The shard properties between all button types
|
|
12
|
+
*/
|
|
13
|
+
export type ButtonProps = PropsWithChildren<{
|
|
14
|
+
/**
|
|
15
|
+
* @default 'medium'
|
|
16
|
+
*/
|
|
17
|
+
size?: ButtonSizes,
|
|
18
|
+
}> & ButtonHTMLAttributes<Element>
|
|
19
|
+
|
|
20
|
+
export const ButtonSizePaddings: Record<ButtonSizes, string> = {
|
|
21
|
+
small: 'btn-sm',
|
|
22
|
+
medium: 'btn-md',
|
|
23
|
+
large: 'btn-lg'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type ButtonWithIconsProps = ButtonProps & {
|
|
27
|
+
startIcon?: ReactNode,
|
|
28
|
+
endIcon?: ReactNode,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type SolidButtonProps = ButtonWithIconsProps & {
|
|
32
|
+
color?: SolidButtonColor,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type OutlineButtonProps = ButtonWithIconsProps & {
|
|
36
|
+
color?: OutlineButtonColor,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type TextButtonProps = ButtonWithIconsProps & {
|
|
40
|
+
color?: TextButtonColor,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A button with a solid background and different sizes
|
|
45
|
+
*/
|
|
46
|
+
const SolidButton = ({
|
|
47
|
+
children,
|
|
48
|
+
disabled = false,
|
|
49
|
+
color = 'primary',
|
|
50
|
+
size = 'medium',
|
|
51
|
+
startIcon,
|
|
52
|
+
endIcon,
|
|
53
|
+
onClick,
|
|
54
|
+
className,
|
|
55
|
+
...restProps
|
|
56
|
+
}: SolidButtonProps) => {
|
|
57
|
+
const colorClasses = {
|
|
58
|
+
primary: 'bg-button-solid-primary-background text-button-solid-primary-text',
|
|
59
|
+
secondary: 'bg-button-solid-secondary-background text-button-solid-secondary-text',
|
|
60
|
+
tertiary: 'bg-button-solid-tertiary-background text-button-solid-tertiary-text',
|
|
61
|
+
positive: 'bg-button-solid-positive-background text-button-solid-positive-text',
|
|
62
|
+
warning: 'bg-button-solid-warning-background text-button-solid-warning-text',
|
|
63
|
+
negative: 'bg-button-solid-negative-background text-button-solid-negative-text',
|
|
64
|
+
}[color]
|
|
65
|
+
|
|
66
|
+
const iconColorClasses = {
|
|
67
|
+
primary: 'text-button-solid-primary-icon',
|
|
68
|
+
secondary: 'text-button-solid-secondary-icon',
|
|
69
|
+
tertiary: 'text-button-solid-tertiary-icon',
|
|
70
|
+
positive: 'text-button-solid-positive-icon',
|
|
71
|
+
warning: 'text-button-solid-warning-icon',
|
|
72
|
+
negative: 'text-button-solid-negative-icon',
|
|
73
|
+
}[color]
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<button
|
|
77
|
+
onClick={disabled ? undefined : onClick}
|
|
78
|
+
disabled={disabled || onClick === undefined}
|
|
79
|
+
className={clsx(
|
|
80
|
+
className,
|
|
81
|
+
{
|
|
82
|
+
'text-disabled-text bg-disabled-background': disabled,
|
|
83
|
+
[clsx(colorClasses, 'hover:brightness-90')]: !disabled
|
|
84
|
+
},
|
|
85
|
+
ButtonSizePaddings[size]
|
|
86
|
+
)}
|
|
87
|
+
{...restProps}
|
|
88
|
+
>
|
|
89
|
+
{startIcon && (
|
|
90
|
+
<span
|
|
91
|
+
className={clsx({
|
|
92
|
+
[iconColorClasses]: !disabled,
|
|
93
|
+
[`text-disabled-icon`]: disabled
|
|
94
|
+
})}
|
|
95
|
+
>
|
|
96
|
+
{startIcon}
|
|
97
|
+
</span>
|
|
98
|
+
)}
|
|
99
|
+
{children}
|
|
100
|
+
{endIcon && (
|
|
101
|
+
<span
|
|
102
|
+
className={clsx({
|
|
103
|
+
[iconColorClasses]: !disabled,
|
|
104
|
+
[`text-disabled-icon`]: disabled
|
|
105
|
+
})}
|
|
106
|
+
>
|
|
107
|
+
{endIcon}
|
|
108
|
+
</span>
|
|
109
|
+
)}
|
|
110
|
+
</button>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A button with an outline border and different sizes
|
|
116
|
+
*/
|
|
117
|
+
const OutlineButton = ({
|
|
118
|
+
children,
|
|
119
|
+
disabled = false,
|
|
120
|
+
color = 'primary',
|
|
121
|
+
size = 'medium',
|
|
122
|
+
startIcon,
|
|
123
|
+
endIcon,
|
|
124
|
+
onClick,
|
|
125
|
+
className,
|
|
126
|
+
...restProps
|
|
127
|
+
}: OutlineButtonProps) => {
|
|
128
|
+
const colorClasses = {
|
|
129
|
+
primary: 'bg-transparent border-2 border-button-outline-primary-text text-button-outline-primary-text',
|
|
130
|
+
}[color]
|
|
131
|
+
|
|
132
|
+
const iconColorClasses = {
|
|
133
|
+
primary: 'text-button-outline-primary-icon',
|
|
134
|
+
}[color]
|
|
135
|
+
return (
|
|
136
|
+
<button
|
|
137
|
+
onClick={disabled ? undefined : onClick}
|
|
138
|
+
disabled={disabled || onClick === undefined}
|
|
139
|
+
className={clsx(
|
|
140
|
+
className, {
|
|
141
|
+
'text-disabled-text border-disabled-outline)': disabled,
|
|
142
|
+
[clsx(colorClasses, 'hover:brightness-80')]: !disabled,
|
|
143
|
+
},
|
|
144
|
+
ButtonSizePaddings[size]
|
|
145
|
+
)}
|
|
146
|
+
{...restProps}
|
|
147
|
+
>
|
|
148
|
+
{startIcon && (
|
|
149
|
+
<span
|
|
150
|
+
className={clsx({
|
|
151
|
+
[iconColorClasses]: !disabled,
|
|
152
|
+
[`text-disabled-icon`]: disabled
|
|
153
|
+
})}
|
|
154
|
+
>
|
|
155
|
+
{startIcon}
|
|
156
|
+
</span>
|
|
157
|
+
)}
|
|
158
|
+
{children}
|
|
159
|
+
{endIcon && (
|
|
160
|
+
<span
|
|
161
|
+
className={clsx({
|
|
162
|
+
[iconColorClasses]: !disabled,
|
|
163
|
+
[`text-disabled-icon`]: disabled
|
|
164
|
+
})}
|
|
165
|
+
>
|
|
166
|
+
{endIcon}
|
|
167
|
+
</span>
|
|
168
|
+
)}
|
|
169
|
+
</button>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* A text that is a button that can have different sizes
|
|
175
|
+
*/
|
|
176
|
+
const TextButton = ({
|
|
177
|
+
children,
|
|
178
|
+
disabled = false,
|
|
179
|
+
color = 'neutral',
|
|
180
|
+
size = 'medium',
|
|
181
|
+
startIcon,
|
|
182
|
+
endIcon,
|
|
183
|
+
onClick,
|
|
184
|
+
className,
|
|
185
|
+
...restProps
|
|
186
|
+
}: TextButtonProps) => {
|
|
187
|
+
const colorClasses = {
|
|
188
|
+
negative: 'bg-transparent text-button-text-negative-text',
|
|
189
|
+
neutral: 'bg-transparent text-button-text-neutral-text',
|
|
190
|
+
}[color]
|
|
191
|
+
|
|
192
|
+
const iconColorClasses = {
|
|
193
|
+
negative: 'text-button-text-negative-icon',
|
|
194
|
+
neutral: 'text-button-text-neutral-icon',
|
|
195
|
+
}[color]
|
|
196
|
+
return (
|
|
197
|
+
<button
|
|
198
|
+
onClick={disabled ? undefined : onClick}
|
|
199
|
+
disabled={disabled || onClick === undefined}
|
|
200
|
+
className={clsx(
|
|
201
|
+
className, {
|
|
202
|
+
'text-disabled-text': disabled,
|
|
203
|
+
[clsx(colorClasses, 'hover:bg-button-text-hover-background rounded-full')]: !disabled,
|
|
204
|
+
},
|
|
205
|
+
ButtonSizePaddings[size]
|
|
206
|
+
)}
|
|
207
|
+
{...restProps}
|
|
208
|
+
>
|
|
209
|
+
{startIcon && (
|
|
210
|
+
<span
|
|
211
|
+
className={clsx({
|
|
212
|
+
[iconColorClasses]: !disabled,
|
|
213
|
+
[`text-disabled-icon`]: disabled
|
|
214
|
+
})}
|
|
215
|
+
>
|
|
216
|
+
{startIcon}
|
|
217
|
+
</span>
|
|
218
|
+
)}
|
|
219
|
+
{children}
|
|
220
|
+
{endIcon && (
|
|
221
|
+
<span
|
|
222
|
+
className={clsx({
|
|
223
|
+
[iconColorClasses]: !disabled,
|
|
224
|
+
[`text-disabled-icon`]: disabled
|
|
225
|
+
})}
|
|
226
|
+
>
|
|
227
|
+
{endIcon}
|
|
228
|
+
</span>
|
|
229
|
+
)}
|
|
230
|
+
</button>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// TODO Icon button
|
|
235
|
+
|
|
236
|
+
export { SolidButton, OutlineButton, TextButton }
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { HTMLProps, PropsWithChildren, ReactNode } from 'react'
|
|
2
|
+
import clsx from 'clsx'
|
|
3
|
+
|
|
4
|
+
export type ChipColor = 'default' | 'dark'| 'red' | 'yellow' | 'green' | 'blue' | 'pink'
|
|
5
|
+
type ChipVariant = 'normal' | 'fullyRounded'
|
|
6
|
+
|
|
7
|
+
export type ChipProps = HTMLProps<HTMLDivElement> & PropsWithChildren<{
|
|
8
|
+
color?: ChipColor,
|
|
9
|
+
variant?: ChipVariant,
|
|
10
|
+
trailingIcon?: ReactNode,
|
|
11
|
+
}>
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A component for displaying a single chip
|
|
15
|
+
*/
|
|
16
|
+
export const Chip = ({
|
|
17
|
+
children,
|
|
18
|
+
trailingIcon,
|
|
19
|
+
color = 'default',
|
|
20
|
+
variant = 'normal',
|
|
21
|
+
className = '',
|
|
22
|
+
...restProps
|
|
23
|
+
}: ChipProps) => {
|
|
24
|
+
const colorMapping: string = {
|
|
25
|
+
default: 'text-tag-default-text bg-tag-default-background',
|
|
26
|
+
dark: 'text-tag-dark-text bg-tag-dark-background',
|
|
27
|
+
red: 'text-tag-red-text bg-tag-red-background',
|
|
28
|
+
yellow: 'text-tag-yellow-text bg-tag-yellow-background',
|
|
29
|
+
green: 'text-tag-green-text bg-tag-green-background',
|
|
30
|
+
blue: 'text-tag-blue-text bg-tag-blue-background',
|
|
31
|
+
pink: 'text-tag-pink-text bg-tag-pink-background',
|
|
32
|
+
}[color]
|
|
33
|
+
|
|
34
|
+
const colorMappingIcon: string = {
|
|
35
|
+
default: 'text-tag-default-icon',
|
|
36
|
+
dark: 'text-tag-dark-icon',
|
|
37
|
+
red: 'text-tag-red-icon',
|
|
38
|
+
yellow: 'text-tag-yellow-icon',
|
|
39
|
+
green: 'text-tag-green-icon',
|
|
40
|
+
blue: 'text-tag-blue-icon',
|
|
41
|
+
pink: 'text-tag-pink-icon',
|
|
42
|
+
}[color]
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div
|
|
46
|
+
{...restProps}
|
|
47
|
+
className={clsx(
|
|
48
|
+
`row w-fit px-2 py-1`,
|
|
49
|
+
colorMapping,
|
|
50
|
+
{
|
|
51
|
+
'rounded-md': variant === 'normal',
|
|
52
|
+
'rounded-full': variant === 'fullyRounded',
|
|
53
|
+
},
|
|
54
|
+
className
|
|
55
|
+
)}
|
|
56
|
+
>
|
|
57
|
+
{children}
|
|
58
|
+
{trailingIcon && (<span className={colorMappingIcon}>{trailingIcon}</span>)}
|
|
59
|
+
</div>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type ChipListProps = {
|
|
64
|
+
list: ChipProps[],
|
|
65
|
+
className?: string,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A component for displaying a list of chips
|
|
70
|
+
*/
|
|
71
|
+
export const ChipList = ({
|
|
72
|
+
list,
|
|
73
|
+
className = ''
|
|
74
|
+
}: ChipListProps) => {
|
|
75
|
+
return (
|
|
76
|
+
<div className={clsx('flex flex-wrap gap-x-4 gap-y-2', className)}>
|
|
77
|
+
{list.map((value, index) => (
|
|
78
|
+
<Chip
|
|
79
|
+
key={index}
|
|
80
|
+
{...value}
|
|
81
|
+
color={value.color ?? 'dark'}
|
|
82
|
+
variant={value.variant ?? 'normal'}
|
|
83
|
+
>
|
|
84
|
+
{value.children}
|
|
85
|
+
</Chip>
|
|
86
|
+
))}
|
|
87
|
+
</div>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react'
|
|
2
|
+
import clsx from 'clsx'
|
|
3
|
+
|
|
4
|
+
export type CircleProps = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'color'> & {
|
|
5
|
+
radius: number,
|
|
6
|
+
className?: string,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const Circle = ({
|
|
10
|
+
radius = 20,
|
|
11
|
+
className = 'bg-primary',
|
|
12
|
+
style,
|
|
13
|
+
...restProps
|
|
14
|
+
}: CircleProps) => {
|
|
15
|
+
const size = radius * 2
|
|
16
|
+
return (
|
|
17
|
+
<div
|
|
18
|
+
className={clsx(`rounded-full`, className)}
|
|
19
|
+
style={{
|
|
20
|
+
width: `${size}px`,
|
|
21
|
+
height: `${size}px`,
|
|
22
|
+
...style,
|
|
23
|
+
}}
|
|
24
|
+
{...restProps}
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { AlertOctagon } from 'lucide-react'
|
|
2
|
+
import type { Languages } from '../hooks/useLanguage'
|
|
3
|
+
import type { PropsForTranslation } from '../hooks/useTranslation'
|
|
4
|
+
import { useTranslation } from '../hooks/useTranslation'
|
|
5
|
+
import clsx from 'clsx'
|
|
6
|
+
|
|
7
|
+
type ErrorComponentTranslation = {
|
|
8
|
+
errorOccurred: string,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const defaultErrorComponentTranslation: Record<Languages, ErrorComponentTranslation> = {
|
|
12
|
+
en: {
|
|
13
|
+
errorOccurred: 'An error occurred'
|
|
14
|
+
},
|
|
15
|
+
de: {
|
|
16
|
+
errorOccurred: 'Ein Fehler ist aufgetreten'
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ErrorComponentProps = {
|
|
21
|
+
errorText?: string,
|
|
22
|
+
classname?: string,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The Component to show when an error occurred
|
|
27
|
+
*/
|
|
28
|
+
export const ErrorComponent = ({
|
|
29
|
+
overwriteTranslation,
|
|
30
|
+
errorText,
|
|
31
|
+
classname
|
|
32
|
+
}: PropsForTranslation<ErrorComponentTranslation, ErrorComponentProps>) => {
|
|
33
|
+
const translation = useTranslation(defaultErrorComponentTranslation, overwriteTranslation)
|
|
34
|
+
return (
|
|
35
|
+
<div className={clsx('col items-center justify-center gap-y-4 w-full h-24', classname)}>
|
|
36
|
+
<AlertOctagon size={64} className="text-warning"/>
|
|
37
|
+
{errorText ?? `${translation.errorOccurred} :(`}
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { PropsWithChildren, ReactNode } from 'react'
|
|
2
|
+
import { forwardRef, useState } from 'react'
|
|
3
|
+
import { ChevronDown, ChevronUp } from 'lucide-react'
|
|
4
|
+
import clsx from 'clsx'
|
|
5
|
+
|
|
6
|
+
type IconBuilder = (expanded: boolean) => ReactNode
|
|
7
|
+
|
|
8
|
+
export type ExpandableProps = PropsWithChildren<{
|
|
9
|
+
label: ReactNode,
|
|
10
|
+
icon?: IconBuilder,
|
|
11
|
+
initialExpansion?: boolean,
|
|
12
|
+
/**
|
|
13
|
+
* Whether the expansion should only happen when the header is clicked or on the entire component
|
|
14
|
+
*/
|
|
15
|
+
clickOnlyOnHeader?: boolean,
|
|
16
|
+
className?: string,
|
|
17
|
+
headerClassName?: string,
|
|
18
|
+
}>
|
|
19
|
+
|
|
20
|
+
const DefaultIcon: IconBuilder = (expanded) => expanded ?
|
|
21
|
+
(<ChevronUp size={16} className="min-w-[16px]"/>)
|
|
22
|
+
: (<ChevronDown size={16} className="min-w-[16px]"/>)
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A Component for showing and hiding content
|
|
26
|
+
*/
|
|
27
|
+
export const Expandable = forwardRef<HTMLDivElement, ExpandableProps>(({
|
|
28
|
+
children,
|
|
29
|
+
label,
|
|
30
|
+
icon,
|
|
31
|
+
initialExpansion = false,
|
|
32
|
+
clickOnlyOnHeader = true,
|
|
33
|
+
className = '',
|
|
34
|
+
headerClassName = ''
|
|
35
|
+
}, ref) => {
|
|
36
|
+
const [isExpanded, setIsExpanded] = useState(initialExpansion)
|
|
37
|
+
icon ??= DefaultIcon
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div
|
|
41
|
+
ref={ref}
|
|
42
|
+
className={clsx('col bg-surface text-on-surface group rounded-lg shadow-sm', { 'cursor-pointer': !clickOnlyOnHeader }, className)}
|
|
43
|
+
onClick={() => !clickOnlyOnHeader && setIsExpanded(!isExpanded)}
|
|
44
|
+
>
|
|
45
|
+
<button
|
|
46
|
+
className={clsx('btn-md rounded-lg justify-between items-center bg-surface text-on-surface', { 'group-hover:brightness-95': !isExpanded }, headerClassName)}
|
|
47
|
+
onClick={() => clickOnlyOnHeader && setIsExpanded(!isExpanded)}
|
|
48
|
+
>
|
|
49
|
+
{label}
|
|
50
|
+
{icon(isExpanded)}
|
|
51
|
+
</button>
|
|
52
|
+
{isExpanded && (
|
|
53
|
+
<div className="col">
|
|
54
|
+
{children}
|
|
55
|
+
</div>
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
Expandable.displayName = 'Expandable'
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
import { Helpwave } from './icons/Helpwave'
|
|
3
|
+
import { Tile } from './layout/Tile'
|
|
4
|
+
|
|
5
|
+
type Size = 'small' | 'large'
|
|
6
|
+
|
|
7
|
+
export type HelpwaveBadgeProps = {
|
|
8
|
+
size?: Size,
|
|
9
|
+
title?: string,
|
|
10
|
+
className?: string,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A Badge with the helpwave logo and the helpwave name
|
|
15
|
+
*/
|
|
16
|
+
export const HelpwaveBadge = ({
|
|
17
|
+
size = 'small',
|
|
18
|
+
title = 'helpwave',
|
|
19
|
+
className = ''
|
|
20
|
+
}: HelpwaveBadgeProps) => {
|
|
21
|
+
const iconSize: number = size === 'small' ? 24 : 64
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Tile
|
|
25
|
+
prefix={(<Helpwave size={iconSize} />)}
|
|
26
|
+
title={{ value: title, className: size === 'small' ? 'textstyle-title-lg text-base' : 'textstyle-title-xl' }}
|
|
27
|
+
className={clsx(
|
|
28
|
+
{
|
|
29
|
+
'px-2 py-1 rounded-md': size === 'small',
|
|
30
|
+
'px-4 py-1 rounded-md': size === 'large',
|
|
31
|
+
}, className
|
|
32
|
+
)}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useState, type PropsWithChildren, type ReactNode } from 'react'
|
|
2
|
+
import { ChevronDown, ChevronUp } from 'lucide-react'
|
|
3
|
+
import clsx from 'clsx'
|
|
4
|
+
|
|
5
|
+
export type HideableContentSectionProps = PropsWithChildren & {
|
|
6
|
+
initiallyOpen?: boolean,
|
|
7
|
+
disabled: boolean,
|
|
8
|
+
header: ReactNode,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A Component to hide and show
|
|
13
|
+
*/
|
|
14
|
+
export const HideableContentSection = ({
|
|
15
|
+
initiallyOpen = true,
|
|
16
|
+
disabled,
|
|
17
|
+
children,
|
|
18
|
+
header
|
|
19
|
+
}: HideableContentSectionProps) => {
|
|
20
|
+
const [open, setOpen] = useState(initiallyOpen)
|
|
21
|
+
return (
|
|
22
|
+
<div className="col">
|
|
23
|
+
<div
|
|
24
|
+
className={clsx('row justify-between items-center', { 'cursor-pointer': !disabled })}
|
|
25
|
+
onClick={() => {
|
|
26
|
+
if (!disabled) {
|
|
27
|
+
setOpen(!open)
|
|
28
|
+
}
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
<div>
|
|
32
|
+
{header}
|
|
33
|
+
</div>
|
|
34
|
+
{!disabled && (open ? <ChevronUp/> : <ChevronDown/>)}
|
|
35
|
+
</div>
|
|
36
|
+
{open && (
|
|
37
|
+
<div>
|
|
38
|
+
{children}
|
|
39
|
+
</div>
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { PropsWithChildren } from 'react'
|
|
2
|
+
import { useEffect, useState } from 'react'
|
|
3
|
+
import { ChevronDown, ChevronUp } from 'lucide-react'
|
|
4
|
+
import clsx from 'clsx'
|
|
5
|
+
import { noop } from '../util/noop'
|
|
6
|
+
|
|
7
|
+
export type InputGroupProps = PropsWithChildren<{
|
|
8
|
+
title: string,
|
|
9
|
+
expanded?: boolean,
|
|
10
|
+
isExpandable?: boolean,
|
|
11
|
+
disabled?: boolean,
|
|
12
|
+
onChange?: (value: boolean) => void,
|
|
13
|
+
className?: string,
|
|
14
|
+
}>
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A Component for layouting inputs in an expandable group
|
|
18
|
+
*/
|
|
19
|
+
export const InputGroup = ({
|
|
20
|
+
children,
|
|
21
|
+
title,
|
|
22
|
+
expanded = true,
|
|
23
|
+
isExpandable = true,
|
|
24
|
+
disabled = false,
|
|
25
|
+
onChange = noop,
|
|
26
|
+
className = '',
|
|
27
|
+
}: InputGroupProps) => {
|
|
28
|
+
const [isExpanded, setIsExpanded] = useState<boolean>(expanded)
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
setIsExpanded(expanded)
|
|
32
|
+
}, [expanded])
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className={clsx('col gap-y-4 p-4 bg-white rounded-xl', className)}>
|
|
36
|
+
<div
|
|
37
|
+
className={clsx('row justify-between items-center', {
|
|
38
|
+
'cursor-pointer': isExpandable && !disabled,
|
|
39
|
+
'cursor-not-allowed': disabled,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
'text-primary': !disabled,
|
|
43
|
+
'text-primary/40': disabled
|
|
44
|
+
})}
|
|
45
|
+
onClick={() => {
|
|
46
|
+
if (!isExpandable) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
const updatedIsExpanded = !isExpanded
|
|
50
|
+
onChange(updatedIsExpanded)
|
|
51
|
+
setIsExpanded(updatedIsExpanded)
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
<span className="textstyle-title-md">{title}</span>
|
|
55
|
+
<div className={clsx('rounded-full text-white w-6 h-6', {
|
|
56
|
+
'bg-primary': (isExpandable && !disabled) || expanded,
|
|
57
|
+
'bg-primary/40': disabled,
|
|
58
|
+
})}>
|
|
59
|
+
{isExpanded
|
|
60
|
+
? <ChevronUp className="-translate-y-[1px]" size={24}/>
|
|
61
|
+
: <ChevronDown className="translate-y-[1px]" size={24}/>
|
|
62
|
+
}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
{isExpanded && (
|
|
66
|
+
<div className="col gap-y-2 h-full">
|
|
67
|
+
{children}
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { PropsWithChildren } from 'react'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
import type { LoadingAnimationProps } from './LoadingAnimation'
|
|
4
|
+
import type { ErrorComponentProps } from './ErrorComponent'
|
|
5
|
+
import { LoadingAnimation } from './LoadingAnimation'
|
|
6
|
+
import { ErrorComponent } from './ErrorComponent'
|
|
7
|
+
|
|
8
|
+
export type LoadingAndErrorComponentProps = PropsWithChildren<{
|
|
9
|
+
isLoading?: boolean,
|
|
10
|
+
hasError?: boolean,
|
|
11
|
+
loadingProps?: LoadingAnimationProps,
|
|
12
|
+
errorProps?: ErrorComponentProps,
|
|
13
|
+
/**
|
|
14
|
+
* in milliseconds
|
|
15
|
+
*/
|
|
16
|
+
minimumLoadingDuration?: number,
|
|
17
|
+
}>
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A Component that shows the Error and Loading animation, when appropriate and the children otherwise
|
|
21
|
+
*/
|
|
22
|
+
export const LoadingAndErrorComponent = ({
|
|
23
|
+
children,
|
|
24
|
+
isLoading = false,
|
|
25
|
+
hasError = false,
|
|
26
|
+
errorProps,
|
|
27
|
+
loadingProps,
|
|
28
|
+
minimumLoadingDuration
|
|
29
|
+
}: LoadingAndErrorComponentProps) => {
|
|
30
|
+
const [isInMinimumLoading, setIsInMinimumLoading] = useState(false)
|
|
31
|
+
const [hasUsedMinimumLoading, setHasUsedMinimumLoading] = useState(false)
|
|
32
|
+
if (minimumLoadingDuration && !isInMinimumLoading && !hasUsedMinimumLoading) {
|
|
33
|
+
setIsInMinimumLoading(true)
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
setIsInMinimumLoading(false)
|
|
36
|
+
setHasUsedMinimumLoading(true)
|
|
37
|
+
}, minimumLoadingDuration)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (isLoading || (minimumLoadingDuration && isInMinimumLoading)) {
|
|
41
|
+
return <LoadingAnimation {...loadingProps}/>
|
|
42
|
+
}
|
|
43
|
+
if (hasError) {
|
|
44
|
+
return <ErrorComponent {...errorProps}/>
|
|
45
|
+
}
|
|
46
|
+
return children
|
|
47
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Languages } from '../hooks/useLanguage'
|
|
2
|
+
import type { PropsForTranslation } from '../hooks/useTranslation'
|
|
3
|
+
import { useTranslation } from '../hooks/useTranslation'
|
|
4
|
+
import { Helpwave } from './icons/Helpwave'
|
|
5
|
+
import clsx from 'clsx'
|
|
6
|
+
|
|
7
|
+
type LoadingAnimationTranslation = {
|
|
8
|
+
loading: string,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const defaultLoadingAnimationTranslation: Record<Languages, LoadingAnimationTranslation> = {
|
|
12
|
+
en: {
|
|
13
|
+
loading: 'Loading data'
|
|
14
|
+
},
|
|
15
|
+
de: {
|
|
16
|
+
loading: 'Lade Daten'
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type LoadingAnimationProps = {
|
|
21
|
+
loadingText?: string,
|
|
22
|
+
classname?: string,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A Component to show when loading data
|
|
27
|
+
*/
|
|
28
|
+
export const LoadingAnimation = ({
|
|
29
|
+
overwriteTranslation,
|
|
30
|
+
loadingText,
|
|
31
|
+
classname
|
|
32
|
+
}: PropsForTranslation<LoadingAnimationTranslation, LoadingAnimationProps>) => {
|
|
33
|
+
const translation = useTranslation(defaultLoadingAnimationTranslation, overwriteTranslation)
|
|
34
|
+
return (
|
|
35
|
+
<div className={clsx('col items-center justify-center w-full h-24', classname)}>
|
|
36
|
+
<Helpwave animate="loading" />
|
|
37
|
+
{loadingText ?? `${translation.loading}...`}
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|