@takaro/lib-components 0.0.10 → 0.0.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takaro/lib-components",
3
- "version": "0.0.10",
3
+ "version": "0.0.13",
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",
@@ -15,58 +15,58 @@
15
15
  "test:update": "jest --updateSnapshot"
16
16
  },
17
17
  "devDependencies": {
18
- "@hookform/devtools": "^4.2.2",
19
- "@mdx-js/react": "^2.1.5",
20
- "@types/jest": "^29.0.0",
21
- "@types/react-window": "^1.8.5",
22
- "@types/styled-components": "^5.1.25",
23
- "@types/topojson-client": "^3.1.4",
24
- "react": "^18.2.0",
25
- "react-dom": "^18.2.0",
18
+ "@hookform/devtools": "4.3.1",
19
+ "@mdx-js/react": "3.0.1",
20
+ "@types/jest": "29.5.13",
21
+ "@types/react-window": "1.8.8",
22
+ "@types/styled-components": "5.1.34",
23
+ "@types/topojson-client": "3.1.5",
24
+ "react": "18.3.1",
25
+ "react-dom": "18.3.1",
26
26
  "storybook": "7.6.20"
27
27
  },
28
28
  "peerDependencies": {
29
- "@floating-ui/react": "^0.24.0 || ^0.26.0",
30
- "@monaco-editor/react": "^4.4.6",
31
- "@rjsf/core": "^5.6.2",
32
- "@rjsf/utils": "^5.6.2",
33
- "@rjsf/validator-ajv8": "^5.6.2",
34
- "@tanstack/react-table": "^8.7.9",
35
- "@tanstack/react-router": "1.35.3",
29
+ "@floating-ui/react": "0.26.24",
30
+ "@monaco-editor/react": "4.6.0",
31
+ "@rjsf/core": "5.20.0",
32
+ "@rjsf/utils": "5.20.0",
33
+ "@rjsf/validator-ajv8": "5.20.0",
34
+ "@tanstack/react-table": "8.20.5",
35
+ "@tanstack/react-router": "1.58.15",
36
36
  "@types/luxon": "3.4.2",
37
- "framer-motion": "^10.16.12 || ^11.0.0",
37
+ "framer-motion": "11.9.0",
38
38
  "luxon": "3.5.0",
39
- "notistack": "^3.0.1",
40
- "polished": "^4.1.3",
41
- "react": "^18.2.0",
42
- "react-dnd": "^16.0.1",
43
- "@sentry/react": "^8.5.0",
44
- "react-dnd-html5-backend": "^16.0.1",
45
- "react-dom": "^18.2.0",
46
- "react-hook-form": "^7.50.1",
47
- "react-icons": "^4.11.0 || ^5.0.0",
48
- "react-intersection-observer": "^9.5.1",
49
- "react-virtualized-auto-sizer": "^1.0.9",
50
- "react-window": "^1.8.8",
51
- "simplebar-react": "^3.2.4",
52
- "styled-components": "^5.3.5",
53
- "web-vitals": "^2.1.2 || ^4.0.0",
54
- "@visx/scale": "^3.5.0",
55
- "@visx/responsive": "^3.3.0",
56
- "@visx/group": "^3.3.0",
57
- "@visx/heatmap": "^3.3.0",
58
- "@visx/tooltip": "^3.3.0",
59
- "@visx/event": "^3.3.0",
60
- "@visx/shape": "^3.5.0",
61
- "@visx/grid": "^3.5.0",
62
- "@visx/vendor": "^3.5.0",
63
- "@visx/brush": "^3.6.1",
64
- "@visx/axis": "^3.5.0",
39
+ "notistack": "3.0.1",
40
+ "polished": "4.3.1",
41
+ "react": "18.3.1",
42
+ "react-dnd": "16.0.1",
43
+ "@sentry/react": "8.32.0",
44
+ "react-dnd-html5-backend": "16.0.1",
45
+ "react-dom": "18.3.1",
46
+ "react-hook-form": "7.53.0",
47
+ "react-icons": "5.3.0",
48
+ "react-intersection-observer": "9.13.1",
49
+ "react-virtualized-auto-sizer": "1.0.24",
50
+ "react-window": "1.8.10",
51
+ "simplebar-react": "3.2.6",
52
+ "styled-components": "5.3.11",
53
+ "web-vitals": "4.2.3",
54
+ "@visx/scale": "3.5.0",
55
+ "@visx/responsive": "3.10.2",
56
+ "@visx/group": "3.3.0",
57
+ "@visx/heatmap": "3.3.0",
58
+ "@visx/tooltip": "3.3.0",
59
+ "@visx/event": "3.3.0",
60
+ "@visx/shape": "3.5.0",
61
+ "@visx/grid": "3.5.0",
62
+ "@visx/vendor": "3.5.0",
63
+ "@visx/brush": "3.10.4",
64
+ "@visx/axis": "3.10.1",
65
65
  "@visx/pattern": "3.3.0",
66
- "@visx/curve": "^3.3.0",
67
- "@visx/gradient": "^3.3.0",
68
- "@visx/mock-data": "^3.3.0",
66
+ "@visx/curve": "3.3.0",
67
+ "@visx/gradient": "3.3.0",
68
+ "@visx/mock-data": "3.3.0",
69
69
  "@visx/geo": "3.5.0",
70
- "topojson-client": "^3.1.0"
70
+ "topojson-client": "3.1.0"
71
71
  }
