@makolabs/ripple 1.1.1 → 1.2.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/dist/index.d.ts CHANGED
@@ -48,7 +48,24 @@ export type AnchorHTMLProps = {
48
48
  href: string;
49
49
  } & Record<string, unknown>;
50
50
  export type ButtonProps = BaseButtonProps & (ButtonHTMLProps | AnchorHTMLProps);
51
- export type { ModalProps } from './modal/modal.js';
51
+ export type ModalProps = {
52
+ open?: boolean;
53
+ onclose?: () => void;
54
+ title?: string;
55
+ description?: string;
56
+ size?: VariantSizes;
57
+ hideCloseButton?: boolean;
58
+ class?: string;
59
+ contentclass?: string;
60
+ bodyclass?: string;
61
+ titleclass?: string;
62
+ headerclass?: string;
63
+ backdropclass?: string;
64
+ footerclass?: string;
65
+ children?: Snippet;
66
+ footer?: Snippet;
67
+ header?: Snippet;
68
+ };
52
69
  export type DrawerProps = {
53
70
  open?: boolean;
54
71
  onclose?: () => void;
@@ -159,6 +176,7 @@ export type MetricCardProps = {
159
176
  segments?: ProgressSegment[];
160
177
  class?: ClassValue;
161
178
  };
179
+ export type { RankedCardProps, RankedCardItem, RankedCardMetric } from './layout/card/ranked-card.js';
162
180
  export type DataRow = Record<string, any>;
163
181
  export type KeyType = keyof DataRow;
164
182
  export type StatusType = 'active' | 'inactive' | 'pending' | 'error' | 'default';
@@ -327,6 +345,7 @@ export { default as Dropdown } from './elements/dropdown/Dropdown.svelte';
327
345
  export { default as Select } from './elements/dropdown/Select.svelte';
328
346
  export { default as Card } from './layout/card/Card.svelte';
329
347
  export { default as MetricCard } from './layout/card/MetricCard.svelte';
348
+ export { default as RankedCard } from './layout/card/RankedCard.svelte';
330
349
  export { default as Alert } from './elements/alert/Alert.svelte';
331
350
  export type TabProps = {
332
351
  value: string;
@@ -376,6 +395,7 @@ export { drawer } from './drawer/drawer.js';
376
395
  export { selectTV } from './elements/dropdown/select.js';
377
396
  export { breadcrumbs } from './header/breadcrumbs.js';
378
397
  export { metricCard } from './layout/card/metric-card.js';
398
+ export { rankedCard } from './layout/card/ranked-card.js';
379
399
  export { activityList } from './layout/activity-list/activity-list.js';
380
400
  export type ChartColorKey = keyof typeof ChartColor;
381
401
  export type ChartColorValue = (typeof ChartColor)[ChartColorKey];
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ export { default as Select } from './elements/dropdown/Select.svelte';
30
30
  // Elements - Card
31
31
  export { default as Card } from './layout/card/Card.svelte';
32
32
  export { default as MetricCard } from './layout/card/MetricCard.svelte';
33
+ export { default as RankedCard } from './layout/card/RankedCard.svelte';
33
34
  // Elements - Alert
34
35
  export { default as Alert } from './elements/alert/Alert.svelte';
35
36
  export { default as Tab } from './layout/tabs/Tab.svelte';
@@ -55,6 +56,7 @@ export { drawer } from './drawer/drawer.js';
55
56
  export { selectTV } from './elements/dropdown/select.js';
56
57
  export { breadcrumbs } from './header/breadcrumbs.js';
57
58
  export { metricCard } from './layout/card/metric-card.js';
59
+ export { rankedCard } from './layout/card/ranked-card.js';
58
60
  export { activityList } from './layout/activity-list/activity-list.js';
59
61
  export { default as Chart } from './charts/Chart.svelte';
60
62
  export { default as FileUpload } from './elements/file-upload/FileUpload.svelte';
@@ -0,0 +1,56 @@
1
+ <script lang="ts">
2
+ import { cn } from '../../helper/cls.js';
3
+ import { rankedCard, type RankedCardProps } from './ranked-card.js';
4
+
5
+ let { items, columns = 3, class: className = '' }: RankedCardProps = $props();
6
+
7
+ const {
8
+ container,
9
+ card,
10
+ header,
11
+ rank,
12
+ title,
13
+ metricsContainer,
14
+ metricRow,
15
+ metricLabel,
16
+ metricValue,
17
+ actionContainer,
18
+ actionText,
19
+ actionLabel
20
+ } = $derived(rankedCard({ columns }));
21
+
22
+ const containerClass = $derived(cn(container(), className));
23
+ </script>
24
+
25
+ <div class={containerClass}>
26
+ {#each items as item}
27
+ <div class={card()}>
28
+ <!-- Header with Rank and Title -->
29
+ <div class={header()}>
30
+ <span class={rank()}>#{item.rank}</span>
31
+ <h4 class={title()}>{item.title}</h4>
32
+ </div>
33
+
34
+ <!-- Metrics -->
35
+ <div class={metricsContainer()}>
36
+ {#each item.metrics as metric}
37
+ <div class={metricRow()}>
38
+ <span class={metricLabel()}>{metric.label}:</span>
39
+ <span class={cn(metricValue(), metric.color || '')}>{metric.value}</span>
40
+ </div>
41
+ {/each}
42
+ </div>
43
+
44
+ <!-- Action/Recommendation -->
45
+ {#if item.action}
46
+ <div class={actionContainer()}>
47
+ <p class={actionText()}>
48
+ <span class={actionLabel()}>Action:</span>
49
+ <span class={item.action.color || ''}>{item.action.text}</span>
50
+ </p>
51
+ </div>
52
+ {/if}
53
+ </div>
54
+ {/each}
55
+ </div>
56
+
@@ -0,0 +1,4 @@
1
+ import { type RankedCardProps } from './ranked-card.js';
2
+ declare const RankedCard: import("svelte").Component<RankedCardProps, {}, "">;
3
+ type RankedCard = ReturnType<typeof RankedCard>;
4
+ export default RankedCard;
@@ -0,0 +1,104 @@
1
+ export declare const rankedCard: import("tailwind-variants").TVReturnType<{
2
+ columns: {
3
+ 1: {
4
+ container: string;
5
+ };
6
+ 2: {
7
+ container: string;
8
+ };
9
+ 3: {
10
+ container: string;
11
+ };
12
+ 4: {
13
+ container: string;
14
+ };
15
+ };
16
+ }, {
17
+ container: string;
18
+ card: string;
19
+ header: string;
20
+ rank: string;
21
+ title: string;
22
+ metricsContainer: string;
23
+ metricRow: string;
24
+ metricLabel: string;
25
+ metricValue: string;
26
+ actionContainer: string;
27
+ actionText: string;
28
+ actionLabel: string;
29
+ }, undefined, {
30
+ columns: {
31
+ 1: {
32
+ container: string;
33
+ };
34
+ 2: {
35
+ container: string;
36
+ };
37
+ 3: {
38
+ container: string;
39
+ };
40
+ 4: {
41
+ container: string;
42
+ };
43
+ };
44
+ }, {
45
+ container: string;
46
+ card: string;
47
+ header: string;
48
+ rank: string;
49
+ title: string;
50
+ metricsContainer: string;
51
+ metricRow: string;
52
+ metricLabel: string;
53
+ metricValue: string;
54
+ actionContainer: string;
55
+ actionText: string;
56
+ actionLabel: string;
57
+ }, import("tailwind-variants").TVReturnType<{
58
+ columns: {
59
+ 1: {
60
+ container: string;
61
+ };
62
+ 2: {
63
+ container: string;
64
+ };
65
+ 3: {
66
+ container: string;
67
+ };
68
+ 4: {
69
+ container: string;
70
+ };
71
+ };
72
+ }, {
73
+ container: string;
74
+ card: string;
75
+ header: string;
76
+ rank: string;
77
+ title: string;
78
+ metricsContainer: string;
79
+ metricRow: string;
80
+ metricLabel: string;
81
+ metricValue: string;
82
+ actionContainer: string;
83
+ actionText: string;
84
+ actionLabel: string;
85
+ }, undefined, unknown, unknown, undefined>>;
86
+ export type RankedCardMetric = {
87
+ label: string;
88
+ value: string;
89
+ color?: string;
90
+ };
91
+ export type RankedCardItem = {
92
+ rank: number;
93
+ title: string;
94
+ metrics: RankedCardMetric[];
95
+ action?: {
96
+ text: string;
97
+ color?: string;
98
+ };
99
+ };
100
+ export type RankedCardProps = {
101
+ items: RankedCardItem[];
102
+ columns?: 1 | 2 | 3 | 4;
103
+ class?: string;
104
+ };
@@ -0,0 +1,28 @@
1
+ import { tv } from 'tailwind-variants';
2
+ export const rankedCard = tv({
3
+ slots: {
4
+ container: 'grid gap-4',
5
+ card: 'border border-default-200 rounded-lg p-4',
6
+ header: 'flex items-center gap-2 mb-3',
7
+ rank: 'text-base font-bold text-default-400',
8
+ title: 'text-sm font-semibold text-default-900',
9
+ metricsContainer: 'space-y-2 mb-3',
10
+ metricRow: 'flex items-center justify-between text-sm',
11
+ metricLabel: 'text-default-600',
12
+ metricValue: 'font-medium text-default-900',
13
+ actionContainer: 'pt-3 border-t border-default-200',
14
+ actionText: 'text-xs text-default-600',
15
+ actionLabel: 'font-semibold text-default-700'
16
+ },
17
+ variants: {
18
+ columns: {
19
+ 1: { container: 'grid-cols-1' },
20
+ 2: { container: 'grid-cols-1 md:grid-cols-2' },
21
+ 3: { container: 'grid-cols-1 md:grid-cols-3' },
22
+ 4: { container: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4' }
23
+ }
24
+ },
25
+ defaultVariants: {
26
+ columns: 3
27
+ }
28
+ });
@@ -2,8 +2,9 @@
2
2
  import { fade, scale } from 'svelte/transition';
3
3
  import { quintOut } from 'svelte/easing';
4
4
  import { cn } from '../helper/cls.js';
5
- import { modal, type ModalProps } from './modal.js';
6
-
5
+ import { modal } from './modal.js';
6
+ import type { ModalProps } from '../index.js';
7
+
7
8
  let {
8
9
  open = $bindable(false),
9
10
  onclose = () => {},
@@ -18,7 +19,8 @@
18
19
  headerclass: headerClassName = '',
19
20
  backdropclass: backdropClassName = '',
20
21
  children,
21
- footer
22
+ footer,
23
+ header
22
24
  }: ModalProps = $props();
23
25
 
24
26
  const styles = $derived(modal({ size }));
@@ -28,26 +30,28 @@
28
30
  const containerClass = $derived(cn(styles.container(), contentClassName));
29
31
  const headerClass = $derived(cn(styles.header(), headerClassName));
30
32
  const titleClass = $derived(cn(styles.title(), titleClassName));
31
- const bodyClass = $derived(cn(
32
- 'flex-1 px-6 overflow-y-auto',
33
- // Adjust top padding based on header presence
34
- title || description ? 'py-4' : 'pt-6 pb-4',
35
- bodyClassName
36
- ));
33
+ const bodyClass = $derived(
34
+ cn(
35
+ 'flex-1 px-6 overflow-y-auto',
36
+ // Adjust top padding based on header presence
37
+ title || description ? 'py-4' : 'pt-6 pb-4',
38
+ bodyClassName
39
+ )
40
+ );
37
41
  const footerClass = $derived(cn(styles.footer(), ''));
38
42
  const closeClass = $derived(cn(styles.close(), ''));
39
43
  const descriptionClass = $derived(cn(styles.description(), ''));
40
-
44
+
41
45
  function handleBackdropClick() {
42
46
  onclose();
43
47
  }
44
-
48
+
45
49
  function handleKeydown(e: KeyboardEvent) {
46
50
  if (e.key === 'Escape' && open) {
47
51
  onclose();
48
52
  }
49
53
  }
50
-
54
+
51
55
  $effect(() => {
52
56
  if (open) {
53
57
  document.body.style.overflow = 'hidden';
@@ -56,7 +60,7 @@
56
60
  document.body.style.overflow = '';
57
61
  document.removeEventListener('keydown', handleKeydown);
58
62
  }
59
-
63
+
60
64
  return () => {
61
65
  document.body.style.overflow = '';
62
66
  document.removeEventListener('keydown', handleKeydown);
@@ -64,78 +68,76 @@
64
68
  });
65
69
  </script>
66
70
 
71
+ {#snippet predefinedHeader()}
72
+ {#if title || description}
73
+ <header class={headerClass}>
74
+ <div class="flex-1">
75
+ {#if title}
76
+ <h2 class={titleClass}>{title}</h2>
77
+ {/if}
78
+ </div>
79
+ {#if !hideCloseButton}
80
+ <button type="button" class={closeClass} onclick={onclose} aria-label="Close">
81
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
82
+ <path
83
+ d="M15 5L5 15M5 5L15 15"
84
+ stroke="currentColor"
85
+ stroke-width="2"
86
+ stroke-linecap="round"
87
+ />
88
+ </svg>
89
+ </button>
90
+ {/if}
91
+ </header>
92
+
93
+ {/if}
94
+
95
+ <!-- Close button only (positioned absolutely when no header) -->
96
+ {#if !title && !description && !hideCloseButton}
97
+ <button
98
+ type="button"
99
+ class="absolute top-4 right-4 z-10 rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600"
100
+ onclick={onclose}
101
+ aria-label="Close"
102
+ >
103
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
104
+ <path
105
+ d="M15 5L5 15M5 5L15 15"
106
+ stroke="currentColor"
107
+ stroke-width="2"
108
+ stroke-linecap="round"
109
+ />
110
+ </svg>
111
+ </button>
112
+ {/if}
113
+ {/snippet}
114
+
67
115
  {#if open}
68
116
  <div class={baseClass} role="dialog" aria-modal="true">
69
117
  <!-- Backdrop -->
70
- <div
118
+ <div
71
119
  class={backdropClass}
72
120
  onclick={handleBackdropClick}
73
121
  transition:fade={{ duration: 200 }}
74
- role="presentation"></div>
75
-
122
+ role="presentation"
123
+ ></div>
124
+
76
125
  <!-- Modal Container -->
77
- <div
78
- class={containerClass}
79
- transition:scale={{ duration: 200, easing: quintOut, start: 0.95 }}
80
- >
126
+ <div class={containerClass} transition:scale={{ duration: 200, easing: quintOut, start: 0.95 }}>
81
127
  <!-- Header -->
82
- {#if title || description}
83
- <header class={headerClass}>
84
- <div class="flex-1">
85
- {#if title}
86
- <h2 class={titleClass}>{title}</h2>
87
- {/if}
88
- {#if description}
89
- <p class={descriptionClass}>{description}</p>
90
- {/if}
91
- </div>
92
-
93
- {#if !hideCloseButton}
94
- <button
95
- type="button"
96
- class={closeClass}
97
- onclick={onclose}
98
- aria-label="Close"
99
- >
100
- <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
101
- <path
102
- d="M15 5L5 15M5 5L15 15"
103
- stroke="currentColor"
104
- stroke-width="2"
105
- stroke-linecap="round"
106
- />
107
- </svg>
108
- </button>
109
- {/if}
110
- </header>
111
- {/if}
112
-
113
- <!-- Close button only (positioned absolutely when no header) -->
114
- {#if !title && !description && !hideCloseButton}
115
- <button
116
- type="button"
117
- class="absolute top-4 right-4 p-2 rounded-lg text-gray-400 hover:text-gray-600 hover:bg-gray-100 transition-colors z-10"
118
- onclick={onclose}
119
- aria-label="Close"
120
- >
121
- <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
122
- <path
123
- d="M15 5L5 15M5 5L15 15"
124
- stroke="currentColor"
125
- stroke-width="2"
126
- stroke-linecap="round"
127
- />
128
- </svg>
129
- </button>
128
+ {#if header}
129
+ {@render header()}
130
+ {:else}
131
+ {@render predefinedHeader()}
130
132
  {/if}
131
-
133
+
132
134
  <!-- Body -->
133
135
  {#if children}
134
136
  <div class={bodyClass}>
135
137
  {@render children()}
136
138
  </div>
137
139
  {/if}
138
-
140
+
139
141
  <!-- Footer -->
140
142
  {#if footer}
141
143
  <footer class={footerClass}>
@@ -144,4 +146,4 @@
144
146
  {/if}
145
147
  </div>
146
148
  </div>
147
- {/if}
149
+ {/if}
@@ -1,4 +1,4 @@
1
- import { type ModalProps } from './modal.js';
1
+ import type { ModalProps } from '../index.js';
2
2
  declare const Modal: import("svelte").Component<ModalProps, {}, "open">;
3
3
  type Modal = ReturnType<typeof Modal>;
4
4
  export default Modal;
@@ -110,4 +110,5 @@ export type ModalProps = {
110
110
  footerclass?: string;
111
111
  children?: Snippet;
112
112
  footer?: Snippet;
113
+ header?: Snippet;
113
114
  };
@@ -15,6 +15,7 @@
15
15
  size?: 'sm' | 'base' | 'lg';
16
16
  equalWidth?: boolean;
17
17
  children?: Snippet<[PipelineStage, number]>;
18
+ beneathChildren?: Snippet<[PipelineStage, number]>;
18
19
  }
19
20
 
20
21
  let {
@@ -22,7 +23,8 @@
22
23
  class: className = '',
23
24
  size = 'base',
24
25
  equalWidth = true,
25
- children
26
+ children,
27
+ beneathChildren
26
28
  }: Props = $props();
27
29
 
28
30
  const pipeline = tv({
@@ -126,7 +128,7 @@
126
128
  {@const isLast = index === stages.length - 1}
127
129
  {@const chevronWidth = 20}
128
130
 
129
- <div class={getStageStyles(stage).stage()} style="{!isFirst ? `margin-left: -${chevronWidth}px; z-index: ${stages.length - index}` : `z-index: ${stages.length - index}`}">
131
+ <div class="flex flex-col {getStageStyles(stage).stage()}" style="{!isFirst ? `margin-left: -${chevronWidth}px; z-index: ${stages.length - index}` : `z-index: ${stages.length - index}`}">
130
132
  <!-- BACKGROUND LAYER (Border color - larger) -->
131
133
  <div
132
134
  class="{getStageStyles(stage).borderLayer()}"
@@ -153,6 +155,13 @@
153
155
  {/if}
154
156
  </div>
155
157
  </div>
158
+
159
+ <!-- Beneath Children - Rendered directly below this stage -->
160
+ {#if beneathChildren}
161
+ <div class="mt-4">
162
+ {@render beneathChildren(stage, index)}
163
+ </div>
164
+ {/if}
156
165
  </div>
157
166
  {/each}
158
167
  </div>
@@ -11,6 +11,7 @@ interface Props {
11
11
  size?: 'sm' | 'base' | 'lg';
12
12
  equalWidth?: boolean;
13
13
  children?: Snippet<[PipelineStage, number]>;
14
+ beneathChildren?: Snippet<[PipelineStage, number]>;
14
15
  }
15
16
  declare const Pipeline: import("svelte").Component<Props, {}, "">;
16
17
  type Pipeline = ReturnType<typeof Pipeline>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makolabs/ripple",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "Simple Svelte 5 powered component library ✨",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {