@justin_evo/evo-ui 1.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.
Files changed (110) hide show
  1. package/dist/Alert/Alert.d.ts +11 -0
  2. package/dist/AutoComplete/AutoComplete.d.ts +95 -0
  3. package/dist/Badge/Badge.d.ts +23 -0
  4. package/dist/Breadcrumb/Breadcrumb.d.ts +16 -0
  5. package/dist/Button/Button.d.ts +54 -0
  6. package/dist/Card/Card.d.ts +60 -0
  7. package/dist/Checkbox/Checkbox.d.ts +16 -0
  8. package/dist/CommandPalette/CommandPalette.d.ts +17 -0
  9. package/dist/Container/Container.d.ts +10 -0
  10. package/dist/Divider/Divider.d.ts +7 -0
  11. package/dist/Form/Form.d.ts +61 -0
  12. package/dist/Grid/Grid.d.ts +23 -0
  13. package/dist/ImageCropper/ImageCropper.d.ts +111 -0
  14. package/dist/Input/Input.d.ts +12 -0
  15. package/dist/Modal/Modal.d.ts +26 -0
  16. package/dist/Nav/Nav.d.ts +63 -0
  17. package/dist/Notification/Notification.d.ts +186 -0
  18. package/dist/Pagination/Pagination.d.ts +10 -0
  19. package/dist/Radio/Radio.d.ts +20 -0
  20. package/dist/RichTextArea/RichTextArea.d.ts +70 -0
  21. package/dist/Select/Select.d.ts +44 -0
  22. package/dist/Skeleton/Skeleton.d.ts +23 -0
  23. package/dist/Stack/Stack.d.ts +16 -0
  24. package/dist/Table/Table.d.ts +77 -0
  25. package/dist/Tabs/Tabs.d.ts +28 -0
  26. package/dist/Theme/ThemeProvider.d.ts +96 -0
  27. package/dist/Theme/ThemeToggle.d.ts +22 -0
  28. package/dist/Toggle/Toggle.d.ts +11 -0
  29. package/dist/Tooltip/Tooltip.d.ts +10 -0
  30. package/dist/TopNav/TopNav.d.ts +76 -0
  31. package/dist/TreeSelect/TreeSelect.d.ts +50 -0
  32. package/dist/declarations.d.ts +6 -0
  33. package/dist/evo-ui.css +1 -0
  34. package/dist/index.cjs.js +1 -0
  35. package/dist/index.d.ts +31 -0
  36. package/dist/index.es.js +5688 -0
  37. package/package.json +52 -0
  38. package/src/Alert/Alert.tsx +49 -0
  39. package/src/AutoComplete/AutoComplete.tsx +810 -0
  40. package/src/Badge/Badge.tsx +53 -0
  41. package/src/Breadcrumb/Breadcrumb.tsx +53 -0
  42. package/src/Button/Button.tsx +125 -0
  43. package/src/Card/Card.tsx +257 -0
  44. package/src/Checkbox/Checkbox.tsx +59 -0
  45. package/src/CommandPalette/CommandPalette.tsx +185 -0
  46. package/src/Container/Container.tsx +31 -0
  47. package/src/Divider/Divider.tsx +31 -0
  48. package/src/Form/Form.tsx +185 -0
  49. package/src/Grid/Grid.tsx +66 -0
  50. package/src/ImageCropper/ImageCropper.tsx +911 -0
  51. package/src/Input/Input.tsx +74 -0
  52. package/src/Modal/Modal.tsx +77 -0
  53. package/src/Nav/Nav.tsx +626 -0
  54. package/src/Notification/Notification.tsx +1503 -0
  55. package/src/Pagination/Pagination.tsx +76 -0
  56. package/src/Radio/Radio.tsx +69 -0
  57. package/src/RichTextArea/RichTextArea.tsx +869 -0
  58. package/src/Select/Select.tsx +515 -0
  59. package/src/Skeleton/Skeleton.tsx +70 -0
  60. package/src/Stack/Stack.tsx +52 -0
  61. package/src/Table/Table.tsx +335 -0
  62. package/src/Tabs/Tabs.tsx +90 -0
  63. package/src/Theme/ThemeProvider.tsx +253 -0
  64. package/src/Theme/ThemeToggle.tsx +79 -0
  65. package/src/Toggle/Toggle.tsx +48 -0
  66. package/src/Tooltip/Tooltip.tsx +38 -0
  67. package/src/TopNav/TopNav.tsx +994 -0
  68. package/src/TreeSelect/TreeSelect.tsx +825 -0
  69. package/src/css/alert.module.scss +93 -0
  70. package/src/css/autocomplete.module.scss +416 -0
  71. package/src/css/badge.module.scss +82 -0
  72. package/src/css/base/_color.scss +159 -0
  73. package/src/css/base/_theme.scss +237 -0
  74. package/src/css/base/_variables.scss +161 -0
  75. package/src/css/breadcrumb.module.scss +50 -0
  76. package/src/css/button.module.scss +385 -0
  77. package/src/css/card.module.scss +217 -0
  78. package/src/css/checkbox.module.scss +120 -0
  79. package/src/css/commandpalette.module.scss +211 -0
  80. package/src/css/container.module.scss +18 -0
  81. package/src/css/divider.module.scss +41 -0
  82. package/src/css/form.module.scss +245 -0
  83. package/src/css/imagecropper.module.scss +397 -0
  84. package/src/css/input.module.scss +89 -0
  85. package/src/css/modal.module.scss +105 -0
  86. package/src/css/nav.module.scss +339 -0
  87. package/src/css/notification.module.scss +691 -0
  88. package/src/css/pagination.module.scss +63 -0
  89. package/src/css/radio.module.scss +89 -0
  90. package/src/css/richtextarea.module.scss +307 -0
  91. package/src/css/select.module.scss +525 -0
  92. package/src/css/skeleton.module.scss +30 -0
  93. package/src/css/table.module.scss +386 -0
  94. package/src/css/tabs.module.scss +63 -0
  95. package/src/css/theme-toggle.module.scss +83 -0
  96. package/src/css/toggle.module.scss +54 -0
  97. package/src/css/tooltip.module.scss +97 -0
  98. package/src/css/topnav.module.scss +396 -0
  99. package/src/css/treeselect.module.scss +558 -0
  100. package/src/css/utilities/_borders.scss +111 -0
  101. package/src/css/utilities/_colors.scss +66 -0
  102. package/src/css/utilities/_effects.scss +216 -0
  103. package/src/css/utilities/_layout.scss +181 -0
  104. package/src/css/utilities/_position.scss +75 -0
  105. package/src/css/utilities/_sizing.scss +138 -0
  106. package/src/css/utilities/_spacing.scss +99 -0
  107. package/src/css/utilities/_typography.scss +121 -0
  108. package/src/css/utilities/index.scss +24 -0
  109. package/src/declarations.d.ts +6 -0
  110. package/src/index.ts +60 -0
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import styles from '../css/badge.module.scss';
3
+
4
+ type BadgeSeverity = 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info';
5
+ type BadgeVariant = 'solid' | 'outline' | 'subtle';
6
+ type BadgeSize = 'sm' | 'md' | 'lg';
7
+
8
+ interface EvoBadgeProps {
9
+ children: React.ReactNode;
10
+ severity?: BadgeSeverity;
11
+ variant?: BadgeVariant;
12
+ size?: BadgeSize;
13
+ dot?: boolean;
14
+ removable?: boolean;
15
+ onRemove?: () => void;
16
+ className?: string;
17
+ }
18
+
19
+ interface EvoBadgeGroupProps {
20
+ children: React.ReactNode;
21
+ className?: string;
22
+ }
23
+
24
+ const EvoBadgeGroup = ({ children, className = '' }: EvoBadgeGroupProps) => (
25
+ <div className={`${styles.badgeGroup} ${className}`}>{children}</div>
26
+ );
27
+
28
+ export const EvoBadge = ({
29
+ children,
30
+ severity = 'primary',
31
+ variant = 'solid',
32
+ size = 'md',
33
+ dot = false,
34
+ removable = false,
35
+ onRemove,
36
+ className = '',
37
+ }: EvoBadgeProps) => (
38
+ <span
39
+ className={[styles.badge, styles[severity], styles[variant], styles[size], className]
40
+ .filter(Boolean)
41
+ .join(' ')}
42
+ >
43
+ {dot && <span className={styles.dot} />}
44
+ {children}
45
+ {removable && (
46
+ <button className={styles.removeBtn} onClick={onRemove} aria-label="Remove">
47
+
48
+ </button>
49
+ )}
50
+ </span>
51
+ );
52
+
53
+ EvoBadge.Group = EvoBadgeGroup;
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import styles from '../css/breadcrumb.module.scss';
3
+
4
+ interface EvoBreadcrumbItemProps {
5
+ children: React.ReactNode;
6
+ href?: string;
7
+ current?: boolean;
8
+ }
9
+
10
+ interface EvoBreadcrumbProps {
11
+ children: React.ReactNode;
12
+ separator?: React.ReactNode;
13
+ className?: string;
14
+ }
15
+
16
+ const EvoBreadcrumbItem = ({ children, href, current = false }: EvoBreadcrumbItemProps) => (
17
+ <li className={styles.item}>
18
+ {href && !current ? (
19
+ <a href={href} className={styles.link}>
20
+ {children}
21
+ </a>
22
+ ) : (
23
+ <span
24
+ className={[styles.text, current ? styles.current : ''].filter(Boolean).join(' ')}
25
+ aria-current={current ? 'page' : undefined}
26
+ >
27
+ {children}
28
+ </span>
29
+ )}
30
+ </li>
31
+ );
32
+
33
+ export const EvoBreadcrumb = ({ children, separator = '/', className = '' }: EvoBreadcrumbProps) => {
34
+ const items = React.Children.toArray(children);
35
+ return (
36
+ <nav aria-label="breadcrumb" className={className}>
37
+ <ol className={styles.breadcrumb}>
38
+ {items.map((item, i) => (
39
+ <React.Fragment key={i}>
40
+ {item}
41
+ {i < items.length - 1 && (
42
+ <li className={styles.separator} aria-hidden="true">
43
+ {separator}
44
+ </li>
45
+ )}
46
+ </React.Fragment>
47
+ ))}
48
+ </ol>
49
+ </nav>
50
+ );
51
+ };
52
+
53
+ EvoBreadcrumb.Item = EvoBreadcrumbItem;
@@ -0,0 +1,125 @@
1
+ import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react';
2
+ import styles from '../css/button.module.scss';
3
+
4
+ type Variant = 'solid' | 'outline' | 'ghost' | 'soft';
5
+ type Severity = 'primary' | 'secondary' | 'danger' | 'warning' | 'success' | 'info';
6
+ type Size = 'sm' | 'md' | 'lg';
7
+ type Shape = 'default' | 'rounded' | 'square';
8
+
9
+ /**
10
+ * Configuration properties for the EvoButton component.
11
+ *
12
+ * Extends every native `<button>` attribute (type, form, name, autoFocus,
13
+ * aria-*, onMouseEnter, …) so consumers don't have to ask for them one by one.
14
+ */
15
+ export interface EvoButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
16
+ /** Text shown inside the button. Convenience shorthand for `children`. */
17
+ label?: string;
18
+
19
+ /**
20
+ * Visual style of the button.
21
+ * - `solid` — filled background (default).
22
+ * - `outline` — bordered, transparent background.
23
+ * - `ghost` — no border or background until hover.
24
+ * - `soft` — tinted background using the severity's soft token.
25
+ * @default 'solid'
26
+ */
27
+ variant?: Variant;
28
+
29
+ /**
30
+ * Semantic color theme.
31
+ * @default 'primary'
32
+ */
33
+ severity?: Severity;
34
+
35
+ /** @default 'md' */
36
+ size?: Size;
37
+
38
+ /**
39
+ * Border-radius shape, orthogonal to `variant`.
40
+ * - `default` — normal radius.
41
+ * - `rounded` — pill / fully rounded edges.
42
+ * - `square` — equal width/height; use for icon-only buttons.
43
+ * @default 'default'
44
+ */
45
+ shape?: Shape;
46
+
47
+ /** Icon rendered before the label. */
48
+ iconLeft?: ReactNode;
49
+
50
+ /** Icon rendered after the label. */
51
+ iconRight?: ReactNode;
52
+
53
+ /**
54
+ * When true, replaces icons with a spinner and disables interaction.
55
+ * `aria-busy` is set automatically.
56
+ */
57
+ loading?: boolean;
58
+
59
+ /** Optional text shown next to the spinner while `loading` is true. */
60
+ loadingText?: string;
61
+
62
+ /** Stretch to fill the parent's width. */
63
+ fullWidth?: boolean;
64
+ }
65
+
66
+ export const EvoButton = forwardRef<HTMLButtonElement, EvoButtonProps>(function EvoButton(
67
+ {
68
+ label,
69
+ children,
70
+ variant = 'solid',
71
+ severity = 'primary',
72
+ size = 'md',
73
+ shape = 'default',
74
+ iconLeft,
75
+ iconRight,
76
+ loading = false,
77
+ loadingText,
78
+ fullWidth = false,
79
+ disabled,
80
+ type = 'button',
81
+ className,
82
+ ...rest
83
+ },
84
+ ref,
85
+ ) {
86
+ const content = children ?? label;
87
+ const visibleContent = loading ? loadingText : content;
88
+ const isDisabled = disabled || loading;
89
+
90
+ const classes = [
91
+ styles.button,
92
+ styles[variant],
93
+ styles[severity],
94
+ styles[size],
95
+ shape !== 'default' ? styles[shape] : '',
96
+ fullWidth ? styles.fullWidth : '',
97
+ !visibleContent ? styles.iconOnly : '',
98
+ className,
99
+ ]
100
+ .filter(Boolean)
101
+ .join(' ');
102
+
103
+ return (
104
+ <button
105
+ ref={ref}
106
+ type={type}
107
+ className={classes}
108
+ disabled={isDisabled}
109
+ aria-busy={loading || undefined}
110
+ {...rest}
111
+ >
112
+ {loading ? (
113
+ <span className={styles.spinner} aria-hidden="true" />
114
+ ) : (
115
+ iconLeft && <span className={styles.icon}>{iconLeft}</span>
116
+ )}
117
+
118
+ {visibleContent != null && visibleContent !== '' && (
119
+ <span className={styles.label}>{visibleContent}</span>
120
+ )}
121
+
122
+ {!loading && iconRight && <span className={styles.icon}>{iconRight}</span>}
123
+ </button>
124
+ );
125
+ });
@@ -0,0 +1,257 @@
1
+ import {
2
+ forwardRef,
3
+ createElement,
4
+ type AnchorHTMLAttributes,
5
+ type ButtonHTMLAttributes,
6
+ type CSSProperties,
7
+ type HTMLAttributes,
8
+ type ReactNode,
9
+ type Ref,
10
+ } from 'react';
11
+ import styles from '../css/card.module.scss';
12
+
13
+ // ============================================================
14
+ // EvoCard
15
+ // ------------------------------------------------------------
16
+ // Compose-based card primitive. The API takes after Radix:
17
+ // <EvoCard.Root> is the documented surface; <EvoCard> is kept
18
+ // as an ergonomic alias that forwards to Root.
19
+ //
20
+ // Visual axes are deliberately minimal — three structural
21
+ // variants (elevated / outlined / ghost). All colour comes
22
+ // from semantic theme tokens, so dark mode is automatic.
23
+ //
24
+ // When `interactive` is set the root is rendered as a real
25
+ // <button type="button"> or, if `href` is provided, a real
26
+ // <a>. We never use <div onClick> — keyboard users and screen
27
+ // readers get first-class treatment.
28
+ // ============================================================
29
+
30
+ export type EvoCardVariant = 'elevated' | 'outlined' | 'ghost';
31
+ export type EvoCardOrientation = 'vertical' | 'horizontal' | 'responsive';
32
+ export type EvoCardHeadingLevel = 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
33
+
34
+ interface EvoCardRootBase {
35
+ variant?: EvoCardVariant;
36
+ orientation?: EvoCardOrientation;
37
+ children: ReactNode;
38
+ }
39
+
40
+ interface EvoCardRootStaticProps
41
+ extends EvoCardRootBase,
42
+ Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
43
+ interactive?: false;
44
+ href?: never;
45
+ }
46
+
47
+ interface EvoCardRootButtonProps
48
+ extends EvoCardRootBase,
49
+ Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children' | 'type'> {
50
+ interactive: true;
51
+ href?: never;
52
+ /** @default 'button' (never auto-submits) */
53
+ type?: 'button' | 'submit' | 'reset';
54
+ }
55
+
56
+ interface EvoCardRootAnchorProps
57
+ extends EvoCardRootBase,
58
+ Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'children' | 'href'> {
59
+ interactive: true;
60
+ href: string;
61
+ }
62
+
63
+ export type EvoCardRootProps =
64
+ | EvoCardRootStaticProps
65
+ | EvoCardRootButtonProps
66
+ | EvoCardRootAnchorProps;
67
+
68
+ function cx(...parts: Array<string | undefined | false | null>) {
69
+ return parts.filter(Boolean).join(' ');
70
+ }
71
+
72
+ function rootClasses(
73
+ variant: EvoCardVariant,
74
+ orientation: EvoCardOrientation,
75
+ interactive: boolean,
76
+ className?: string,
77
+ ) {
78
+ return cx(
79
+ styles.root,
80
+ styles[variant],
81
+ styles[`orient-${orientation}`],
82
+ interactive && styles.interactive,
83
+ className,
84
+ );
85
+ }
86
+
87
+ export const EvoCardRoot = forwardRef<HTMLElement, EvoCardRootProps>(
88
+ function EvoCardRoot(props, ref) {
89
+ const {
90
+ variant = 'elevated',
91
+ orientation = 'vertical',
92
+ interactive,
93
+ className,
94
+ children,
95
+ ...rest
96
+ } = props as EvoCardRootStaticProps &
97
+ Partial<EvoCardRootAnchorProps> &
98
+ Partial<EvoCardRootButtonProps> & { interactive?: boolean };
99
+
100
+ const isInteractive = interactive === true;
101
+ const classes = rootClasses(variant, orientation, isInteractive, className);
102
+
103
+ if (isInteractive && typeof (rest as { href?: string }).href === 'string') {
104
+ const anchorRest = rest as AnchorHTMLAttributes<HTMLAnchorElement>;
105
+ return (
106
+ <a ref={ref as Ref<HTMLAnchorElement>} className={classes} {...anchorRest}>
107
+ {children}
108
+ </a>
109
+ );
110
+ }
111
+
112
+ if (isInteractive) {
113
+ const { type = 'button', ...buttonRest } =
114
+ rest as ButtonHTMLAttributes<HTMLButtonElement>;
115
+ return (
116
+ <button
117
+ ref={ref as Ref<HTMLButtonElement>}
118
+ type={type}
119
+ className={classes}
120
+ {...buttonRest}
121
+ >
122
+ {children}
123
+ </button>
124
+ );
125
+ }
126
+
127
+ return (
128
+ <div
129
+ ref={ref as Ref<HTMLDivElement>}
130
+ className={classes}
131
+ {...(rest as HTMLAttributes<HTMLDivElement>)}
132
+ >
133
+ {children}
134
+ </div>
135
+ );
136
+ },
137
+ );
138
+ EvoCardRoot.displayName = 'EvoCardRoot';
139
+
140
+ // ----- Section slots -----
141
+
142
+ export type EvoCardHeaderProps = HTMLAttributes<HTMLDivElement>;
143
+ export type EvoCardBodyProps = HTMLAttributes<HTMLDivElement>;
144
+ export type EvoCardFooterProps = HTMLAttributes<HTMLDivElement>;
145
+
146
+ export const EvoCardHeader = forwardRef<HTMLDivElement, EvoCardHeaderProps>(
147
+ function EvoCardHeader({ className, ...rest }, ref) {
148
+ return <div ref={ref} className={cx(styles.header, className)} {...rest} />;
149
+ },
150
+ );
151
+ EvoCardHeader.displayName = 'EvoCardHeader';
152
+
153
+ export const EvoCardBody = forwardRef<HTMLDivElement, EvoCardBodyProps>(
154
+ function EvoCardBody({ className, ...rest }, ref) {
155
+ return <div ref={ref} className={cx(styles.body, className)} {...rest} />;
156
+ },
157
+ );
158
+ EvoCardBody.displayName = 'EvoCardBody';
159
+
160
+ export const EvoCardFooter = forwardRef<HTMLDivElement, EvoCardFooterProps>(
161
+ function EvoCardFooter({ className, ...rest }, ref) {
162
+ return <div ref={ref} className={cx(styles.footer, className)} {...rest} />;
163
+ },
164
+ );
165
+ EvoCardFooter.displayName = 'EvoCardFooter';
166
+
167
+ // ----- Title (configurable heading level) -----
168
+
169
+ export interface EvoCardTitleProps extends HTMLAttributes<HTMLHeadingElement> {
170
+ /** Semantic level. Visual size is fixed by the stylesheet. @default 'h3' */
171
+ as?: EvoCardHeadingLevel;
172
+ children: ReactNode;
173
+ }
174
+
175
+ export const EvoCardTitle = forwardRef<HTMLHeadingElement, EvoCardTitleProps>(
176
+ function EvoCardTitle({ as = 'h3', className, children, ...rest }, ref) {
177
+ return createElement(
178
+ as,
179
+ { ref, className: cx(styles.title, className), ...rest },
180
+ children,
181
+ );
182
+ },
183
+ );
184
+ EvoCardTitle.displayName = 'EvoCardTitle';
185
+
186
+ // ----- Description -----
187
+
188
+ export type EvoCardDescriptionProps = HTMLAttributes<HTMLParagraphElement>;
189
+
190
+ export const EvoCardDescription = forwardRef<
191
+ HTMLParagraphElement,
192
+ EvoCardDescriptionProps
193
+ >(function EvoCardDescription({ className, ...rest }, ref) {
194
+ return <p ref={ref} className={cx(styles.description, className)} {...rest} />;
195
+ });
196
+ EvoCardDescription.displayName = 'EvoCardDescription';
197
+
198
+ // ----- Media -----
199
+
200
+ export interface EvoCardMediaProps extends HTMLAttributes<HTMLDivElement> {
201
+ /** Convenience: renders an <img>. Omit and pass children for custom media. */
202
+ src?: string;
203
+ /** Required when `src` is set. Empty string marks the image as decorative. */
204
+ alt?: string;
205
+ /** e.g. 16/9 or '4/3'. Applied when no explicit dimensions are given. */
206
+ aspectRatio?: number | string;
207
+ children?: ReactNode;
208
+ }
209
+
210
+ export const EvoCardMedia = forwardRef<HTMLDivElement, EvoCardMediaProps>(
211
+ function EvoCardMedia(
212
+ { src, alt, aspectRatio, className, style, children, ...rest },
213
+ ref,
214
+ ) {
215
+ const mediaStyle: CSSProperties | undefined =
216
+ aspectRatio != null
217
+ ? { aspectRatio: aspectRatio as CSSProperties['aspectRatio'], ...style }
218
+ : style;
219
+ return (
220
+ <div
221
+ ref={ref}
222
+ className={cx(styles.media, className)}
223
+ style={mediaStyle}
224
+ {...rest}
225
+ >
226
+ {src ? <img src={src} alt={alt ?? ''} className={styles.mediaImg} /> : children}
227
+ </div>
228
+ );
229
+ },
230
+ );
231
+ EvoCardMedia.displayName = 'EvoCardMedia';
232
+
233
+ // ----- Compound export -----
234
+ //
235
+ // `EvoCard` is callable (forwards to Root) AND carries the sub-components
236
+ // as static properties so consumers can write either:
237
+ // <EvoCard variant="outlined">…</EvoCard>
238
+ // <EvoCard.Root variant="outlined">…</EvoCard.Root>
239
+
240
+ type EvoCardComponent = typeof EvoCardRoot & {
241
+ Root: typeof EvoCardRoot;
242
+ Header: typeof EvoCardHeader;
243
+ Title: typeof EvoCardTitle;
244
+ Description: typeof EvoCardDescription;
245
+ Body: typeof EvoCardBody;
246
+ Footer: typeof EvoCardFooter;
247
+ Media: typeof EvoCardMedia;
248
+ };
249
+
250
+ export const EvoCard = EvoCardRoot as EvoCardComponent;
251
+ EvoCard.Root = EvoCardRoot;
252
+ EvoCard.Header = EvoCardHeader;
253
+ EvoCard.Title = EvoCardTitle;
254
+ EvoCard.Description = EvoCardDescription;
255
+ EvoCard.Body = EvoCardBody;
256
+ EvoCard.Footer = EvoCardFooter;
257
+ EvoCard.Media = EvoCardMedia;
@@ -0,0 +1,59 @@
1
+ import React from 'react';
2
+ import styles from '../css/checkbox.module.scss';
3
+
4
+ interface EvoCheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
5
+ label?: string;
6
+ helperText?: string;
7
+ indeterminate?: boolean;
8
+ }
9
+
10
+ interface EvoCheckboxGroupProps {
11
+ children: React.ReactNode;
12
+ label?: string;
13
+ className?: string;
14
+ }
15
+
16
+ const EvoCheckboxGroup = ({ children, label, className = '' }: EvoCheckboxGroupProps) => (
17
+ <fieldset className={`${styles.group} ${className}`}>
18
+ {label && <legend className={styles.groupLabel}>{label}</legend>}
19
+ {children}
20
+ </fieldset>
21
+ );
22
+
23
+ export const EvoCheckbox = ({
24
+ label,
25
+ helperText,
26
+ indeterminate = false,
27
+ className = '',
28
+ disabled,
29
+ id,
30
+ ...rest
31
+ }: EvoCheckboxProps) => {
32
+ const inputRef = React.useRef<HTMLInputElement>(null);
33
+
34
+ React.useEffect(() => {
35
+ if (inputRef.current) inputRef.current.indeterminate = indeterminate;
36
+ }, [indeterminate]);
37
+
38
+ const inputId = id ?? label?.toLowerCase().replace(/\s+/g, '-');
39
+
40
+ return (
41
+ <div className={[styles.checkbox, disabled ? styles.disabled : '', className].filter(Boolean).join(' ')}>
42
+ <input
43
+ type="checkbox"
44
+ id={inputId}
45
+ ref={inputRef}
46
+ disabled={disabled}
47
+ className={styles.input}
48
+ {...rest}
49
+ />
50
+ <label htmlFor={inputId} className={styles.label}>
51
+ <span className={styles.checkmark} />
52
+ <span className={styles.labelText}>{label}</span>
53
+ </label>
54
+ {helperText && <p className={styles.helperText}>{helperText}</p>}
55
+ </div>
56
+ );
57
+ };
58
+
59
+ EvoCheckbox.Group = EvoCheckboxGroup;