72
72
  }
@@ -1,5 +1,6 @@
1
1
  import { cloneElement, forwardRef, ReactElement } from 'react';
2
2
  import { Color, Size } from '../../../styled/types';
3
+ import { Badge } from '../../../components';
3
4
  import { Default } from './style';
4
5
 
5
6
  export interface IconButtonProps {
@@ -35,7 +36,7 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(functio
35
36
  return (
36
37
  <Default type="button" color={color} onClick={onClick} ref={ref} disabled={disabled} aria-label={ariaLabel}>
37
38
  {cloneElement(icon, { size: getSize(size) })}
38
- {badge && <div>{badge}</div>}
39
+ {badge && <Badge>{badge}</Badge>}
39
40
  </Default>
40
41
  );
41
42
  });
@@ -51,25 +51,6 @@ export const Default = styled.button<{ color: Color }>`
51
51
  }
52
52
  }
53
53
 
54
- div {
55
- background-color: ${({ theme }) => theme.colors.background};
56
- color: ${({ theme }) => theme.colors.text};
57
- font-size: ${({ theme }) => theme.fontSize.tiny};
58
- font-weight: 600;
59
- border-radius: ${({ theme }) => theme.borderRadius.small};
60
- width: fit-content;
61
- height: 1.5rem;
62
- line-height: 1.1rem;
63
- display: flex;
64
- align-items: center;
65
- justify-content: center;
66
- position: absolute;
67
- top: -${({ theme }) => theme.spacing['0_75']};
68
- right: -${({ theme }) => theme.spacing['0_75']};
69
- padding: ${({ theme }) => theme.spacing['0_25']};
70
- border: 1px solid ${({ theme }) => theme.colors.backgroundAccent};
71
- }
72
-
73
54
  svg {
74
55
  cursor: pointer;
75
56
  fill: ${({ theme }) => theme.colors.text};
@@ -38,7 +38,7 @@ export interface TableProps<DataType extends object> {
38
38
  isLoading?: boolean;
39
39
 
40
40
  // currently not possible to type this properly: https://github.com/TanStack/table/issues/4241
41
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
+
42
42
  columns: ColumnDef<DataType, any>[];
43
43
 
44
44
  /// Renders actions that are always visible
@@ -93,7 +93,19 @@ export function Table<DataType extends object>({
93
93
  if (column.id === undefined) {
94
94
  throw new Error('ColumnDef must have an id');
95
95
  }
96
- acc[column.id] = column?.meta?.hiddenColumn ? !column.meta.hiddenColumn : true;
96
+
97
+ if (column?.meta?.includeColumn == false) {
98
+ acc[column.id] = false;
99
+ return acc;
100
+ } else {
101
+ acc[column.id] = true;
102
+ }
103
+
104
+ if (column?.meta?.hideColumn) {
105
+ acc[column.id] = !column.meta.hideColumn;
106
+ } else {
107
+ acc[column.id] = true;
108
+ }
97
109
  return acc;
98
110
  },
99
111
  {} as Record<string, boolean>,
@@ -5,7 +5,10 @@ type DataTypes = 'datetime' | 'number' | 'string' | 'boolean' | 'uuid';
5
5
 
6
6
  declare module '@tanstack/table-core' {
7
7
  interface ColumnMeta<TData extends RowData, TValue> {
8
- hiddenColumn?: boolean;
8
+ /// Column is not visible in table by default, but can be enabled with view options
9
+ hideColumn?: boolean;
10
+ canShowColumn?: boolean;
11
+ includeColumn?: boolean;
9
12
  dataType?: DataTypes;
10
13
  }
11
14
  }
@@ -72,7 +72,7 @@ export function ColumnHeader<DataType extends object>({ header, table, isLoading
72
72
  const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2;
73
73
 
74
74
  // Determine mouse position
75
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
75
+
76
76
  const clientOffset = monitor.getClientOffset()!;
77
77
 
78
78
  // Get pixels to the top
@@ -18,7 +18,16 @@ export function ColumnVisibility<DataType extends object>({
18
18
  openColumnVisibilityTooltip,
19
19
  setHasShownColumnVisibilityTooltip,
20
20
  }: ColumnVisibilityProps<DataType>) {
21
- const viableColumns = table.getAllLeafColumns().filter((column) => column.getCanHide() === true);
21
+ const viableColumns = table
22
+ .getAllLeafColumns()
23
+ .filter((column) => column.getCanHide() === true)
24
+ .filter((column) => {
25
+ if (column.columnDef.meta?.includeColumn === false) {
26
+ return false;
27
+ }
28
+ return true;
29
+ });
30
+
22
31
  const hiddenColumns = viableColumns.filter((column) => column.getIsVisible() === false);
23
32
 
24
33
  return (
@@ -1,4 +1,4 @@
1
- import { forwardRef, MouseEvent, useState } from 'react';
1
+ import { forwardRef, isValidElement, MouseEvent, ReactElement, useState } from 'react';
2
2
  import { Container, Grid, IconContainer, ButtonContainer } from './style';
3
3
  import {
4
4
  AiFillCheckCircle as Success,
@@ -20,14 +20,15 @@ type Action = {
20
20
  export interface AlertProps {
21
21
  variant: AlertVariants;
22
22
  title?: string;
23
- text?: string | string[];
23
+ text?: string | string[] | ReactElement;
24
24
  elevation?: Elevation;
25
25
  dismiss?: boolean;
26
26
  action?: Action;
27
+ showIcon?: boolean;
27
28
  }
28
29
 
29
30
  export const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(
30
- { variant, title, text, dismiss = false, elevation = 4, action },
31
+ { variant, title, text, dismiss = false, elevation = 4, action, showIcon = true },
31
32
  ref,
32
33
  ) {
33
34
  const [visible, setVisible] = useState(true);
@@ -39,7 +40,6 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(
39
40
  e.stopPropagation();
40
41
  action?.execute();
41
42
  };
42
-
43
43
  const handleDismiss = (e: MouseEvent<HTMLButtonElement>) => {
44
44
  e.preventDefault();
45
45
  e.stopPropagation();
@@ -58,6 +58,19 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(
58
58
  return <Info size={24} />;
59
59
  }
60
60
  }
61
+
62
+ function renderText() {
63
+ if (isValidElement(text)) {
64
+ return text;
65
+ }
66
+
67
+ if (typeof text === 'string') {
68
+ return <p>{text}</p>;
69
+ } else if (Array.isArray(text)) {
70
+ return <ul>{text?.map((message) => <li key={'message-' + message}>{message}</li>)}</ul>;
71
+ }
72
+ }
73
+
61
74
  return (
62
75
  <AnimatePresence>
63
76
  {visible && (
@@ -72,8 +85,8 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(
72
85
  ref={ref}
73
86
  role="status"
74
87
  >
75
- <Grid hasTitle={hasTitle}>
76
- <IconContainer variant={variant}>{getIcon()}</IconContainer>
88
+ <Grid hasTitle={hasTitle} showIcon={showIcon}>
89
+ {showIcon && <IconContainer variant={variant}>{getIcon()}</IconContainer>}
77
90
 
78
91
  {/* If title is declared set title, otherwise put everything on single line */}
79
92
  {title && (
@@ -82,25 +95,13 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(
82
95
  <div />
83
96
  </>
84
97
  )}
85
- {typeof text === 'string' ? (
86
- <p>{text}</p>
87
- ) : (
88
- text && (
89
- <ul>
90
- {text.map((message) => (
91
- <li key={'message-' + message}>{message}</li>
92
- ))}
93
- </ul>
94
- )
95
- )}
98
+ {renderText()}
96
99
  {hasTitle ? <div /> : null}
97
100
  <ButtonContainer hasTitle={hasTitle} show={dismiss || action ? true : false} variant={variant}>
98
101
  {action && (
99
- <Button size="small" variant="outline" onClick={handleExecute} text={action.text} color={variant} />
100
- )}
101
- {dismiss && (
102
- <Button size="small" color="white" variant="outline" onClick={handleDismiss} text="Dismiss" />
102
+ <Button size="tiny" variant="outline" onClick={handleExecute} text={action.text} color={variant} />
103
103
  )}
104
+ {dismiss && <Button size="tiny" color="white" variant="outline" onClick={handleDismiss} text="Dismiss" />}
104
105
  </ButtonContainer>
105
106
  </Grid>
106
107
  </Container>
@@ -58,10 +58,15 @@ export const Container = styled(motion.div)<{
58
58
  }}
59
59
  `;
60
60
 
61
- export const Grid = styled.div<{ hasTitle: boolean }>`
61
+ export const Grid = styled.div<{ hasTitle: boolean; showIcon: boolean }>`
62
62
  display: grid;
63
- grid-template-columns: ${({ theme, hasTitle }) =>
64
- !hasTitle ? `${theme.spacing[5]} 1fr fit-content(100px)` : `${theme.spacing[5]} 1fr`};}
63
+ grid-template-columns: ${({ theme, hasTitle, showIcon }) => {
64
+ const result = '';
65
+ if (showIcon) result.concat(`${theme.spacing[5]}`);
66
+ result.concat(' 1fr');
67
+ if (hasTitle) result.concat(' fit-content(100px)');
68
+ return result;
69
+ }}
65
70
  align-items: center;
66
71
  gap: ${({ theme, hasTitle }) => (hasTitle ? 0 : theme.spacing['0_5'])};
67
72
  `;
@@ -83,5 +88,6 @@ export const ButtonContainer = styled.div<{
83
88
  }>`
84
89
  display: ${({ show }): string => (show ? 'flex' : 'none')};
85
90
  align-items: center;
91
+ white-space: nowrap;
86
92
  margin-top: ${({ theme, hasTitle }): string => (hasTitle ? theme.spacing['1'] : theme.spacing[0])};
