@makolabs/ripple 0.5.0 → 1.0.2

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/dist/index.d.ts CHANGED
@@ -48,27 +48,7 @@ export type AnchorHTMLProps = {
48
48
  href: string;
49
49
  } & Record<string, unknown>;
50
50
  export type ButtonProps = BaseButtonProps & (ButtonHTMLProps | AnchorHTMLProps);
51
- export type MakeModalProps = {
52
- open?: boolean;
53
- onclose?: () => void;
54
- title?: string;
55
- description?: string;
56
- hideCloseButton?: boolean;
57
- closeOnBackdropClick?: boolean;
58
- closeOnEsc?: boolean;
59
- position?: 'top' | 'center' | 'bottom';
60
- size?: VariantSizes;
61
- class?: string;
62
- backdropclass?: string;
63
- contentclass?: string;
64
- headerclass?: string;
65
- bodyclass?: string;
66
- titleclass?: string;
67
- children?: Snippet;
68
- header?: Snippet;
69
- footer?: Snippet;
70
- custom?: Snippet;
71
- };
51
+ export type { ModalProps } from './modal/modal.js';
72
52
  export type DrawerProps = {
73
53
  open?: boolean;
74
54
  onclose?: () => void;
@@ -337,6 +317,8 @@ export { tv, cn } from './helper/cls.js';
337
317
  export { isRouteActive } from './helper/nav.svelte.js';
338
318
  export { default as Button } from './button/Button.svelte';
339
319
  export { default as Modal } from './modal/Modal.svelte';
320
+ export { default as Pipeline } from './pipeline/Pipeline.svelte';
321
+ export type { PipelineStage } from './pipeline/Pipeline.svelte';
340
322
  export { default as Drawer } from './drawer/Drawer.svelte';
341
323
  export { default as PageHeader } from './header/PageHeader.svelte';
342
324
  export { default as Breadcrumbs } from './header/Breadcrumbs.svelte';
package/dist/index.js CHANGED
@@ -16,6 +16,8 @@ export { isRouteActive } from './helper/nav.svelte.js';
16
16
  export { default as Button } from './button/Button.svelte';
17
17
  // Modal
18
18
  export { default as Modal } from './modal/Modal.svelte';
19
+ // Pipeline
20
+ export { default as Pipeline } from './pipeline/Pipeline.svelte';
19
21
  // Drawer
20
22
  export { default as Drawer } from './drawer/Drawer.svelte';
21
23
  // Header
@@ -2,8 +2,8 @@ export declare const metricCard: import("tailwind-variants").TVReturnType<{
2
2
  [key: string]: {
3
3
  [key: string]: import("tailwind-variants").ClassValue | {
4
4
  base?: import("tailwind-variants").ClassValue;
5
- value?: import("tailwind-variants").ClassValue;
6
5
  title?: import("tailwind-variants").ClassValue;
6
+ value?: import("tailwind-variants").ClassValue;
7
7
  progress?: import("tailwind-variants").ClassValue;
8
8
  detail?: import("tailwind-variants").ClassValue;
9
9
  };
@@ -12,8 +12,8 @@ export declare const metricCard: import("tailwind-variants").TVReturnType<{
12
12
  [x: string]: {
13
13
  [x: string]: import("tailwind-variants").ClassValue | {
14
14
  base?: import("tailwind-variants").ClassValue;
15
- value?: import("tailwind-variants").ClassValue;
16
15
  title?: import("tailwind-variants").ClassValue;
16
+ value?: import("tailwind-variants").ClassValue;
17
17
  progress?: import("tailwind-variants").ClassValue;
18
18
  detail?: import("tailwind-variants").ClassValue;
19
19
  };
@@ -28,8 +28,8 @@ export declare const metricCard: import("tailwind-variants").TVReturnType<{
28
28
  [key: string]: {
29
29
  [key: string]: import("tailwind-variants").ClassValue | {
30
30
  base?: import("tailwind-variants").ClassValue;
31
- value?: import("tailwind-variants").ClassValue;
32
31
  title?: import("tailwind-variants").ClassValue;
32
+ value?: import("tailwind-variants").ClassValue;
33
33
  progress?: import("tailwind-variants").ClassValue;
34
34
  detail?: import("tailwind-variants").ClassValue;
35
35
  };
@@ -1,209 +1,107 @@
1
1
  <script lang="ts">
2
- import { onMount, onDestroy, tick } from 'svelte';
3
- import { browser } from '$app/environment';
4
2
  import { fade, scale } from 'svelte/transition';
5
3
  import { quintOut } from 'svelte/easing';
6
- import { cn } from '../helper/cls.js';
7
- import { modal } from './modal.js';
8
- import { Size, type MakeModalProps } from '../index.js';
9
-
4
+ import { modal, type ModalProps } from './modal.js';
5
+
10
6
  let {
11
7
  open = $bindable(false),
12
- onclose: onClose = () => {},
8
+ onclose = () => {},
13
9
  title,
14
10
  description,
15
- position = 'center',
16
- size = Size.BASE,
17
- children,
18
- header,
19
- custom,
11
+ size,
12
+ hideCloseButton = false,
20
13
  class: className = '',
21
- backdropclass: backdropClassName = '',
22
- contentclass: contentClassName = '',
23
- headerclass: headerClassName = '',
24
- titleclass: titleClass = '',
25
- bodyclass: bodyClassName = ''
26
- }: MakeModalProps = $props();
27
-
28
- let modalElement: HTMLDivElement | undefined = $state();
29
-
30
- const {
31
- base,
32
- backdrop,
33
- contentWrapper,
34
- content,
35
- header: headerVClass,
36
- title: titleVClass,
37
- closeButton,
38
- body
39
- } = $derived(
40
- modal({
41
- open,
42
- position,
43
- size
44
- })
45
- );
46
-
47
- const closeButtonClasses = $derived(closeButton());
48
- const contentItemClass = $derived(content());
49
- const baseClass = $derived(cn(base(), className));
50
- const backdropClass = $derived(cn(backdrop(), backdropClassName));
51
- const contentWrapperClass = $derived(cn(contentWrapper(), contentClassName));
52
- const headerClass = $derived(cn(headerVClass(), headerClassName));
53
- const titleClasses = $derived(cn(titleVClass(), titleClass));
54
- const bodyClasses = $derived(cn(body(), bodyClassName));
14
+ children,
15
+ footer
16
+ }: ModalProps = $props();
55
17
 
56
- function handleBackdropClick(e: MouseEvent) {
57
- if (e.target === e.currentTarget) {
58
- onClose();
59
- }
18
+ const styles = $derived(modal({ size }));
19
+
20
+ function handleBackdropClick() {
21
+ onclose();
60
22
  }
61
-
62
- function handleEscapeKey(e: KeyboardEvent) {
23
+
24
+ function handleKeydown(e: KeyboardEvent) {
63
25
  if (e.key === 'Escape' && open) {
64
- onClose();
65
- e.preventDefault();
66
- e.stopPropagation();
26
+ onclose();
67
27
  }
68
28
  }
69
-
70
- function handleCloseClick() {
71
- onClose();
72
- }
73
-
74
- // Focus trap functionality
75
- let previouslyFocusedElement: HTMLElement | null = null;
76
-
77
- function trapFocus(element: HTMLElement) {
78
- const focusableElements = element.querySelectorAll(
79
- 'a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])'
80
- );
81
-
82
- if (focusableElements.length === 0) return;
83
-
84
- const firstElement = focusableElements[0] as HTMLElement;
85
- const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;
86
-
87
- function handleTabKey(e: KeyboardEvent) {
88
- if (e.key !== 'Tab') return;
89
-
90
- if (e.shiftKey) {
91
- if (document.activeElement === firstElement) {
92
- lastElement.focus();
93
- e.preventDefault();
94
- }
95
- } else {
96
- if (document.activeElement === lastElement) {
97
- firstElement.focus();
98
- e.preventDefault();
99
- }
100
- }
101
- }
102
-
103
- element.addEventListener('keydown', handleTabKey);
104
- firstElement.focus();
105
-
106
- return () => {
107
- element.removeEventListener('keydown', handleTabKey);
108
- };
109
- }
110
-
111
- onMount(() => {
112
- document.addEventListener('keydown', handleEscapeKey);
113
- return () => {
114
- document.removeEventListener('keydown', handleEscapeKey);
115
- };
116
- });
117
-
29
+
118
30
  $effect(() => {
119
- if (!browser) return;
120
31
  if (open) {
121
- previouslyFocusedElement = document.activeElement as HTMLElement;
122
32
  document.body.style.overflow = 'hidden';
123
-
124
- tick().then(() => {
125
- if (modalElement) {
126
- trapFocus(modalElement);
127
- }
128
- });
33
+ document.addEventListener('keydown', handleKeydown);
129
34
  } else {
130
35
  document.body.style.overflow = '';
131
- if (previouslyFocusedElement) {
132
- previouslyFocusedElement.focus();
133
- }
36
+ document.removeEventListener('keydown', handleKeydown);
134
37
  }
135
- });
136
-
137
- onDestroy(() => {
138
- if (browser && document.body.style.overflow === 'hidden') {
38
+
39
+ return () => {
139
40
  document.body.style.overflow = '';
140
- }
41
+ document.removeEventListener('keydown', handleKeydown);
42
+ };
141
43
  });
142
44
  </script>
143
45
 
144
- {#if browser}
145
- <div
146
- class={baseClass}
147
- bind:this={modalElement}
148
- role="dialog"
149
- aria-modal="true"
150
- aria-labelledby={title ? 'modal-title' : undefined}
151
- aria-describedby={description ? 'modal-description' : undefined}
152
- >
46
+ {#if open}
47
+ <div class={styles.base()} role="dialog" aria-modal="true">
153
48
  <!-- Backdrop -->
154
- <button
155
- class={backdropClass}
49
+ <div
50
+ class={styles.backdrop()}
156
51
  onclick={handleBackdropClick}
157
52
  transition:fade={{ duration: 200 }}
158
- aria-label="Close modal"
159
- ></button>
160
-
161
- <!-- Modal content -->
162
- <div
163
- class={contentWrapperClass}
53
+ role="presentation"></div>
54
+
55
+ <!-- Modal Container -->
56
+ <div
57
+ class="{styles.container()} {className}"
164
58
  transition:scale={{ duration: 200, easing: quintOut, start: 0.95 }}
165
59
  >
166
- {#if custom}
167
- {@render custom()}
168
- {:else}
169
- <div class={contentItemClass}>
170
- {#if header || title}
171
- <div class={headerClass}>
172
- {#if header}
173
- {@render header()}
174
- {:else}
175
- {#if title}
176
- <h3 id="modal-title" class={titleClasses}>{title}</h3>
177
- {/if}
178
- <button
179
- type="button"
180
- class={closeButtonClasses}
181
- onclick={handleCloseClick}
182
- aria-label="Close modal"
183
- >
184
- <svg
185
- xmlns="http://www.w3.org/2000/svg"
186
- width="12"
187
- height="12"
188
- viewBox="0 0 12 12"
189
- >
190
- <path
191
- fill="currentColor"
192
- d="m1.897 2.054l.073-.084a.75.75 0 0 1 .976-.073l.084.073L6 4.939l2.97-2.97a.75.75 0 1 1 1.06 1.061L7.061 6l2.97 2.97a.75.75 0 0 1 .072.976l-.073.084a.75.75 0 0 1-.976.073l-.084-.073L6 7.061l-2.97 2.97A.75.75 0 1 1 1.97 8.97L4.939 6l-2.97-2.97a.75.75 0 0 1-.072-.976l.073-.084z"
193
- />
194
- </svg>
195
- </button>
196
- {/if}
197
- </div>
198
- {/if}
199
-
200
- {#if children}
201
- <div class={bodyClasses}>
202
- {@render children()}
203
- </div>
60
+ <!-- Header -->
61
+ {#if title || !hideCloseButton}
62
+ <header class={styles.header()}>
63
+ <div class="flex-1">
64
+ {#if title}
65
+ <h2 class={styles.title()}>{title}</h2>
66
+ {/if}
67
+ {#if description}
68
+ <p class={styles.description()}>{description}</p>
69
+ {/if}
70
+ </div>
71
+
72
+ {#if !hideCloseButton}
73
+ <button
74
+ type="button"
75
+ class={styles.close()}
76
+ onclick={onclose}
77
+ aria-label="Close"
78
+ >
79
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
80
+ <path
81
+ d="M15 5L5 15M5 5L15 15"
82
+ stroke="currentColor"
83
+ stroke-width="2"
84
+ stroke-linecap="round"
85
+ />
86
+ </svg>
87
+ </button>
204
88
  {/if}
89
+ </header>
90
+ {/if}
91
+
92
+ <!-- Body -->
93
+ {#if children}
94
+ <div class={styles.body()}>
95
+ {@render children()}
205
96
  </div>
206
97
  {/if}
98
+
99
+ <!-- Footer -->
100
+ {#if footer}
101
+ <footer class={styles.footer()}>
102
+ {@render footer()}
103
+ </footer>
104
+ {/if}
207
105
  </div>
208
106
  </div>
209
- {/if}
107
+ {/if}
@@ -1,4 +1,4 @@
1
- import { type MakeModalProps } from '../index.js';
2
- declare const Modal: import("svelte").Component<MakeModalProps, {}, "open">;
1
+ import { type ModalProps } from './modal.js';
2
+ declare const Modal: import("svelte").Component<ModalProps, {}, "open">;
3
3
  type Modal = ReturnType<typeof Modal>;
4
4
  export default Modal;
@@ -1,212 +1,107 @@
1
- import { Size } from '../index.js';
1
+ import { Size, type VariantSizes } from '../index.js';
2
+ import type { Snippet } from 'svelte';
2
3
  export declare const modal: import("tailwind-variants").TVReturnType<{
3
- open: {
4
- true: {
5
- base: string;
6
- backdrop: string;
7
- contentWrapper: string;
8
- };
9
- false: {
10
- base: string;
11
- backdrop: string;
12
- contentWrapper: string;
13
- };
14
- };
15
- position: {
16
- center: {
17
- contentWrapper: string;
18
- };
19
- top: {
20
- contentWrapper: string;
21
- };
22
- bottom: {
23
- contentWrapper: string;
24
- };
25
- left: {
26
- contentWrapper: string;
27
- };
28
- right: {
29
- contentWrapper: string;
30
- };
31
- };
32
4
  size: {
33
5
  [Size.XS]: {
34
- contentWrapper: string;
6
+ container: string;
35
7
  };
36
8
  [Size.SM]: {
37
- contentWrapper: string;
9
+ container: string;
38
10
  };
39
11
  [Size.BASE]: {
40
- contentWrapper: string;
12
+ container: string;
41
13
  };
42
14
  [Size.LG]: {
43
- contentWrapper: string;
15
+ container: string;
44
16
  };
45
17
  [Size.XL]: {
46
- contentWrapper: string;
18
+ container: string;
47
19
  };
48
20
  [Size.XXL]: {
49
- contentWrapper: string;
50
- };
51
- };
52
- border: {
53
- none: {
54
- content: string;
55
- };
56
- default: {
57
- content: string;
58
- };
59
- colored: {
60
- content: string;
21
+ container: string;
61
22
  };
62
23
  };
63
24
  }, {
64
25
  base: string;
65
26
  backdrop: string;
66
- contentWrapper: string;
67
- content: string;
27
+ container: string;
68
28
  header: string;
69
29
  body: string;
30
+ footer: string;
70
31
  title: string;
71
- closeButton: string;
32
+ description: string;
33
+ close: string;
72
34
  }, undefined, {
73
- open: {
74
- true: {
75
- base: string;
76
- backdrop: string;
77
- contentWrapper: string;
78
- };
79
- false: {
80
- base: string;
81
- backdrop: string;
82
- contentWrapper: string;
83
- };
84
- };
85
- position: {
86
- center: {
87
- contentWrapper: string;
88
- };
89
- top: {
90
- contentWrapper: string;
91
- };
92
- bottom: {
93
- contentWrapper: string;
94
- };
95
- left: {
96
- contentWrapper: string;
97
- };
98
- right: {
99
- contentWrapper: string;
100
- };
101
- };
102
35
  size: {
103
36
  [Size.XS]: {
104
- contentWrapper: string;
37
+ container: string;
105
38
  };
106
39
  [Size.SM]: {
107
- contentWrapper: string;
40
+ container: string;
108
41
  };
109
42
  [Size.BASE]: {
110
- contentWrapper: string;
43
+ container: string;
111
44
  };
112
45
  [Size.LG]: {
113
- contentWrapper: string;
46
+ container: string;
114
47
  };
115
48
  [Size.XL]: {
116
- contentWrapper: string;
49
+ container: string;
117
50
  };
118
51
  [Size.XXL]: {
119
- contentWrapper: string;
120
- };
121
- };
122
- border: {
123
- none: {
124
- content: string;
125
- };
126
- default: {
127
- content: string;
128
- };
129
- colored: {
130
- content: string;
52
+ container: string;
131
53
  };
132
54
  };
133
55
  }, {
134
56
  base: string;
135
57
  backdrop: string;
136
- contentWrapper: string;
137
- content: string;
58
+ container: string;
138
59
  header: string;
139
60
  body: string;
61
+ footer: string;
140
62
  title: string;
141
- closeButton: string;
63
+ description: string;
64
+ close: string;
142
65
  }, import("tailwind-variants").TVReturnType<{
143
- open: {
144
- true: {
145
- base: string;
146
- backdrop: string;
147
- contentWrapper: string;
148
- };
149
- false: {
150
- base: string;
151
- backdrop: string;
152
- contentWrapper: string;
153
- };
154
- };
155
- position: {
156
- center: {
157
- contentWrapper: string;
158
- };
159
- top: {
160
- contentWrapper: string;
161
- };
162
- bottom: {
163
- contentWrapper: string;
164
- };
165
- left: {
166
- contentWrapper: string;
167
- };
168
- right: {
169
- contentWrapper: string;
170
- };
171
- };
172
66
  size: {
173
67
  [Size.XS]: {
174
- contentWrapper: string;
68
+ container: string;
175
69
  };
176
70
  [Size.SM]: {
177
- contentWrapper: string;
71
+ container: string;
178
72
  };
179
73
  [Size.BASE]: {
180
- contentWrapper: string;
74
+ container: string;
181
75
  };
182
76
  [Size.LG]: {
183
- contentWrapper: string;
77
+ container: string;
184
78
  };
185
79
  [Size.XL]: {
186
- contentWrapper: string;
80
+ container: string;
187
81
  };
188
82
  [Size.XXL]: {
189
- contentWrapper: string;
190
- };
191
- };
192
- border: {
193
- none: {
194
- content: string;
195
- };
196
- default: {
197
- content: string;
198
- };
199
- colored: {
200
- content: string;
83
+ container: string;
201
84
  };
202
85
  };
203
86
  }, {
204
87
  base: string;
205
88
  backdrop: string;
206
- contentWrapper: string;
207
- content: string;
89
+ container: string;
208
90
  header: string;
209
91
  body: string;
92
+ footer: string;
210
93
  title: string;
211
- closeButton: string;
94
+ description: string;
95
+ close: string;
212
96
  }, undefined, unknown, unknown, undefined>>;
97
+ export type ModalProps = {
98
+ open?: boolean;
99
+ onclose?: () => void;
100
+ title?: string;
101
+ description?: string;
102
+ size?: VariantSizes;
103
+ hideCloseButton?: boolean;
104
+ class?: string;
105
+ children?: Snippet;
106
+ footer?: Snippet;
107
+ };
@@ -2,81 +2,39 @@ import { tv } from 'tailwind-variants';
2
2
  import { Size } from '../index.js';
3
3
  export const modal = tv({
4
4
  slots: {
5
- base: 'fixed inset-0 z-50 flex items-center justify-center overflow-y-auto',
6
- backdrop: 'fixed inset-0 transition-opacity bg-black/50',
7
- contentWrapper: 'absolute transform overflow-hidden transition-all px-2',
8
- content: 'bg-white overflow-hidden rounded-lg',
9
- header: 'px-4 py-3 flex items-center justify-between border-b border-default-200',
10
- body: 'px-3 py-2 max-h-[70dvh] overflow-y-auto',
11
- title: 'text-default-900 leading-6 text-base font-semibold',
12
- closeButton: 'text-default-400 hover:text-default-500 rounded-md cursor-pointer'
5
+ base: 'fixed inset-0 z-50 flex items-center justify-center p-4',
6
+ backdrop: 'fixed inset-0 bg-black/50 backdrop-blur-sm',
7
+ container: 'relative w-full flex flex-col bg-white rounded-xl shadow-2xl overflow-hidden',
8
+ header: 'px-6 py-4 border-b border-gray-200 flex items-center justify-between shrink-0',
9
+ body: 'flex-1 px-6 py-4 overflow-y-auto',
10
+ footer: 'px-6 py-4 border-t border-gray-200 bg-gray-50/80 shrink-0',
11
+ title: 'text-lg font-semibold text-gray-900',
12
+ description: 'text-sm text-gray-600 mt-1',
13
+ close: 'p-2 -mr-2 ml-4 rounded-lg text-gray-400 hover:text-gray-600 hover:bg-gray-100 transition-colors'
13
14
  },
14
15
  variants: {
15
- open: {
16
- true: {
17
- base: 'visible',
18
- backdrop: 'visible opacity-100',
19
- contentWrapper: 'visible scale-100 opacity-100'
20
- },
21
- false: {
22
- base: 'invisible',
23
- backdrop: 'invisible opacity-0',
24
- contentWrapper: 'invisible scale-95 opacity-0'
25
- }
26
- },
27
- position: {
28
- center: {
29
- contentWrapper: 'mx-auto'
30
- },
31
- top: {
32
- contentWrapper: 'mx-auto top-7'
33
- },
34
- bottom: {
35
- contentWrapper: 'mx-auto bottom-7'
36
- },
37
- left: {
38
- contentWrapper: 'left-7'
39
- },
40
- right: {
41
- contentWrapper: 'right-7'
42
- }
43
- },
44
16
  size: {
45
17
  [Size.XS]: {
46
- contentWrapper: 'w-full max-w-xs'
18
+ container: 'max-w-xs max-h-[80vh]'
47
19
  },
48
20
  [Size.SM]: {
49
- contentWrapper: 'w-full max-w-sm'
21
+ container: 'max-w-sm max-h-[85vh]'
50
22
  },
51
23
  [Size.BASE]: {
52
- contentWrapper: 'w-full max-w-md'
24
+ container: 'max-w-md max-h-[85vh]'
53
25
  },
54
26
  [Size.LG]: {
55
- contentWrapper: 'w-full max-w-lg'
27
+ container: 'max-w-lg max-h-[90vh]'
56
28
  },
57
29
  [Size.XL]: {
58
- contentWrapper: 'w-full max-w-xl'
30
+ container: 'max-w-xl max-h-[90vh]'
59
31
  },
60
32
  [Size.XXL]: {
61
- contentWrapper: 'w-full max-w-2xl'
62
- }
63
- },
64
- border: {
65
- none: {
66
- content: 'border-0'
67
- },
68
- default: {
69
- content: 'border border-default-200'
70
- },
71
- colored: {
72
- content: 'border'
33
+ container: 'max-w-2xl max-h-[90vh]'
73
34
  }
74
35
  }
75
36
  },
76
37
  defaultVariants: {
77
- open: false,
78
- position: 'center',
79
- size: 'base',
80
- color: 'default'
38
+ size: Size.BASE
81
39
  }
82
40
  });
@@ -0,0 +1,186 @@
1
+ <script lang="ts">
2
+ import { tv } from 'tailwind-variants';
3
+ import type { Snippet } from 'svelte';
4
+
5
+ export type PipelineStage = {
6
+ label: string;
7
+ count?: number | string;
8
+ color?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info';
9
+ active?: boolean;
10
+ };
11
+
12
+ interface Props {
13
+ stages: PipelineStage[];
14
+ class?: string;
15
+ size?: 'sm' | 'base' | 'lg';
16
+ equalWidth?: boolean;
17
+ children?: Snippet<[PipelineStage, number]>;
18
+ }
19
+
20
+ let {
21
+ stages = [],
22
+ class: className = '',
23
+ size = 'base',
24
+ equalWidth = true,
25
+ children
26
+ }: Props = $props();
27
+
28
+ const pipeline = tv({
29
+ slots: {
30
+ container: 'flex items-center gap-0 w-full',
31
+ stage: 'relative flex items-center justify-center transition-all duration-200',
32
+ content: 'relative z-10 flex flex-col items-center justify-center px-4 text-center',
33
+ label: 'font-medium text-gray-700 leading-tight',
34
+ count: 'font-bold text-gray-900',
35
+ chevron: 'absolute inset-0',
36
+ chevronPath: 'fill-current'
37
+ },
38
+ variants: {
39
+ size: {
40
+ sm: {
41
+ stage: 'h-16 min-w-[120px]',
42
+ label: 'text-xs',
43
+ count: 'text-lg mt-1',
44
+ content: 'px-3'
45
+ },
46
+ base: {
47
+ stage: 'h-20 min-w-[150px]',
48
+ label: 'text-sm',
49
+ count: 'text-2xl mt-1',
50
+ content: 'px-4'
51
+ },
52
+ lg: {
53
+ stage: 'h-24 min-w-[180px]',
54
+ label: 'text-base',
55
+ count: 'text-3xl mt-2',
56
+ content: 'px-5'
57
+ }
58
+ },
59
+ color: {
60
+ default: {
61
+ chevronPath: 'text-gray-100'
62
+ },
63
+ primary: {
64
+ chevronPath: 'text-blue-100',
65
+ label: 'text-blue-900',
66
+ count: 'text-blue-900'
67
+ },
68
+ success: {
69
+ chevronPath: 'text-green-100',
70
+ label: 'text-green-900',
71
+ count: 'text-green-900'
72
+ },
73
+ warning: {
74
+ chevronPath: 'text-orange-100',
75
+ label: 'text-orange-900',
76
+ count: 'text-orange-900'
77
+ },
78
+ danger: {
79
+ chevronPath: 'text-red-100',
80
+ label: 'text-red-900',
81
+ count: 'text-red-900'
82
+ },
83
+ info: {
84
+ chevronPath: 'text-purple-100',
85
+ label: 'text-purple-900',
86
+ count: 'text-purple-900'
87
+ }
88
+ },
89
+ active: {
90
+ true: {
91
+ stage: 'ring-2 ring-offset-2',
92
+ chevronPath: 'brightness-110'
93
+ }
94
+ },
95
+ equalWidth: {
96
+ true: {
97
+ stage: 'flex-1'
98
+ },
99
+ false: {
100
+ stage: ''
101
+ }
102
+ }
103
+ }
104
+ });
105
+
106
+ const styles = $derived(pipeline({ size, equalWidth }));
107
+
108
+ function getStageStyles(stage: PipelineStage) {
109
+ return pipeline({
110
+ size,
111
+ color: stage.color || 'default',
112
+ active: stage.active,
113
+ equalWidth
114
+ });
115
+ }
116
+ </script>
117
+
118
+ <div class="{styles.container()} {className}">
119
+ {#each stages as stage, index}
120
+ <div
121
+ class={getStageStyles(stage).stage()}
122
+ >
123
+ <!-- Chevron SVG Background -->
124
+ <svg
125
+ class={getStageStyles(stage).chevron()}
126
+ viewBox="0 0 200 80"
127
+ preserveAspectRatio="none"
128
+ xmlns="http://www.w3.org/2000/svg"
129
+ >
130
+ {#if index === 0}
131
+ <!-- First item - no tail -->
132
+ <path
133
+ d="M 0 0 L 160 0 L 200 40 L 160 80 L 0 80 Z"
134
+ class={getStageStyles(stage).chevronPath()}
135
+ />
136
+ <path
137
+ d="M 0 0 L 160 0 L 200 40 L 160 80 L 0 80 Z"
138
+ fill="none"
139
+ stroke={stage.active ? 'rgb(59 130 246)' : 'rgb(209 213 219)'}
140
+ stroke-width="2"
141
+ />
142
+ {:else if index === stages.length - 1}
143
+ <!-- Last item - no arrow -->
144
+ <path
145
+ d="M 0 0 L 200 0 L 200 80 L 0 80 L 40 40 Z"
146
+ class={getStageStyles(stage).chevronPath()}
147
+ />
148
+ <path
149
+ d="M 0 0 L 200 0 L 200 80 L 0 80 L 40 40 Z"
150
+ fill="none"
151
+ stroke={stage.active ? 'rgb(59 130 246)' : 'rgb(209 213 219)'}
152
+ stroke-width="2"
153
+ />
154
+ {:else}
155
+ <!-- Middle items - full chevron -->
156
+ <path
157
+ d="M 0 0 L 160 0 L 200 40 L 160 80 L 0 80 L 40 40 Z"
158
+ class={getStageStyles(stage).chevronPath()}
159
+ />
160
+ <path
161
+ d="M 0 0 L 160 0 L 200 40 L 160 80 L 0 80 L 40 40 Z"
162
+ fill="none"
163
+ stroke={stage.active ? 'rgb(59 130 246)' : 'rgb(209 213 219)'}
164
+ stroke-width="2"
165
+ />
166
+ {/if}
167
+ </svg>
168
+
169
+ <!-- Content -->
170
+ <div class={getStageStyles(stage).content()}>
171
+ {#if children}
172
+ {@render children(stage, index)}
173
+ {:else}
174
+ <span class={getStageStyles(stage).label()}>
175
+ {stage.label}
176
+ </span>
177
+ {#if stage.count !== undefined}
178
+ <span class={getStageStyles(stage).count()}>
179
+ {stage.count}
180
+ </span>
181
+ {/if}
182
+ {/if}
183
+ </div>
184
+ </div>
185
+ {/each}
186
+ </div>
@@ -0,0 +1,17 @@
1
+ import type { Snippet } from 'svelte';
2
+ export type PipelineStage = {
3
+ label: string;
4
+ count?: number | string;
5
+ color?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info';
6
+ active?: boolean;
7
+ };
8
+ interface Props {
9
+ stages: PipelineStage[];
10
+ class?: string;
11
+ size?: 'sm' | 'base' | 'lg';
12
+ equalWidth?: boolean;
13
+ children?: Snippet<[PipelineStage, number]>;
14
+ }
15
+ declare const Pipeline: import("svelte").Component<Props, {}, "">;
16
+ type Pipeline = ReturnType<typeof Pipeline>;
17
+ export default Pipeline;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makolabs/ripple",
3
- "version": "0.5.0",
3
+ "version": "1.0.2",
4
4
  "description": "Simple Svelte 5 powered component library ✨",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {