@makolabs/ripple 2.2.0 → 2.3.0

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 (38) hide show
  1. package/dist/charts/Chart.svelte +307 -53
  2. package/dist/charts/chart-types.d.ts +12 -0
  3. package/dist/elements/accordion/Accordion.svelte +27 -10
  4. package/dist/elements/accordion/accordion-types.d.ts +6 -0
  5. package/dist/elements/dropdown/Select.svelte +191 -63
  6. package/dist/elements/dropdown/dropdown-types.d.ts +13 -1
  7. package/dist/elements/dropdown/select.d.ts +15 -0
  8. package/dist/elements/dropdown/select.js +14 -8
  9. package/dist/forms/DateRange.svelte +16 -3
  10. package/dist/forms/Input.svelte +6 -4
  11. package/dist/forms/MarketSelector.svelte +7 -27
  12. package/dist/forms/NumberInput.svelte +9 -6
  13. package/dist/forms/SegmentedControl.svelte +5 -18
  14. package/dist/forms/Tags.svelte +1 -1
  15. package/dist/forms/form-types.d.ts +2 -31
  16. package/dist/forms/market/market-selector-types.d.ts +1 -21
  17. package/dist/forms/segmented-control.d.ts +4 -34
  18. package/dist/forms/segmented-control.js +19 -59
  19. package/dist/index.d.ts +3 -9
  20. package/dist/index.js +0 -6
  21. package/dist/variants.js +6 -6
  22. package/package.json +1 -1
  23. package/dist/elements/collapsible/Collapsible.svelte +0 -79
  24. package/dist/elements/collapsible/Collapsible.svelte.d.ts +0 -4
  25. package/dist/elements/collapsible/CollapsibleTestWrapper.svelte +0 -23
  26. package/dist/elements/collapsible/CollapsibleTestWrapper.svelte.d.ts +0 -8
  27. package/dist/elements/collapsible/collapsible-types.d.ts +0 -16
  28. package/dist/elements/collapsible/collapsible-types.js +0 -1
  29. package/dist/elements/combobox/Combobox.svelte +0 -274
  30. package/dist/elements/combobox/Combobox.svelte.d.ts +0 -25
  31. package/dist/elements/combobox/ComboboxTestWrapper.svelte +0 -38
  32. package/dist/elements/combobox/ComboboxTestWrapper.svelte.d.ts +0 -4
  33. package/dist/elements/combobox/combobox-types.d.ts +0 -39
  34. package/dist/elements/combobox/combobox-types.js +0 -1
  35. package/dist/forms/RadioInputs.svelte +0 -73
  36. package/dist/forms/RadioInputs.svelte.d.ts +0 -4
  37. package/dist/forms/RadioPill.svelte +0 -66
  38. package/dist/forms/RadioPill.svelte.d.ts +0 -4
@@ -19,7 +19,10 @@
19
19
  type ChartProps,
20
20
  type ChartColorString,
21
21
  type ChartColors,
22
- type SeriesConfig
22
+ type ChartThreshold,
23
+ type ChartAnnotation,
24
+ type SeriesConfig,
25
+ type TooltipConfig
23
26
  } from '../index.js';
24
27
  import { defaultChartColors } from '../variants.js';
25
28
 
@@ -71,13 +74,12 @@
71
74
  orient: 'horizontal',
72
75
  ...(config.legend || {})
73
76
  });