87
93
  `;
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import { Meta, StoryFn } from '@storybook/react';
3
+ import { Badge, BadgeProps } from '.';
4
+
5
+ export default {
6
+ title: 'Feedback/Badge',
7
+ component: Badge,
8
+ args: {
9
+ variant: 'warning',
10
+ animate: false,
11
+ },
12
+ } as Meta<BadgeProps>;
13
+
14
+ export const Default: StoryFn<BadgeProps> = (args) => (
15
+ <div>
16
+ <h2 style={{ backgroundColor: 'orange', position: 'relative', width: 'fit-content' }}>
17
+ this is the title{' '}
18
+ <Badge variant={args.variant} animate={args.animate}>
19
+ here
20
+ </Badge>
21
+ </h2>
22
+ </div>
23
+ );
@@ -0,0 +1,47 @@
1
+ import { forwardRef, PropsWithChildren } from 'react';
2
+ import { pulseAnimation, styled } from '../../../styled';
3
+ import { AlertVariants, Color } from '../../../styled';
4
+ import { shade } from 'polished';
5
+
6
+ type ColorVariant = AlertVariants | Color | 'default';
7
+
8
+ const Container = styled.div<{ variant: ColorVariant; animate: boolean }>`
9
+ background-color: ${({ theme, variant }) =>
10
+ variant === 'default' ? theme.colors.background : shade('0.8', theme.colors[variant])};
11
+ color: ${({ theme, variant }) => (variant === 'default' ? theme.colors.text : theme.colors[variant])};
12
+ font-size: ${({ theme }) => theme.fontSize.tiny};
13
+ font-weight: 600;
14
+ border-radius: ${({ theme }) => theme.borderRadius.small};
15
+ width: fit-content;
16
+ height: 1.5rem;
17
+ line-height: 1.1rem;
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ position: absolute;
22
+ top: -${({ theme }) => theme.spacing['1']};
23
+ right: -${({ theme }) => theme.spacing['0_75']};
24
+ padding: ${({ theme }) => theme.spacing['0_25']};
25
+ border: 1px solid
26
+ ${({ theme, variant }) => (variant === 'default' ? theme.colors.backgroundAccent : theme.colors[variant])};
27
+
28
+ animation: ${({ animate, variant, theme }) =>
29
+ animate ? pulseAnimation(variant === 'default' ? theme.colors.backgroundAccent : theme.colors[variant]) : 'none'}
30
+ 5s infinite ease-in-out;
31
+ `;
32
+
33
+ export interface BadgeProps {
34
+ variant?: ColorVariant;
35
+ animate?: boolean;
36
+ }
37
+
38
+ export const Badge = forwardRef<HTMLDivElement, PropsWithChildren<BadgeProps>>(function Badge(
39
+ { variant = 'default', children, animate = false },
40
+ ref,
41
+ ) {
42
+ return (
43
+ <Container ref={ref} variant={variant} animate={animate}>
44
+ {children}
45
+ </Container>
46
+ );
47
+ });
@@ -31,3 +31,6 @@ export type { ErrorPageProps } from './ErrorPage';
31
31
 
32
32
  export { FormError } from './FormError';
33
33
  export type { FormErrorProps } from './FormError';
34
+
35
+ export { Badge } from './Badge';
36
+ export type { BadgeProps } from './Badge';
@@ -3,6 +3,7 @@ import { DurationField, DurationFieldProps } from '../../../components';
3
3
  import { z } from 'zod';
4
4
  import { StoryFn, Meta } from '@storybook/react';
5
5
  import { useForm, SubmitHandler } from 'react-hook-form';
6
+ import { zodResolver } from '@hookform/resolvers/zod';
6
7
 
7
8
  export default {
8
9
  title: 'Inputs/DurationField',
@@ -26,7 +27,9 @@ export const Default: StoryFn<DurationFieldProps> = (args) => {
26
27
  duration: z.number().positive(),
27
28
  });
28
29
 
29
- const { control, handleSubmit } = useForm<z.infer<typeof validationSchema>>();
30
+ const { control, handleSubmit } = useForm<z.infer<typeof validationSchema>>({
31
+ resolver: zodResolver(validationSchema),
32
+ });
30
33
  const onSubmit: SubmitHandler<z.infer<typeof validationSchema>> = ({ duration }) => {
31
34
  setResult(duration);
32
35
  };
@@ -42,7 +42,6 @@ export const getLabelFromChildren = (children: ReactNode, value: string) => {
42
42
  }
43
43
  }
44
44
 
45
- // eslint-disable-next-line no-console
46
45
  console.error(
47
46
  `No label found for value ${value}. This occurs when a value is passed through the defaultValue prop of useForm, but the value is not present in the options.`,
48
47
  );
@@ -6,6 +6,13 @@ const Container = styled.div`
6
6
  margin-bottom: ${({ theme }) => theme.spacing['1']};
