ecmc-design-core 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.
Files changed (37) hide show
  1. package/README.md +55 -0
  2. package/dist/atoms/button/Button.svelte +61 -0
  3. package/dist/atoms/button/Button.svelte.d.ts +4 -0
  4. package/dist/atoms/button/constants.d.ts +7 -0
  5. package/dist/atoms/button/constants.js +8 -0
  6. package/dist/atoms/button/types.d.ts +35 -0
  7. package/dist/atoms/container/Container.svelte +106 -0
  8. package/dist/atoms/container/Container.svelte.d.ts +4 -0
  9. package/dist/atoms/container/constants.d.ts +16 -0
  10. package/dist/atoms/container/constants.js +19 -0
  11. package/dist/atoms/container/sub/box.container.svelte +33 -0
  12. package/dist/atoms/container/sub/box.container.svelte.d.ts +4 -0
  13. package/dist/atoms/container/sub/centered.container.svelte +28 -0
  14. package/dist/atoms/container/sub/centered.container.svelte.d.ts +4 -0
  15. package/dist/atoms/container/sub/gridbox.container.svelte +32 -0
  16. package/dist/atoms/container/sub/gridbox.container.svelte.d.ts +4 -0
  17. package/dist/atoms/container/sub/hbox.container.svelte +10 -0
  18. package/dist/atoms/container/sub/hbox.container.svelte.d.ts +4 -0
  19. package/dist/atoms/container/sub/vbox.container.svelte +10 -0
  20. package/dist/atoms/container/sub/vbox.container.svelte.d.ts +4 -0
  21. package/dist/atoms/container/types.d.ts +252 -0
  22. package/dist/atoms/input/Input.svelte +75 -0
  23. package/dist/atoms/input/Input.svelte.d.ts +4 -0
  24. package/dist/atoms/input/constants.d.ts +2 -0
  25. package/dist/atoms/input/constants.js +6 -0
  26. package/dist/atoms/input/types.d.ts +92 -0
  27. package/dist/atoms/text/Text.svelte +68 -0
  28. package/dist/atoms/text/Text.svelte.d.ts +4 -0
  29. package/dist/atoms/text/constants.d.ts +23 -0
  30. package/dist/atoms/text/constants.js +27 -0
  31. package/dist/atoms/text/types.d.ts +62 -0
  32. package/dist/index.d.ts +12 -0
  33. package/dist/index.js +11 -0
  34. package/dist/utils/cn.d.ts +6 -0
  35. package/dist/utils/cn.js +8 -0
  36. package/dist/utils/ecmc.css +126 -0
  37. package/package.json +91 -0
@@ -0,0 +1,252 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+
4
+ /**
5
+ * Props for the Box container component (HBox and VBox variants)
6
+ *
7
+ * Provides flexbox layout capabilities with customizable spacing, alignment, and wrapping.
8
+ *
9
+ * @example
10
+ * ```svelte
11
+ * <HBox gap="md" align="center" justify="space-between">
12
+ * <div>Item 1</div>
13
+ * <div>Item 2</div>
14
+ * </HBox>
15
+ * ```
16
+ */
17
+ export interface BoxProps extends ContainerProps {
18
+ /**
19
+ * Gap spacing between flex items (maps to CSS --spacing-* variables)
20
+ * - `none`: 0 - No gap
21
+ * - `xsm`: 0.5rem (8px) - var(--spacing-xsm)
22
+ * - `sm`: 1rem (16px) - var(--spacing-sm)
23
+ * - `md`: 1.5rem (24px) - var(--spacing-md)
24
+ * - `lg`: 2rem (32px) - var(--spacing-lg)
25
+ * - `xl`: 3rem (48px) - var(--spacing-xl)
26
+ */
27
+ gap?: 'none' | 'xsm' | 'sm' | 'md' | 'lg' | 'xl';
28
+
29
+ /**
30
+ * Cross-axis alignment of flex items
31
+ * - `flex-start`: Align items to the start
32
+ * - `center`: Center items
33
+ * - `flex-end`: Align items to the end
34
+ * - `stretch`: Stretch items to fill container
35
+ * - `baseline`: Align items along their baselines
36
+ *
37
+ * @default 'flex-start'
38
+ */
39
+ align?: 'flex-start' | 'center' | 'flex-end' | 'stretch' | 'baseline';
40
+
41
+ /**
42
+ * Main-axis justification of flex items
43
+ * - `flex-start`: Pack items at the start
44
+ * - `center`: Center items
45
+ * - `flex-end`: Pack items at the end
46
+ * - `space-between`: Distribute with space between
47
+ * - `space-around`: Distribute with space around
48
+ * - `space-evenly`: Distribute with even spacing
49
+ *
50
+ * @default 'flex-start'
51
+ */
52
+ justify?:
53
+ | 'flex-start'
54
+ | 'center'
55
+ | 'flex-end'
56
+ | 'space-between'
57
+ | 'space-around'
58
+ | 'space-evenly';
59
+
60
+ /**
61
+ * Whether flex items should wrap to multiple lines
62
+ *
63
+ * @default false
64
+ */
65
+ wrap?: boolean;
66
+ }
67
+
68
+ /**
69
+ * Internal props for Box components with direction variant
70
+ *
71
+ * @internal
72
+ */
73
+ export interface InternalBoxProps extends BoxProps {
74
+ /**
75
+ * Flex direction of the container
76
+ * - `horizontal`: Row layout (HBox)
77
+ * - `vertical`: Column layout (VBox)
78
+ */
79
+ variant: 'horizontal' | 'vertical';
80
+ }
81
+
82
+ /**
83
+ * Props for the Centered container component
84
+ *
85
+ * Centers content horizontally and/or vertically using flexbox.
86
+ *
87
+ * @example
88
+ * ```svelte
89
+ * <Centered horizontal vertical>
90
+ * <div>Perfectly centered content</div>
91
+ * </Centered>
92
+ * ```
93
+ */
94
+ export interface CenteredProps extends ContainerProps {
95
+ /**
96
+ * Whether to center content horizontally
97
+ *
98
+ * @default true
99
+ */
100
+ horizontal?: boolean;
101
+
102
+ /**
103
+ * Whether to center content vertically
104
+ *
105
+ * @default true
106
+ */
107
+ vertical?: boolean;
108
+ }
109
+
110
+ /**
111
+ * Props for the GridBox container component
112
+ *
113
+ * Provides CSS Grid layout capabilities with customizable columns and spacing.
114
+ *
115
+ * @example
116
+ * ```svelte
117
+ * <GridBox columns={3} gap="md">
118
+ * <div>Item 1</div>
119
+ * <div>Item 2</div>
120
+ * <div>Item 3</div>
121
+ * </GridBox>
122
+ * ```
123
+ */
124
+ export interface GridBoxProps extends ContainerProps {
125
+ /**
126
+ * Number of columns or custom grid-template-columns value
127
+ * - Number: Creates equal-width columns (e.g., 3 → `repeat(3, 1fr)`)
128
+ * - String: Custom CSS value (e.g., "200px 1fr 2fr")
129
+ *
130
+ * @default 2
131
+ */
132
+ columns?: number | string;
133
+
134
+ /**
135
+ * Gap spacing between grid items (maps to CSS --spacing-* variables)
136
+ * - `none`: 0 - No gap
137
+ * - `xsm`: 0.5rem (8px) - var(--spacing-xsm)
138
+ * - `sm`: 1rem (16px) - var(--spacing-sm)
139
+ * - `md`: 1.5rem (24px) - var(--spacing-md)
140
+ * - `lg`: 2rem (32px) - var(--spacing-lg)
141
+ * - `xl`: 3rem (48px) - var(--spacing-xl)
142
+ */
143
+ gap?: 'none' | 'xsm' | 'sm' | 'md' | 'lg' | 'xl';
144
+
145
+ /**
146
+ * Controls how auto-placed items flow into the grid
147
+ * - `row`: Fill each row before moving to next
148
+ * - `column`: Fill each column before moving to next
149
+ * - `dense`: Attempt to fill holes in the grid
150
+ * - `row dense`: Row flow with hole-filling
151
+ * - `column dense`: Column flow with hole-filling
152
+ *
153
+ * @default 'row'
154
+ */
155
+ autoFlow?: 'row' | 'column' | 'dense' | 'row dense' | 'column dense';
156
+ }
157
+
158
+ /**
159
+ * Container metrics interface for tracking div element properties
160
+ *
161
+ * Provides comprehensive measurements of a container element including
162
+ * dimensions, position, and scroll state.
163
+ */
164
+ export interface ContainerMetrics {
165
+ /**
166
+ * Element dimensions including borders and scrollbars
167
+ */
168
+ width: number;
169
+ height: number;
170
+
171
+ /**
172
+ * Element position relative to viewport
173
+ */
174
+ top: number;
175
+ left: number;
176
+
177
+ /**
178
+ * Element position relative to nearest positioned ancestor
179
+ */
180
+ offsetTop: number;
181
+ offsetLeft: number;
182
+
183
+ /**
184
+ * Total scrollable content dimensions
185
+ */
186
+ scrollWidth: number;
187
+ scrollHeight: number;
188
+
189
+ /**
190
+ * Current scroll position
191
+ */
192
+ scrollTop: number;
193
+ scrollLeft: number;
194
+
195
+ /**
196
+ * Inner dimensions excluding scrollbars
197
+ */
198
+ clientWidth: number;
199
+ clientHeight: number;
200
+ }
201
+
202
+ /**
203
+ * Base props for all container components
204
+ *
205
+ * Extends standard HTML div attributes with container-specific features.
206
+ *
207
+ * @example
208
+ * ```svelte
209
+ * <Container padding="md" bind:metrics={containerMetrics}>
210
+ * <p>Content with medium padding</p>
211
+ * </Container>
212
+ * ```
213
+ */
214
+ export interface ContainerProps extends HTMLAttributes<HTMLDivElement> {
215
+ /**
216
+ * The content to be rendered inside the container
217
+ */
218
+ children?: Snippet;
219
+
220
+ /**
221
+ * Padding options (maps to CSS --spacing-* variables)
222
+ * - `none`: 0 - No padding
223
+ * - `xsm`: 0.5rem (8px) - var(--spacing-xsm)
224
+ * - `sm`: 1rem (16px) - var(--spacing-sm)
225
+ * - `md`: 1.5rem (24px) - var(--spacing-md)
226
+ * - `lg`: 2rem (32px) - var(--spacing-lg)
227
+ * - `xl`: 3rem (48px) - var(--spacing-xl)
228
+ */
229
+ padding?: 'none' | 'xsm' | 'sm' | 'md' | 'lg' | 'xl';
230
+
231
+ fill?: boolean;
232
+
233
+ bg?: boolean;
234
+
235
+ /**
236
+ * Optional bind property to track container element metrics
237
+ *
238
+ * Provides real-time access to container dimensions, position, and scroll state.
239
+ *
240
+ * @example
241
+ * ```svelte
242
+ * <script>
243
+ * let metrics: ContainerMetrics;
244
+ * </script>
245
+ *
246
+ * <Container bind:metrics>
247
+ * Width: {metrics?.width}px
248
+ * </Container>
249
+ * ```
250
+ */
251
+ metrics?: ContainerMetrics | undefined;
252
+ }
@@ -0,0 +1,75 @@
1
+ <script lang="ts">
2
+ import { HBox, VBox, Centered } from '../../index.js';
3
+ import Text from '../text/Text.svelte';
4
+ import { defaultProps } from './constants.js';
5
+ import type { InputProps } from './types.js';
6
+ import { onMount } from 'svelte';
7
+
8
+ let {
9
+ type = defaultProps.type!,
10
+ labelLeft = defaultProps.labelLeft!,
11
+ placeholder = defaultProps.placeholder!,
12
+ value = $bindable(),
13
+ label,
14
+ id: providedId
15
+ }: InputProps = $props();
16
+
17
+ let id = $derived(providedId ?? `${label?.replace(/\s+/g, '-')}-id`);
18
+
19
+ let isSmallScreen = $state(false);
20
+ let effectiveLabelLeft = $derived(isSmallScreen ? false : labelLeft);
21
+
22
+ function checkScreenSize() {
23
+ isSmallScreen = window.innerWidth < 640;
24
+ }
25
+
26
+ onMount(() => {
27
+ checkScreenSize();
28
+ window.addEventListener('resize', checkScreenSize);
29
+ return () => window.removeEventListener('resize', checkScreenSize);
30
+ });
31
+ </script>
32
+
33
+ {#snippet contents()}
34
+ <label for={id}>
35
+ <Centered fill={true} padding="none" horizontal={effectiveLabelLeft}>
36
+ <Text>{label}</Text>
37
+ </Centered>
38
+ </label>
39
+ <input
40
+ {id}
41
+ bind:value
42
+ {type}
43
+ {placeholder}
44
+ aria-label={label ? label : 'unlabeled, please report'}
45
+ />
46
+ {/snippet}
47
+
48
+ {#if effectiveLabelLeft}
49
+ <HBox gap="xsm">
50
+ {@render contents()}
51
+ </HBox>
52
+ {:else}
53
+ <VBox gap="none">
54
+ {@render contents()}
55
+ </VBox>
56
+ {/if}
57
+
58
+ <style>
59
+ input {
60
+ padding: var(--spacing-xsm);
61
+ background-color: light-dark(var(--neutral-300), var(--neutral-700));
62
+ border: 1px solid light-dark(var(--neutral-300), var(--neutral-700));
63
+ outline: none;
64
+ width: 100%;
65
+ }
66
+
67
+ input::placeholder {
68
+ color: light-dark(var(--neutral-500), var(--neutral-400));
69
+ opacity: 0.7;
70
+ }
71
+
72
+ input:focus {
73
+ border-color: light-dark(var(--neutral-900), var(--neutral-300));
74
+ }
75
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { InputProps } from './types.js';
2
+ declare const Input: import("svelte").Component<InputProps, {}, "value">;
3
+ type Input = ReturnType<typeof Input>;
4
+ export default Input;
@@ -0,0 +1,2 @@
1
+ import type { InputProps } from './types.js';
2
+ export declare const defaultProps: Partial<InputProps>;
@@ -0,0 +1,6 @@
1
+ export const defaultProps = {
2
+ type: 'text',
3
+ labelLeft: false,
4
+ label: '',
5
+ placeholder: ''
6
+ };
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Props for the Input component
3
+ *
4
+ * A form input field with label support, multiple input types, and responsive label positioning.
5
+ * Features automatic label repositioning on small screens and accessibility support.
6
+ *
7
+ * @example
8
+ * ```svelte
9
+ * <script>
10
+ * let email = $state('');
11
+ * </script>
12
+ *
13
+ * <Input
14
+ * label="Email Address"
15
+ * type="email"
16
+ * bind:value={email}
17
+ * placeholder="Enter your email"
18
+ * id="email-input"
19
+ * />
20
+ * ```
21
+ *
22
+ * @example
23
+ * ```svelte
24
+ * <!-- Horizontal label layout (desktop only) -->
25
+ * <Input
26
+ * label="Username"
27
+ * labelLeft={true}
28
+ * bind:value={username}
29
+ * id="username"
30
+ * />
31
+ * ```
32
+ */
33
+ export interface InputProps {
34
+ /**
35
+ * The type of input field to render
36
+ * - `text`: Standard text input (default)
37
+ * - `email`: Email input with validation
38
+ * - `password`: Password input with masked characters
39
+ *
40
+ * @default 'text'
41
+ */
42
+ type?: 'text' | 'email' | 'password';
43
+
44
+ /**
45
+ * Label text displayed above or beside the input field
46
+ * Also used for the `aria-label` accessibility attribute
47
+ *
48
+ * @default ''
49
+ */
50
+ label?: string;
51
+
52
+ /**
53
+ * Position the label to the left of the input (horizontal layout)
54
+ * - `false`: Label appears above input (vertical layout)
55
+ * - `true`: Label appears to the left (horizontal layout on desktop)
56
+ *
57
+ * Note: Automatically reverts to vertical layout on screens smaller than 640px
58
+ *
59
+ * @default false
60
+ */
61
+ labelLeft?: boolean;
62
+
63
+ /**
64
+ * Placeholder text shown when the input is empty
65
+ *
66
+ * @default ''
67
+ */
68
+ placeholder?: string;
69
+
70
+ /**
71
+ * The current value of the input field
72
+ * This prop is bindable using `bind:value`
73
+ *
74
+ * @example
75
+ * ```svelte
76
+ * let userInput = $state('');
77
+ * <Input bind:value={userInput} />
78
+ * ```
79
+ */
80
+ value: string;
81
+
82
+ /**
83
+ * The HTML id attribute for the input element
84
+ * Used to associate the label with the input for accessibility
85
+ *
86
+ * @example
87
+ * ```svelte
88
+ * <Input id="user-email" label="Email" />
89
+ * ```
90
+ */
91
+ id?: string;
92
+ }
@@ -0,0 +1,68 @@
1
+ <script lang="ts">
2
+ import { cn } from '../../utils/cn.js';
3
+ import type { TextProps } from './types.js';
4
+ import { defaultProps, textVariantClasses, textAlignClasses } from './constants.js';
5
+
6
+ // Props with defaults
7
+ let {
8
+ children,
9
+ variant = defaultProps.variant!,
10
+ italic = defaultProps.italic!,
11
+ inline = defaultProps.inline!,
12
+ align = defaultProps.align!,
13
+ ...restProps
14
+ }: TextProps = $props();
15
+
16
+ let computedClasses = $derived(
17
+ cn(textAlignClasses[align], textVariantClasses[variant], italic ? 'text--italic' : '')
18
+ );
19
+ </script>
20
+
21
+ {#if inline}
22
+ <span class={computedClasses} {...restProps}>
23
+ {@render children?.()}
24
+ </span>
25
+ {:else}
26
+ <div class={computedClasses} {...restProps}>
27
+ {@render children?.()}
28
+ </div>
29
+ {/if}
30
+
31
+ <style>
32
+ .text--headline {
33
+ font-weight: 700;
34
+ font-size: var(--font-size-4);
35
+ }
36
+
37
+ .text--subtitle {
38
+ font-weight: 500;
39
+ font-size: var(--font-size-3);
40
+ }
41
+
42
+ .text--body-plus {
43
+ font-weight: 300;
44
+ font-size: var(--font-size-2);
45
+ }
46
+
47
+ .text--body {
48
+ font-weight: 300;
49
+ font-size: var(--font-size-1);
50
+ }
51
+
52
+ .text--half {
53
+ font-weight: 100;
54
+ font-size: var(--font-size-0);
55
+ }
56
+
57
+ .text--italic {
58
+ font-style: italic;
59
+ }
60
+
61
+ .text--align-center {
62
+ text-align: center;
63
+ }
64
+
65
+ .text--align-right {
66
+ text-align: right;
67
+ }
68
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { TextProps } from './types.js';
2
+ declare const Text: import("svelte").Component<TextProps, {}, "">;
3
+ type Text = ReturnType<typeof Text>;
4
+ export default Text;
@@ -0,0 +1,23 @@
1
+ import type { TextProps } from './types.js';
2
+ /**
3
+ * Default props for the text atom
4
+ */
5
+ export declare const defaultProps: Partial<TextProps>;
6
+ /**
7
+ * CSS class mappings for text variants
8
+ */
9
+ export declare const textVariantClasses: {
10
+ readonly headline: "text--headline";
11
+ readonly subtitle: "text--subtitle";
12
+ readonly body: "text--body";
13
+ readonly bodyPlus: "text--body-plus";
14
+ readonly half: "text--half";
15
+ };
16
+ /**
17
+ * CSS class mappings for text alignment
18
+ */
19
+ export declare const textAlignClasses: {
20
+ readonly center: "text--align-center";
21
+ readonly right: "text--align-right";
22
+ readonly left: "";
23
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Default props for the text atom
3
+ */
4
+ export const defaultProps = {
5
+ variant: 'body',
6
+ italic: false,
7
+ inline: false,
8
+ align: 'left'
9
+ };
10
+ /**
11
+ * CSS class mappings for text variants
12
+ */
13
+ export const textVariantClasses = {
14
+ headline: 'text--headline',
15
+ subtitle: 'text--subtitle',
16
+ body: 'text--body',
17
+ bodyPlus: 'text--body-plus',
18
+ half: 'text--half'
19
+ };
20
+ /**
21
+ * CSS class mappings for text alignment
22
+ */
23
+ export const textAlignClasses = {
24
+ center: 'text--align-center',
25
+ right: 'text--align-right',
26
+ left: ''
27
+ };
@@ -0,0 +1,62 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+
4
+ /**
5
+ * Props for the Text component
6
+ *
7
+ * Provides typography with various size variants, alignment, and styling options.
8
+ *
9
+ * @example
10
+ * ```svelte
11
+ * <Text variant="headline" align="center">
12
+ * Welcome to our app
13
+ * </Text>
14
+ *
15
+ * <Text italic>
16
+ * This is some italic body text
17
+ * </Text>
18
+ * ```
19
+ */
20
+ export interface TextProps extends HTMLAttributes<HTMLElement> {
21
+ /**
22
+ * The content to be rendered inside the text
23
+ */
24
+ children?: Snippet;
25
+
26
+ /**
27
+ * Text variant that determines the size and weight styling
28
+ * - `headline`: Large heading text (3rem/48px) - var(--font-size-4)
29
+ * - `subtitle`: Medium heading text (2.5rem/40px) - var(--font-size-3)
30
+ * - `body`: Standard body text (1rem/16px) - var(--font-size-1)
31
+ * - `bodyPlus`: Larger body text (1.2rem/19.2px) - var(--font-size-2)
32
+ *
33
+ * @default 'body'
34
+ */
35
+ variant?: 'headline' | 'subtitle' | 'body' | 'bodyPlus' | 'half';
36
+
37
+ /**
38
+ * Whether the text should be rendered in italic style
39
+ *
40
+ * @default false
41
+ */
42
+ italic?: boolean;
43
+
44
+ /**
45
+ * Whether the text should display inline (span) or block-level (div)
46
+ * - `false`: Renders as block-level `<div>`
47
+ * - `true`: Renders as inline `<span>`
48
+ *
49
+ * @default false
50
+ */
51
+ inline?: boolean;
52
+
53
+ /**
54
+ * Horizontal text alignment
55
+ * - `left`: Align text to the left
56
+ * - `center`: Center text
57
+ * - `right`: Align text to the right
58
+ *
59
+ * @default 'left'
60
+ */
61
+ align?: 'right' | 'center' | 'left';
62
+ }
@@ -0,0 +1,12 @@
1
+ export { default as Container } from './atoms/container/Container.svelte';
2
+ export type { ContainerProps, ContainerMetrics } from './atoms/container/types.js';
3
+ export { default as HBox } from './atoms/container/sub/hbox.container.svelte';
4
+ export { default as VBox } from './atoms/container/sub/vbox.container.svelte';
5
+ export { default as GridBox } from './atoms/container/sub/gridbox.container.svelte';
6
+ export { default as Centered } from './atoms/container/sub/centered.container.svelte';
7
+ export { default as Text } from './atoms/text/Text.svelte';
8
+ export type { TextProps } from './atoms/text/types.js';
9
+ export { default as Button } from './atoms/button/Button.svelte';
10
+ export type { ButtonProps } from './atoms/button/types.js';
11
+ export { default as Input } from './atoms/input/Input.svelte';
12
+ export type { InputProps } from './atoms/input/types.js';
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ // Reexport your entry components here
2
+ // Atoms
3
+ export { default as Container } from './atoms/container/Container.svelte';
4
+ // Reexport sub-containers
5
+ export { default as HBox } from './atoms/container/sub/hbox.container.svelte';
6
+ export { default as VBox } from './atoms/container/sub/vbox.container.svelte';
7
+ export { default as GridBox } from './atoms/container/sub/gridbox.container.svelte';
8
+ export { default as Centered } from './atoms/container/sub/centered.container.svelte';
9
+ export { default as Text } from './atoms/text/Text.svelte';
10
+ export { default as Button } from './atoms/button/Button.svelte';
11
+ export { default as Input } from './atoms/input/Input.svelte';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Simple class name concatenation utility
3
+ * @param classes - Class names to concatenate (strings, undefined, or null)
4
+ * @returns Merged class string
5
+ */
6
+ export declare function cn(...classes: (string | undefined | null | boolean)[]): string;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Simple class name concatenation utility
3
+ * @param classes - Class names to concatenate (strings, undefined, or null)
4
+ * @returns Merged class string
5
+ */
6
+ export function cn(...classes) {
7
+ return classes.filter(Boolean).join(' ').trim();
8
+ }