74
- const tooltip = $derived(
75
- config.tooltip || {
76
- show: true,
77
- trigger: 'axis',
78
- formatter: undefined
79
- }
80
- );
77
+ const tooltip = $derived({
78
+ show: true,
79
+ trigger: 'axis' as const,
80
+ formatter: undefined as TooltipConfig['formatter'],
81
+ ...(config.tooltip || {})
82
+ });
81
83
  const toolbox = $derived(
82
84
  config.toolbox || {
83
85
  show: false,
@@ -113,35 +115,106 @@
113
115
  return color ? chartColors[color as keyof ChartColors] || color : undefined;
114
116
  }
115
117
 
116
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
117
- function getAreaStyle(seriesConfig: SeriesConfig<any>) {
118
- if (!seriesConfig.color) return undefined;
119
- if (!seriesConfig.showArea) return undefined;
118
+ function hexToRgb(hex: string): [number, number, number] {
119
+ const h = hex.replace('#', '');
120
+ return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
121
+ }
122
+
123
+ function rgbToHex(r: number, g: number, b: number): string {
124
+ return (
125
+ '#' +
126
+ [r, g, b]
127
+ .map((v) =>
128
+ Math.max(0, Math.min(255, Math.round(v)))
129
+ .toString(16)
130
+ .padStart(2, '0')
131
+ )
132
+ .join('')
133
+ );
134
+ }
135
+
136
+ function lighten(hex: string, amount: number): string {
137
+ const [r, g, b] = hexToRgb(hex);
138
+ return rgbToHex(r + (255 - r) * amount, g + (255 - g) * amount, b + (255 - b) * amount);
139
+ }
140
+
141
+ function darken(hex: string, amount: number): string {
142
+ const [r, g, b] = hexToRgb(hex);
143
+ return rgbToHex(r * (1 - amount), g * (1 - amount), b * (1 - amount));
144
+ }
120
145
 
121
- const baseLinearAreaStyle = {
146
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
147
+ function barGradient(baseHex: string, stacked: boolean): any {
148
+ if (stacked) {
149
+ // Horizontal sheen — same light direction for all segments
150
+ return {
151
+ type: 'linear',
152
+ x: 0,
153
+ y: 0,
154
+ x2: 1,
155
+ y2: 0,
156
+ colorStops: [
157
+ { offset: 0, color: baseHex },
158
+ { offset: 0.5, color: lighten(baseHex, 0.1) },
159
+ { offset: 1, color: baseHex }
160
+ ]
161
+ };
162
+ }
163
+ // Standalone bars — top-to-bottom depth
164
+ return {
122
165
  type: 'linear',
123
166
  x: 0,
124
167
  y: 0,
125
168
  x2: 0,
126
- y2: 1
169
+ y2: 1,
170
+ colorStops: [
171
+ { offset: 0, color: lighten(baseHex, 0.2) },
172
+ { offset: 0.6, color: baseHex },
173
+ { offset: 1, color: darken(baseHex, 0.15) }
174
+ ]
175
+ };
176
+ }
177
+
178
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
179
+ function lineGradient(baseHex: string): any {
180
+ return {
181
+ type: 'linear',
182
+ x: 0,
183
+ y: 0,
184
+ x2: 1,
185
+ y2: 0,
186
+ colorStops: [
187
+ { offset: 0, color: darken(baseHex, 0.05) },
188
+ { offset: 0.5, color: baseHex },
189
+ { offset: 1, color: lighten(baseHex, 0.1) }
190
+ ]
127
191
  };
128
- const color = getColor(seriesConfig.color);
129
- const opacity = seriesConfig.areaOpacity || 0.2;
130
- const opacityHex = Math.floor(opacity * 255)
192
+ }
193
+
194
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
195
+ function getAreaStyle(seriesConfig: SeriesConfig<any>) {
196
+ if (!seriesConfig.color) return undefined;
197
+ if (!seriesConfig.showArea) return undefined;
198
+
199
+ const color = getColor(seriesConfig.color) ?? '#6366f1';
200
+ const opacity = seriesConfig.areaOpacity || 0.25;
201
+ const opHigh = Math.floor(opacity * 255)
202
+ .toString(16)
203
+ .padStart(2, '0');
204
+ const opMid = Math.floor(opacity * 0.4 * 255)
131
205
  .toString(16)
132
206
  .padStart(2, '0');
133
207
  return {
134
208
  color: {
135
- ...baseLinearAreaStyle,
209
+ type: 'linear',
210
+ x: 0,
211
+ y: 0,
212
+ x2: 0,
213
+ y2: 1,
136
214
  colorStops: [
137
- {
138
- offset: 0,
139
- color: color + opacityHex
140
- },
141
- {
142
- offset: 1,
143
- color: color + '00'
144
- }
215
+ { offset: 0, color: color + opHigh },
216
+ { offset: 0.6, color: color + opMid },
217
+ { offset: 1, color: color + '00' }
145
218
  ]
146
219
  }
147
220
  };
@@ -182,16 +255,128 @@
182
255
  return String(value);
183
256
  }
184
257
 
258
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
259
+ function buildMarkLineFromThresholds(axis: 'x' | 'y'): any {
260
+ const axisCfgs = axis === 'x' ? [xAxis] : yAxis;
261
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
262
+ const lines: any[] = [];
263
+ let lineIdx = 0;
264
+
265
+ for (const cfg of axisCfgs) {
266
+ if (!cfg.thresholds) continue;
267
+ for (const t of cfg.thresholds) {
268
+ // Alternate label positions to reduce overlap
269
+ const pos =
270
+ axis === 'x' ? 'insideEndTop' : lineIdx % 2 === 0 ? 'insideStartTop' : 'insideEndTop';
271
+
272
+ if (t === 'average') {
273
+ lines.push({
274
+ type: 'average',
275
+ name: 'Avg',
276
+ label: { formatter: 'Avg', position: pos, fontSize: 11 },
277
+ lineStyle: { type: 'dashed', color: '#6b7280', width: 1 }
278
+ });
279
+ } else {
280
+ const resolved = t as ChartThreshold;
281
+ const color = getColor(resolved.color) ?? '#6b7280';
282
+ const entry = axis === 'x' ? { xAxis: resolved.value } : { yAxis: resolved.value };
283
+ lines.push({
284
+ ...entry,
285
+ label: resolved.label
286
+ ? {
287
+ formatter: resolved.label,
288
+ position: pos,
289
+ fontSize: 11,
290
+ color,
291
+ ...(axis === 'x' && { rotate: 0 })
292
+ }
293
+ : { show: false },
294
+ lineStyle: {
295
+ type: resolved.style ?? 'dashed',
296
+ color,
297
+ width: 2
298
+ }
299
+ });
300
+ }
301
+ lineIdx++;
302
+ }
303
+ }
304
+ if (lines.length === 0) return undefined;
305
+ return { silent: true, symbol: 'none', data: lines };
306
+ }
307
+
308
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
309
+ function buildMarkPointsFromData(seriesDataKey: string | number | symbol): any {
310
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
311
+ const points: any[] = [];
312
+ data.forEach((item) => {
313
+ const annotation = item.annotation as ChartAnnotation | undefined;
314
+ if (!annotation) return;
315
+ const xVal = String(item[xAxis.dataKey]);
316
+ const yVal = item[seriesDataKey as keyof typeof item];
317
+ if (yVal === undefined) return;
318
+ const color = getColor(annotation.color) ?? undefined;
319
+ // Use category name string for x — matches the xAxis.data categories
320
+ points.push({
321
+ coord: [xVal, yVal],
322
+ symbol: 'circle',
323
+ symbolSize: 12,
324
+ ...(color && { itemStyle: { color } }),
325
+ label: annotation.label
326
+ ? {
327
+ formatter: annotation.label,
328
+ position: 'top',
329
+ distance: 8,
330
+ color: color ?? '#374151',
331
+ fontSize: 11,
332
+ fontWeight: 500
333
+ }
334
+ : { show: false }
335
+ });
336
+ });
337
+ if (points.length === 0) return undefined;
338
+ return { data: points };
339
+ }
340
+
341
+ function mergeMarkLine(
342
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
343
+ a: any | undefined,
344
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
345
+ b: any | undefined
346
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
347
+ ): any | undefined {
348
+ if (!a && !b) return undefined;
349
+ if (!a) return b;
350
+ if (!b) return a;
351
+ return { ...a, data: [...(a.data ?? []), ...(b.data ?? [])] };
352
+ }
353
+
185
354
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
186
355
  function configureSeries(): any[] {
187
356
  return series.map((seriesConfig) => {
188
357
  const seriesType = seriesConfig.type || 'line';
189
358
 
190
359
  if (seriesType === 'pie') {
191
- const pieData = data.map((item) => ({
192
- value: item[seriesConfig.dataKey],
193
- name: item[xAxis?.dataKey] || item.label
194
- }));
360
+ const sliceColors = getSeriesColors(data.length);
361
+ const pieData = data.map((item, i) => {
362
+ const baseHex = getColor(seriesConfig.color) ?? sliceColors[i % sliceColors.length];
363
+ return {
364
+ value: item[seriesConfig.dataKey],
365
+ name: item[xAxis?.dataKey] || item.label,
366
+ itemStyle: {
367
+ color: {
368
+ type: 'radial',
369
+ x: 0.4,
370
+ y: 0.35,
371
+ r: 0.8,
372
+ colorStops: [
373
+ { offset: 0, color: lighten(baseHex, 0.15) },
374
+ { offset: 1, color: darken(baseHex, 0.1) }
375
+ ]
376
+ }
377
+ }
378
+ };
379
+ });
195
380
 
196
381
  return {
197
382
  name: seriesConfig.name || seriesConfig.dataKey,
@@ -201,19 +386,24 @@
201
386
  itemStyle: {
202
387
  borderRadius: 4,
203
388
  borderWidth: 2,
204
- borderColor: '#fff',
205
- color: getColor(seriesConfig.color)
389
+ borderColor: '#fff'
206
390
  },
207
391
  label: {
208
- show: seriesConfig.showLabel !== undefined ? seriesConfig.showLabel : true
392
+ show: seriesConfig.showLabel !== undefined ? seriesConfig.showLabel : true,
393
+ color: '#374151',
394
+ fontSize: 12
209
395
  },
210
396
  labelLine: {
211
- show: seriesConfig.showLabel !== undefined ? seriesConfig.showLabel : true
397
+ show: seriesConfig.showLabel !== undefined ? seriesConfig.showLabel : true,
398
+ lineStyle: { color: '#d1d5db' }
212
399
  },
213
400
  data: pieData,
214
401
  animation: true,
215
402
  animationDuration: AnimationDuration,
216
- emphasis: seriesConfig.emphasis || { focus: 'series' }
403
+ emphasis: seriesConfig.emphasis || {
404
+ focus: 'self',
405
+ itemStyle: { shadowBlur: 12, shadowColor: 'rgba(0,0,0,0.15)' }
406
+ }
217
407
  };
218
408
  }
219
409
 
@@ -229,6 +419,13 @@
229
419
  (s) => s.type === 'bar' || s.type === 'horizontal-bar'
230
420
  ).length;
231
421
 
422
+ // Thresholds attach to the first non-pie series; annotations attach per-series
423
+ const isFirstNonPie = series.filter((s) => s.type !== 'pie').indexOf(seriesConfig) === 0;
424
+ const xMarkLine = isFirstNonPie ? buildMarkLineFromThresholds('x') : undefined;
425
+ const yMarkLine = isFirstNonPie ? buildMarkLineFromThresholds('y') : undefined;
426
+ const axisMarkLine = mergeMarkLine(xMarkLine, yMarkLine);
427
+ const pointAnnotations = buildMarkPointsFromData(seriesConfig.dataKey);
428
+
232
429
  return {
233
430
  name: seriesConfig.name || seriesConfig.dataKey,
234
431
  type: actualType,
@@ -236,17 +433,30 @@
236
433
  data: seriesData,
237
434
  stack: seriesConfig.stack,
238
435
  color: getColor(seriesConfig.color),
239
- emphasis: seriesConfig.emphasis || { focus: 'series' },
436
+ emphasis: seriesConfig.emphasis || {
437
+ focus: 'series',
438
+ blurScope: 'global'
439
+ },
440
+ blur: { itemStyle: { opacity: 0.25 }, lineStyle: { opacity: 0.25 } },
240
441
  animation: true,
241
442
  animationDuration: AnimationDuration,
443
+ ...(axisMarkLine && { markLine: axisMarkLine }),
444
+ ...(pointAnnotations && { markPoint: pointAnnotations }),
242
445
 
243
446
  ...(actualType === 'line' && {
244
447
  smooth: seriesConfig.smooth !== undefined ? seriesConfig.smooth : true,
245
448
  showSymbol: seriesConfig.showSymbol !== undefined ? seriesConfig.showSymbol : true,
246
- symbolSize: 6,
247
- lineStyle: {
248
- width: seriesConfig.lineWidth || 2,
449
+ symbolSize: 8,
450
+ itemStyle: {
249
451
  color: getColor(seriesConfig.color),
452
+ borderColor: '#fff',
453
+ borderWidth: 2,
454
+ shadowColor: (getColor(seriesConfig.color) ?? '#6366f1') + '40',
455
+ shadowBlur: 6
456
+ },
457
+ lineStyle: {
458
+ width: seriesConfig.lineWidth || 2.5,
459
+ color: lineGradient(getColor(seriesConfig.color) ?? '#6366f1'),
250
460
  type: seriesConfig.lineStyle?.type || 'solid'
251
461
  },
252
462
  areaStyle: getAreaStyle(seriesConfig)
@@ -264,11 +474,18 @@
264
474
  barCategoryGap: '20%',
265
475
  barGap: '10%',
266
476
  color: getColor(seriesConfig.color),
267
- itemStyle: {
268
- color: getColor(seriesConfig.color),
269
- borderRadius: [0, 0, 0, 0],
270
- opacity: seriesConfig.opacity || 1
271
- }
477
+ itemStyle: (() => {
478
+ const baseHex = getColor(seriesConfig.color) ?? '#6366f1';
479
+ const isStacked = !!seriesConfig.stack;
480
+ const isLastInStack = isStacked
481
+ ? series.filter((s) => s.stack === seriesConfig.stack).at(-1) === seriesConfig
482
+ : true;
483
+ return {
484
+ color: barGradient(baseHex, isStacked),
485
+ borderRadius: isLastInStack ? [4, 4, 0, 0] : [0, 0, 0, 0],
486
+ opacity: seriesConfig.opacity ?? 1
487
+ };
488
+ })()
272
489
  }),
273
490
 
274
491
  label: {
@@ -384,19 +601,41 @@
384
601
  // Regular chart configuration
385
602
  const categories = data.map((item) => item[xAxis.dataKey]);
386
603
 
604
+ const systemFont =
605
+ "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif";
387
606
  const options = {
388
607
  color: colorPalette,
608
+ textStyle: { fontFamily: systemFont },
389
609
 
390
610
  tooltip: {
391
611
  show: tooltip.show,
392
612
  trigger: tooltip.trigger,
393
- formatter: tooltip.formatter || defaultFormatter
613
+ formatter: tooltip.formatter || defaultFormatter,
614
+ backgroundColor: 'rgba(255, 255, 255, 0.96)',
615
+ borderColor: 'transparent',
616
+ borderRadius: 8,
617
+ padding: [10, 14],
618
+ textStyle: { color: '#374151', fontSize: 12 },
619
+ extraCssText: 'box-shadow: 0 4px 12px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.06);',
620
+ axisPointer: {
621
+ type: 'line',
622
+ lineStyle: {
623
+ color: '#d1d5db',
624
+ width: 1,
625
+ type: 'solid'
626
+ },
627
+ crossStyle: { color: '#d1d5db' }
628
+ }
394
629
  },
395
630
 
396
631
  legend: {
397
632
  show: legend.show,
398
633
  type: 'scroll',
399
634
  orient: legend.orient,
635
+ textStyle: { color: '#6b7280', fontSize: 12 },
636
+ itemWidth: 14,
637
+ itemHeight: 8,
638
+ itemGap: 16,
400
639
  ...(legend.position === 'top' && { top: 0, left: 'center' }),
401
640
  ...(legend.position === 'bottom' && { bottom: 0, left: 'center' }),
402
641
  ...(legend.position === 'left' && { left: 0, top: 'middle' }),
@@ -404,17 +643,22 @@
404
643
  },
405
644
 
406
645
  grid: {
407
- top: legend.position === 'top' ? '15%' : grid.top || '10%',
408
- right: grid.right || '3%',
646
+ top: legend.position === 'top' ? '18%' : grid.top || '12%',
647
+ right: grid.right || '4%',
409
648
  bottom: legend.position === 'bottom' ? '15%' : grid.bottom || '10%',
410
- left: grid.left || '3%',
411
- containLabel: grid.containLabel !== undefined ? grid.containLabel : true
649
+ left: grid.left || '4%',
650
+ containLabel: grid.containLabel !== undefined ? grid.containLabel : true,
651
+ backgroundColor: 'rgba(249, 250, 251, 0.5)',
652
+ show: true,
653
+ borderColor: 'transparent'
412
654
  },
413
655
 
414
656
  xAxis: {
415
657
  type: 'category',
416
658
  data: categories,
417
659
  axisLabel: {
660
+ color: '#6b7280',
661
+ fontSize: 12,
418
662
  ...(xAxis.tick && {
419
663
  fontSize: xAxis.tick.fontSize,
420
664
  rotate: xAxis.tick.rotate,
@@ -422,6 +666,7 @@
422
666
  }),
423
667
  formatter: xAxis.format || '{value}'
424
668
  },
669
+ axisTick: { show: false },
425
670
  boundaryGap: series.every((s) => s.type === 'line')
426
671
  ? false
427
672
  : series.some((s) => s.type === 'bar' || s.type === 'horizontal-bar')
@@ -430,11 +675,14 @@
430
675
  name: xAxis.label,
431
676
  nameLocation: 'middle',
432
677
  nameGap: 30,
678
+ nameTextStyle: { color: '#6b7280', fontSize: 12 },
433
679
  splitLine: {
434
- show: grid.vertical || false
680
+ show: grid.vertical || false,
681
+ lineStyle: { color: '#f3f4f6' }
435
682
  },
436
683
  axisLine: xAxis.axisLine || {
437
- show: false
684
+ show: true,
685
+ lineStyle: { color: '#e5e7eb', width: 1 }
438
686
  }
439
687
  },
440
688
 
@@ -444,18 +692,24 @@
444
692
  nameLocation: 'middle',
445
693
  nameGap: 50,
446
694
  nameRotate: 90,
695
+ nameTextStyle: { color: '#6b7280', fontSize: 12 },
447
696
  position: axis.position || (index === 0 ? 'left' : 'right'),
448
697
  min: axis.min,
449
698
  max: axis.max,
450
699
  axisLine: axis.axisLine || {
451
- show: false
700
+ show: true,
701
+ lineStyle: { color: '#e5e7eb', width: 1 }
452
702
  },
703
+ axisTick: { show: false },
453
704
  axisLabel: {
705
+ color: '#9ca3af',
706
+ fontSize: 11,
454
707
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
455
708
  formatter: (value: any) => formatValue(value, axis.format, axis.unit)
456
709
  },
457
710
  splitLine: {
458
- show: index === 0 ? grid.horizontal || true : false
711
+ show: index === 0 ? grid.horizontal || true : false,
712
+ lineStyle: { color: '#f3f4f6', type: 'dashed' }
459
713
  }
460
714
  })),
461
715
 
@@ -8,6 +8,16 @@ export type ChartColors = {
8
8
  };
9
9
  export type ChartType = 'line' | 'bar' | 'horizontal-bar' | 'pie' | 'stacked-bar';
10
10
  export type ChartColorString = `#${string}` | keyof ChartColors;
11
+ export type ChartThreshold = {
12
+ value: number | string;
13
+ label?: string;
14
+ color?: ChartColorString;
15
+ style?: 'solid' | 'dashed' | 'dotted';
16
+ };
17
+ export type ChartAnnotation = {
18
+ label?: string;
19
+ color?: ChartColorString;
20
+ };
11
21
  export type XAxisConfig<T> = {
12
22
  dataKey: keyof T;
13
23
  label?: string;
@@ -25,6 +35,7 @@ export type XAxisConfig<T> = {
25
35
  type?: 'solid' | 'dashed' | 'dotted';
26
36
  };
27
37
  };
38
+ thresholds?: Array<ChartThreshold | 'average'>;
28
39
  };
29
40
  export type YAxisConfig<T> = {
30
41
  dataKey: keyof T;
@@ -42,6 +53,7 @@ export type YAxisConfig<T> = {
42
53
  type?: 'solid' | 'dashed' | 'dotted';
43
54
  };
44
55
  };
56
+ thresholds?: Array<ChartThreshold | 'average'>;
45
57
  };
46
58
  export interface SeriesConfig<T> {
47
59
  dataKey: keyof T;
@@ -12,16 +12,20 @@
12
12
  bordered,
13
13
  color,
14
14
  open = $bindable(false),
15
+ disabled = false,
15
16
  onexpand,
16
17
  oncollapse,
18
+ ontoggle,
17
19
  class: className,
18
20
  titleclass,
19
21
  titleClass = titleclass ?? '',
20
22
  bodyclass,
21
23
  bodyClass = bodyclass ?? '',
24
+ contentClass,
22
25
  headerclass,
23
26
  headerClass = headerclass ?? '',
24
27
  summary,
28
+ header,
25
29
  children,
26
30
  icon: Icon,
27
31
  testId
@@ -47,13 +51,11 @@
47
51
  );
48
52
 
49
53
  function handleClick() {
50
- if (!children) return; // Don't toggle if no children
54
+ if (!children || disabled) return;
51
55
  open = !open;
52
- if (open && onexpand) {
53
- onexpand();
54
- } else if (!open && oncollapse) {
55
- oncollapse();
56
- }
56
+ ontoggle?.(open);
57
+ if (open) onexpand?.();
58
+ else oncollapse?.();
57
59
  }
58
60
 
59
61
  const baseClasses = $derived(cn(styles.base(), className));
@@ -62,13 +64,24 @@
62
64
  <div class={baseClasses}>
63
65
  {#if children}
64
66
  <button
65
- class={cn(styles.header(), 'flex gap-3', headerClass)}
67
+ class={cn(
68
+ styles.header(),
69
+ 'flex gap-3',
70
+ headerClass,
71
+ disabled && 'cursor-not-allowed opacity-50'
72
+ )}
66
73
  aria-expanded={open}
67
74
  aria-controls={id}
75
+ aria-disabled={disabled ? 'true' : undefined}
76
+ {disabled}
68
77
  onclick={handleClick}
69
78
  data-testid={buildTestId('accordion', 'trigger', testId)}
70
79
  >
71
- {@render accordionHeaderContent()}
80
+ {#if header}
81
+ {@render header({ open })}
82
+ {:else}
83
+ {@render accordionHeaderContent()}
84
+ {/if}
72
85
  <div class="flex items-center">
73
86
  <svg
74
87
  xmlns="http://www.w3.org/2000/svg"
@@ -87,7 +100,7 @@
87
100
  </button>
88
101
  {#if open}
89
102
  <div
90
- class={cn(styles.body(), bodyClass)}
103
+ class={cn(styles.body(), bodyClass, contentClass)}
91
104
  {id}
92
105
  data-testid={buildTestId('accordion', 'content', testId)}
93
106
  >
@@ -96,7 +109,11 @@
96
109
  {/if}
97
110
  {:else}
98
111
  <div class={cn(styles.header(), 'flex gap-3', headerClass)}>
99
- {@render accordionHeaderContent()}
112
+ {#if header}
113
+ {@render header({ open })}
114
+ {:else}
115
+ {@render accordionHeaderContent()}
116
+ {/if}
100
117
  </div>
101
118
  {/if}
102
119
  </div>
@@ -8,7 +8,11 @@ export type AccordionProps = {
8
8
  description?: string;
9
9
  children?: Snippet;
10
10
  summary?: Snippet;
11
+ header?: Snippet<[{
12
+ open: boolean;
13
+ }]>;
11
14
  open?: boolean;
15
+ disabled?: boolean;
12
16
  color?: VariantColors;
13
17
  class?: ClassValue;
14
18
  /** @deprecated Use titleClass instead */
@@ -17,6 +21,7 @@ export type AccordionProps = {
17
21
  /** @deprecated Use bodyClass instead */
18
22
  bodyclass?: ClassValue;
19
23
  bodyClass?: ClassValue;
24
+ contentClass?: ClassValue;
20
25
  /** @deprecated Use headerClass instead */
21
26
  headerclass?: ClassValue;
22
27
  headerClass?: ClassValue;
@@ -25,5 +30,6 @@ export type AccordionProps = {
25
30
  bordered?: boolean;
26
31
  onexpand?: () => void;
27
32
  oncollapse?: () => void;
33
+ ontoggle?: (open: boolean) => void;
28
34
  testId?: string;
29
35
  };