7
7
  hyphens: auto;
8
8
 
9
+ border: 1px solid ${({ theme }) => theme.colors.backgroundAccent};
10
+ padding: 10px 5px 0px 5px;
11
+ border-top-left-radius: 0;
12
+ border-top-right-radius: 0;
13
+ border-bottom-left-radius: 5px;
14
+ border-bottom-right-radius: 5px;
15
+
9
16
  p {
10
17
  hypens: auto;
11
18
  }
@@ -22,6 +22,18 @@ const Container = styled.button<{ open: boolean }>`
22
22
  margin-bottom: ${({ theme, open }) => (!open ? theme.spacing['1'] : theme.spacing['0_5'])};
23
23
  border: 1px solid ${({ theme }) => theme.colors.backgroundAccent};
24
24
  color: ${({ theme }) => theme.colors.textAlt};
25
+ font-size: ${({ theme }) => theme.fontSize.mediumLarge};
26
+
27
+ ${({ open }) => {
28
+ if (open) {
29
+ return `
30
+ border-bottom: 0;
31
+ border-bottom-left-radius: 0;
32
+ border-bottom-right-radius: 0;
33
+ margin-bottom: 0;
34
+ `;
35
+ }
36
+ }}
25
37
 
26
38
  svg {
27
39
  fill: ${({ theme }) => theme.colors.textAlt};
@@ -1,5 +1,6 @@
1
1
  // TODO: add custom replace option
2
2
  export function getInitials(name: string) {
3
+ if (!name) return '?';
3
4
  const replacedName = name.replace(/[._]/g, ' ');
4
5
  const parts = replacedName.split(' ');
5
6
 
@@ -8,7 +8,6 @@ export function useLocalStorage<T>(key: string, initialValue: T) {
8
8
  const item = window.localStorage.getItem(key);
9
9
  return item ? JSON.parse(item) : initialValue;
10
10
  } catch (e) {
11
- // eslint-disable-next-line no-console
12
11
  console.error('Error reading the local storage value', e);
13
12
  return initialValue;
14
13
  }
@@ -24,11 +23,9 @@ export function useLocalStorage<T>(key: string, initialValue: T) {
24
23
  } catch (e) {
25
24
  // DOMException code 22 for QuotaExceededError
26
25
  if (e instanceof DOMException && e.name === 'QuotaExceededError') {
27
- // eslint-disable-next-line no-console
28
26
  console.error('LocalStorage quota exceeded', e);
29
27
  setError(e);
30
28
  } else {
31
- // eslint-disable-next-line no-console
32
29
  console.error('Error setting the local storage value', e);
33
30
  }
34
31
  }