@makolabs/ripple 1.9.2 → 1.10.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.
@@ -14,8 +14,12 @@
14
14
  name = '',
15
15
  showFlags = true,
16
16
  label = '',
17
+ variant = undefined,
18
+ collapsed = false,
17
19
  appearance = 'surface',
18
20
  orientation = 'horizontal',
21
+ labelLayout = undefined,
22
+ labelClass = undefined,
19
23
  color = Color.PRIMARY,
20
24
  size = Size.BASE,
21
25
  compact = false,
@@ -25,6 +29,24 @@
25
29
  testId
26
30
  }: MarketSelectorProps = $props();
27
31
 
32
+ const resolvedAppearance = $derived.by(() => {
33
+ if (variant === 'default') return 'clarkDefault';
34
+ if (variant === 'sidebar') return 'clarkSidebar';
35
+ return appearance;
36
+ });
37
+
38
+ const resolvedLabelLayout = $derived(labelLayout ?? (variant !== undefined ? 'inline' : 'above'));
39
+
40
+ const resolvedOrientation = $derived(
41
+ variant === 'sidebar' && collapsed ? 'vertical' : orientation
42
+ );
43
+
44
+ const resolvedCompact = $derived(variant === 'sidebar' && collapsed ? true : compact);
45
+
46
+ const resolvedLabelClass = $derived(
47
+ labelClass ?? (variant === 'sidebar' && collapsed && label ? 'sr-only' : undefined)
48
+ );
49
+
28
50
  const options = $derived.by((): SegmentedOption[] => {
29
51
  return markets.map((code) => ({
30
52
  value: code,
@@ -55,11 +77,13 @@
55
77
  {options}
56
78
  {name}
57
79
  {label}
58
- {appearance}
59
- {orientation}
80
+ appearance={resolvedAppearance}
81
+ orientation={resolvedOrientation}
82
+ labelLayout={resolvedLabelLayout}
83
+ labelClass={resolvedLabelClass}
60
84
  {color}
61
85
  {size}
62
- {compact}
86
+ compact={resolvedCompact}
63
87
  {disabled}
64
88
  {testId}
65
89
  onchange={handleChange}
@@ -3,7 +3,7 @@
3
3
  import { cn } from '../helper/cls.js';
4
4
  import { buildTestId } from '../helper/testid.js';
5
5
  import { Color, Size } from '../variants.js';
6
- import { segmentedTrack, segmentClasses } from './segmented-control.js';
6
+ import { segmentedLabelClasses, segmentedTrack, segmentClasses } from './segmented-control.js';
7
7
  import type { SegmentedControlProps, VariantColors, VariantSizes } from '../index.js';
8
8
 
9
9
  const groupId = `seg-${Math.random().toString(36).slice(2, 11)}`;
@@ -19,6 +19,8 @@
19
19
  color = Color.PRIMARY,
20
20
  size = Size.BASE,
21
21
  compact = false,
22
+ labelLayout = 'above',
23
+ labelClass = '',
22
24
  disabled = false,
23
25
  errors = [],
24
26
  onchange = undefined,
@@ -27,13 +29,30 @@
27
29
 
28
30
  const hasError = $derived(errors && errors.length > 0);
29
31
 
32
+ const clarkCollapsed = $derived(
33
+ compact && appearance === 'clarkSidebar' && orientation === 'vertical'
34
+ );
35
+
30
36
  const trackClass = $derived(
31
37
  cn(
32
- segmentedTrack({ appearance, orientation }),
33
- orientation === 'vertical' && compact && 'items-stretch'
38
+ segmentedTrack({
39
+ appearance,
40
+ orientation,
41
+ clarkCollapsed
42
+ }),
43
+ orientation === 'vertical' && compact && appearance !== 'clarkSidebar' && 'items-stretch'
34
44
  )
35
45
  );
36
46
 
47
+ const rootClass = $derived(
48
+ cn(
49
+ labelLayout === 'inline' ? 'flex flex-row items-center gap-2' : 'flex flex-col gap-2',
50
+ className
51
+ )
52
+ );
53
+
54
+ const labelTextClass = $derived(cn(segmentedLabelClasses(appearance, !!hasError), labelClass));
55
+
37
56
  async function handleSelect(optionValue: string, optionDisabled: boolean | undefined) {
38
57
  if (disabled || optionDisabled) return;
39
58
  if (optionValue === value) return;
@@ -91,20 +110,19 @@
91
110
  }
92
111
  }
93
112
  }
113
+
114
+ const isClark = $derived(appearance === 'clarkDefault' || appearance === 'clarkSidebar');
94
115
  </script>
95
116
 
96
- <div class={cn('flex flex-col gap-2', className)}>
97
- {#if hasError}
98
- <div class="text-danger-500 text-sm">{errors[0]}</div>
99
- {/if}
117
+ <div class={rootClass}>
100
118
  {#if label}
101
- <span
102
- id="{groupId}-label"
103
- class={cn('text-sm font-medium', hasError ? 'text-danger-500' : 'text-default-700')}
104
- >
119
+ <span id="{groupId}-label" class={labelTextClass}>
105
120
  {label}
106
121
  </span>
107
122
  {/if}
123
+ {#if hasError}
124
+ <div class="text-danger-500 text-sm">{errors[0]}</div>
125
+ {/if}
108
126
 
109
127
  <div
110
128
  class={trackClass}
@@ -134,7 +152,8 @@
134
152
  size: size as VariantSizes,
135
153
  isFirst,
136
154
  isLast,
137
- orientation
155
+ orientation,
156
+ clarkCollapsed
138
157
  })}
139
158
  data-testid={buildTestId('segmented', 'item', testId, index)}
140
159
  onclick={() => handleSelect(option.value, option.disabled)}
@@ -143,7 +162,13 @@
143
162
  {#if option.prefix}
144
163
  <span class="text-base leading-none" aria-hidden="true">{option.prefix}</span>
145
164
  {/if}
146
- <span class:text-sm={size === Size.XS || size === Size.SM} class:sr-only={compact}>
165
+ <span
166
+ class={cn(
167
+ !compact && (size === Size.XS || size === Size.SM) && !isClark && 'text-sm',
168
+ isClark && !compact && 'text-xs font-medium',
169
+ compact && 'sr-only'
170
+ )}
171
+ >
147
172
  {option.label}
148
173
  </span>
149
174
  </button>
@@ -180,13 +180,20 @@ export type SegmentedControlProps = {
180
180
  name?: string;
181
181
  label?: string;
182
182
  class?: ClassValue;
183
- /** Light track (e.g. page toolbar) vs dark track (e.g. sidebar) */
184
- appearance?: 'surface' | 'inverted';
183
+ /**
184
+ * - `surface` / `inverted`: generic Ripple styling (`color` applies).
185
+ * - `clarkDefault` / `clarkSidebar`: match Clark AR market bar (fixed blue selection, inline-friendly).
186
+ */
187
+ appearance?: 'surface' | 'inverted' | 'clarkDefault' | 'clarkSidebar';
185
188
  orientation?: 'horizontal' | 'vertical';
186
189
  color?: VariantColors;
187
190
  size?: VariantSizes;
188
- /** Hide label text; keep prefix only (label is `sr-only` for screen readers) */
191
+ /** Hide segment label text; prefix only (`sr-only`); with Clark sidebar + vertical = collapsed column */
189
192
  compact?: boolean;
193
+ /** Label above the track vs beside the track (Clark uses `inline`) */
194
+ labelLayout?: 'above' | 'inline';
195
+ /** Optional class on the visible label (e.g. `sr-only` when collapsed sidebar) */
196
+ labelClass?: ClassValue;
190
197
  disabled?: boolean;
191
198
  errors?: string[];
192
199
  onchange?: (value: string) => void;
@@ -1,6 +1,8 @@
1
1
  import type { ClassValue } from 'tailwind-variants';
2
2
  import type { CountryCode } from './country-data.js';
3
3
  import type { VariantColors, VariantSizes } from '../../index.js';
4
+ /** Clark AR toolbar (`default`) vs dark sidebar (`sidebar`) — maps to SegmentedControl Clark appearances. */
5
+ export type MarketSelectorVariant = 'default' | 'sidebar';
4
6
  export type MarketSelectorProps = {
5
7
  /**
6
8
  * Non-empty ordered list of ISO 3166-1 alpha-2 codes to show (e.g. `[Market.DE, Market.GB, Market.CH]`).
@@ -13,8 +15,21 @@ export type MarketSelectorProps = {
13
15
  defaultMarket?: CountryCode;
14
16
  showFlags?: boolean;
15
17
  label?: string;
16
- appearance?: 'surface' | 'inverted';
18
+ /**
19
+ * Clark AR–style market bar. When set, overrides `appearance` to `clarkDefault` / `clarkSidebar`
20
+ * and defaults `labelLayout` to `inline` (override with `labelLayout`).
21
+ */
22
+ variant?: MarketSelectorVariant;
23
+ /**
24
+ * With `variant="sidebar"` only: vertical icon column and `sr-only` segment labels (Clark collapsed sidebar).
25
+ */
26
+ collapsed?: boolean;
27
+ appearance?: 'surface' | 'inverted' | 'clarkDefault' | 'clarkSidebar';
17
28
  orientation?: 'horizontal' | 'vertical';
29
+ /** Label above vs beside the track (Clark uses `inline` via `variant`). */
30
+ labelLayout?: 'above' | 'inline';
31
+ /** Extra classes on the visible label (e.g. override default `sr-only` when collapsed). */
32
+ labelClass?: ClassValue;
18
33
  color?: VariantColors;
19
34
  size?: VariantSizes;
20
35
  compact?: boolean;
@@ -1,39 +1,72 @@
1
1
  import type { VariantColors, VariantSizes } from '../index.js';
2
+ /** @see SegmentedControlProps['appearance'] */
3
+ export type SegmentAppearance = 'surface' | 'inverted' | 'clarkDefault' | 'clarkSidebar';
2
4
  export declare const segmentedTrack: import("tailwind-variants").TVReturnType<{
3
5
  appearance: {
4
6
  surface: string;
5
7
  inverted: string;
8
+ /** Clark (light toolbar): flush segments, gray border, white track */
9
+ clarkDefault: string;
10
+ /** Clark (dark sidebar): translucent track */
11
+ clarkSidebar: string;
6
12
  };
7
13
  orientation: {
8
14
  horizontal: string;
9
15
  vertical: string;
10
16
  };
11
- }, undefined, "inline-flex overflow-hidden rounded-lg border p-0.5", {
17
+ /** Collapsed sidebar column layout (Clark) */
18
+ clarkCollapsed: {
19
+ true: string;
20
+ false: string;
21
+ };
22
+ }, undefined, "inline-flex overflow-hidden rounded-lg border", {
12
23
  appearance: {
13
24
  surface: string;
14
25
  inverted: string;
26
+ /** Clark (light toolbar): flush segments, gray border, white track */
27
+ clarkDefault: string;
28
+ /** Clark (dark sidebar): translucent track */
29
+ clarkSidebar: string;
15
30
  };
16
31
  orientation: {
17
32
  horizontal: string;
18
33
  vertical: string;
19
34
  };
35
+ /** Collapsed sidebar column layout (Clark) */
36
+ clarkCollapsed: {
37
+ true: string;
38
+ false: string;
39
+ };
20
40
  }, undefined, import("tailwind-variants").TVReturnType<{
21
41
  appearance: {
22
42
  surface: string;
23
43
  inverted: string;
44
+ /** Clark (light toolbar): flush segments, gray border, white track */
45
+ clarkDefault: string;
46
+ /** Clark (dark sidebar): translucent track */
47
+ clarkSidebar: string;
24
48
  };
25
49
  orientation: {
26
50
  horizontal: string;
27
51
  vertical: string;
28
52
  };
29
- }, undefined, "inline-flex overflow-hidden rounded-lg border p-0.5", unknown, unknown, undefined>>;
53
+ /** Collapsed sidebar column layout (Clark) */
54
+ clarkCollapsed: {
55
+ true: string;
56
+ false: string;
57
+ };
58
+ }, undefined, "inline-flex overflow-hidden rounded-lg border", unknown, unknown, undefined>>;
30
59
  export declare function segmentClasses(args: {
31
60
  selected: boolean;
32
61
  disabled: boolean;
33
- appearance: 'surface' | 'inverted';
62
+ appearance: SegmentAppearance;
34
63
  color: VariantColors;
35
64
  size: VariantSizes;
36
65
  isFirst: boolean;
37
66
  isLast: boolean;
38
67
  orientation: 'horizontal' | 'vertical';
68
+ /** Clark sidebar collapsed: stacked segments, smaller padding */
69
+ clarkCollapsed?: boolean;
39
70
  }): string;
71
+ /** Label next to / above the track (Clark uses muted gray or default-300 in sidebar). */
72
+ export declare function segmentedLabelClasses(appearance: SegmentAppearance, hasError: boolean): string;
@@ -2,20 +2,37 @@ import { tv } from 'tailwind-variants';
2
2
  import { cn } from '../helper/cls.js';
3
3
  import { Color, Size } from '../variants.js';
4
4
  export const segmentedTrack = tv({
5
- base: 'inline-flex overflow-hidden rounded-lg border p-0.5',
5
+ base: 'inline-flex overflow-hidden rounded-lg border',
6
6
  variants: {
7
7
  appearance: {
8
- surface: 'border-default-200 bg-default-50',
9
- inverted: 'border-white/20 bg-white/5'
8
+ surface: 'border-default-200 bg-default-50 p-0.5',
9
+ inverted: 'border-white/20 bg-white/5 p-0.5',
10
+ /** Clark (light toolbar): flush segments, gray border, white track */
11
+ clarkDefault: 'items-center border-gray-300 bg-white p-0',
12
+ /** Clark (dark sidebar): translucent track */
13
+ clarkSidebar: 'items-center border-white/20 bg-white/5 p-0'
10
14
  },
11
15
  orientation: {
12
16
  horizontal: 'flex-row',
13
17
  vertical: 'w-full flex-col'
18
+ },
19
+ /** Collapsed sidebar column layout (Clark) */
20
+ clarkCollapsed: {
21
+ true: 'gap-1',
22
+ false: ''
14
23
  }
15
24
  },
25
+ compoundVariants: [
26
+ {
27
+ appearance: ['clarkDefault', 'clarkSidebar'],
28
+ orientation: 'vertical',
29
+ class: 'items-center'
30
+ }
31
+ ],
16
32
  defaultVariants: {
17
33
  appearance: 'surface',
18
- orientation: 'horizontal'
34
+ orientation: 'horizontal',
35
+ clarkCollapsed: false
19
36
  }
20
37
  });
21
38
  const selectedByColor = {
@@ -35,8 +52,31 @@ const segmentSize = {
35
52
  [Size.XL]: 'gap-2 px-4 py-2.5 text-lg',
36
53
  [Size.XXL]: 'gap-2 px-5 py-3 text-xl'
37
54
  };
55
+ function isClark(appearance) {
56
+ return appearance === 'clarkDefault' || appearance === 'clarkSidebar';
57
+ }
38
58
  export function segmentClasses(args) {
39
- const { selected, disabled, appearance, color, size, isFirst, isLast, orientation } = args;
59
+ const { selected, disabled, appearance, color, size, isFirst, isLast, orientation, clarkCollapsed } = args;
60
+ if (isClark(appearance)) {
61
+ const isSidebar = appearance === 'clarkSidebar';
62
+ const rounding = clarkCollapsed && isSidebar
63
+ ? 'rounded-md'
64
+ : orientation === 'horizontal'
65
+ ? cn(isFirst && 'rounded-l-lg', isLast && 'rounded-r-lg')
66
+ : cn(isFirst && 'rounded-t-lg', isLast && 'rounded-b-lg');
67
+ const clarkPad = clarkCollapsed && isSidebar ? 'justify-center px-2 py-1.5' : 'gap-1.5 px-3 py-1.5';
68
+ const clarkBase = cn('group relative flex cursor-pointer items-center transition-all duration-150', clarkPad, 'focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:outline-none', isSidebar ? 'focus-visible:ring-offset-0' : 'focus-visible:ring-offset-2', rounding, disabled && 'cursor-not-allowed opacity-50');
69
+ if (disabled) {
70
+ return cn(clarkBase, 'text-default-400');
71
+ }
72
+ if (selected) {
73
+ return cn(clarkBase, 'bg-blue-600 text-white');
74
+ }
75
+ if (appearance === 'clarkDefault') {
76
+ return cn(clarkBase, 'bg-white text-gray-600 hover:bg-gray-50');
77
+ }
78
+ return cn(clarkBase, 'text-default-200 hover:bg-white/10');
79
+ }
40
80
  const rounding = orientation === 'horizontal'
41
81
  ? cn(isFirst && 'rounded-l-md', isLast && 'rounded-r-md')
42
82
  : cn(isFirst && 'rounded-t-md', isLast && 'rounded-b-md');
@@ -48,7 +88,20 @@ export function segmentClasses(args) {
48
88
  return cn(base, selectedByColor[color]);
49
89
  }
50
90
  if (appearance === 'surface') {
51
- return cn(base, 'text-default-700 hover:bg-white/90', 'bg-transparent');
91
+ return cn(base, 'bg-transparent text-default-700 hover:bg-white/90');
92
+ }
93
+ return cn(base, 'bg-transparent text-default-200 hover:bg-white/10');
94
+ }
95
+ /** Label next to / above the track (Clark uses muted gray or default-300 in sidebar). */
96
+ export function segmentedLabelClasses(appearance, hasError) {
97
+ if (hasError) {
98
+ return 'text-sm font-medium text-danger-500';
99
+ }
100
+ if (appearance === 'clarkDefault') {
101
+ return 'text-sm text-gray-500';
102
+ }
103
+ if (appearance === 'clarkSidebar') {
104
+ return 'text-xs text-default-300';
52
105
  }
53
- return cn(base, 'text-default-200 hover:bg-white/10', 'bg-transparent');
106
+ return cn('text-sm font-medium', 'text-default-700');
54
107
  }
package/dist/index.d.ts CHANGED
@@ -34,7 +34,7 @@ export type { CountryCode } from './forms/market/country-data.js';
34
34
  export type { MarketCode } from './forms/market/market.js';
35
35
  export { ALL_COUNTRY_CODES, COUNTRY_NAMES } from './forms/market/country-data.js';
36
36
  export { Market } from './forms/market/market.js';
37
- export type { MarketSelectorProps } from './forms/market/market-selector-types.js';
37
+ export type { MarketSelectorProps, MarketSelectorVariant } from './forms/market/market-selector-types.js';
38
38
  export { countryCodeToFlagEmoji } from './forms/market/flag-emoji.js';
39
39
  export type { ProgressSegment, ProgressProps } from './elements/progress/progress-types.js';
40
40
  export type { AccordionProps } from './elements/accordion/accordion-types.js';
@@ -106,7 +106,8 @@ export { metricCard } from './layout/card/metric-card.js';
106
106
  export { rankedCard } from './layout/card/ranked-card.js';
107
107
  export { activityList } from './layout/activity-list/activity-list.js';
108
108
  export { slider } from './forms/slider.js';
109
- export { segmentedTrack, segmentClasses } from './forms/segmented-control.js';
109
+ export type { SegmentAppearance } from './forms/segmented-control.js';
110
+ export { segmentedTrack, segmentClasses, segmentedLabelClasses } from './forms/segmented-control.js';
110
111
  export { CompactFilters } from './filters/index.js';
111
112
  export * from './file-browser/index.js';
112
113
  export * from './adapters/storage/index.js';
package/dist/index.js CHANGED
@@ -103,7 +103,7 @@ export { metricCard } from './layout/card/metric-card.js';
103
103
  export { rankedCard } from './layout/card/ranked-card.js';
104
104
  export { activityList } from './layout/activity-list/activity-list.js';
105
105
  export { slider } from './forms/slider.js';
106
- export { segmentedTrack, segmentClasses } from './forms/segmented-control.js';
106
+ export { segmentedTrack, segmentClasses, segmentedLabelClasses } from './forms/segmented-control.js';
107
107
  // ============================================================================
108
108
  // Re-export filters
109
109
  // ============================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makolabs/ripple",
3
- "version": "1.9.2",
3
+ "version": "1.10.0",
4
4
  "description": "Simple Svelte 5 powered component library ✨",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {