@pgcorp/ui-kit 0.7.3 → 0.8.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 (36) hide show
  1. package/README.md +161 -8
  2. package/docs/accessibility.md +12 -0
  3. package/docs/getting-started.md +32 -2
  4. package/docs/public-api.md +27 -2
  5. package/package.json +18 -3
  6. package/src/components/shared/codeLanguages.ts +2 -107
  7. package/src/components/shared/containers/SPanel.css +32 -1
  8. package/src/components/shared/containers/SPanel.vue +4 -0
  9. package/src/components/shared/controls/SCheckbox.vue +2 -2
  10. package/src/components/shared/controls/SInteractiveSurface.css +54 -4
  11. package/src/components/shared/controls/SInteractiveSurface.vue +45 -31
  12. package/src/components/shared/controls/SListbox.vue +4 -4
  13. package/src/components/shared/controls/SSwitch.vue +2 -2
  14. package/src/components/shared/data-display/SActionCard.css +6 -2
  15. package/src/components/shared/data-display/SActionCard.vue +13 -3
  16. package/src/components/shared/data-display/SActionListItem.css +7 -8
  17. package/src/components/shared/data-display/SActionListItem.vue +21 -18
  18. package/src/components/shared/data-display/SChart.css +149 -0
  19. package/src/components/shared/data-display/SChart.vue +493 -0
  20. package/src/components/shared/data-display/SChip.css +24 -0
  21. package/src/components/shared/data-display/SChip.vue +30 -6
  22. package/src/components/shared/data-display/SCodeBlock.css +68 -0
  23. package/src/components/shared/data-display/SCodeBlock.vue +102 -16
  24. package/src/components/shared/data-display/SMetricCard.css +1 -0
  25. package/src/components/shared/data-display/SMetricCard.vue +1 -0
  26. package/src/components/shared/data-display/STable.vue +120 -145
  27. package/src/components/shared/data-display/chart.ts +54 -0
  28. package/src/components/shared/navigation/STabList.vue +0 -3
  29. package/src/internal/chartGeometry.ts +852 -0
  30. package/src/internal/codeLanguageIdentity.ts +89 -0
  31. package/src/internal/lazyCodeEditor.ts +1 -0
  32. package/src/internal/linkTarget.ts +1 -3
  33. package/src/internal/ownedAttrs.ts +1 -0
  34. package/src/internal/useBinaryInput.ts +9 -2
  35. package/src/styles/tailwind.css +3 -0
  36. package/src/styles/tokens.css +33 -0
@@ -14,10 +14,36 @@
14
14
  color var(--s-motion-standard) ease;
15
15
  }
16
16
 
17
+ .s-interactive-surface:has(.s-interactive-surface__actions > *) {
18
+ container-type: inline-size;
19
+ }
20
+
21
+ .s-interactive-surface__layout {
22
+ @apply grid min-h-0 min-w-0 w-full items-center;
23
+ grid-template-columns: minmax(0, 1fr);
24
+ }
25
+
26
+ .s-interactive-surface:has(.s-interactive-surface__actions > *) .s-interactive-surface__layout {
27
+ grid-template-columns: minmax(0, 1fr) auto;
28
+ }
29
+
30
+ .s-interactive-surface__primary {
31
+ @apply relative flex min-h-0 min-w-0;
32
+ }
33
+
34
+ .s-interactive-surface__actions {
35
+ @apply flex min-w-0 shrink-0 items-center;
36
+ gap: var(--s-space-control-gap);
37
+ }
38
+
17
39
  .s-interactive-surface[data-appearance='plain'] {
18
40
  @apply border-transparent bg-transparent dark:border-transparent dark:bg-transparent;
19
41
  }
20
42
 
43
+ .s-interactive-surface[data-appearance='bare'] {
44
+ @apply overflow-visible border-0 bg-transparent dark:bg-transparent;
45
+ }
46
+
21
47
  .s-interactive-surface__activation {
22
48
  @apply absolute inset-0 z-10 block h-full w-full cursor-pointer border-0 bg-transparent p-0;
23
49
  border-radius: inherit;
@@ -42,7 +68,9 @@
42
68
  pointer-events: none;
43
69
  }
44
70
 
45
- .s-interactive-surface[data-fill='container'] { @apply h-full w-full; }
71
+ .s-interactive-surface[data-fill='container'],
72
+ .s-interactive-surface[data-fill='container'] .s-interactive-surface__layout,
73
+ .s-interactive-surface[data-fill='container'] .s-interactive-surface__primary { @apply h-full w-full; }
46
74
  .s-interactive-surface[data-density='compact'] { @apply text-sm; }
47
75
  .s-interactive-surface[data-density='comfortable'] { @apply text-base; }
48
76
  .s-interactive-surface[data-padding='none'] { @apply p-0; }
@@ -56,9 +84,31 @@
56
84
  .s-interactive-surface[data-s-tone='danger'] { @apply border-danger-200 bg-danger-50 dark:border-danger-800 dark:bg-danger-950; }
57
85
  .s-interactive-surface[data-selected='true'] { @apply border-primary-400 bg-primary-50 dark:border-primary-600 dark:bg-primary-950; }
58
86
  .s-interactive-surface[data-pressed='true'] { @apply border-primary-500 bg-primary-100 dark:border-primary-500 dark:bg-primary-950/70; }
59
- .s-interactive-surface[data-disabled='true'] { @apply cursor-not-allowed opacity-60; }
60
- .s-interactive-surface[data-content-align='start'] { @apply justify-start text-left; }
61
- .s-interactive-surface[data-content-align='center'] { @apply justify-center text-center; }
87
+ .s-interactive-surface[data-disabled='true'] { @apply cursor-not-allowed; }
88
+ .s-interactive-surface[data-disabled='true']:not(:has(.s-interactive-surface__actions > *)),
89
+ .s-interactive-surface[data-disabled='true']:has(.s-interactive-surface__actions > *) .s-interactive-surface__primary {
90
+ @apply opacity-60;
91
+ }
92
+ .s-interactive-surface[data-content-align='start'] .s-interactive-surface__content { @apply justify-start text-left; }
93
+ .s-interactive-surface[data-content-align='center'] .s-interactive-surface__content { @apply justify-center text-center; }
94
+
95
+ .s-interactive-surface[data-actions-layout='stacked']:has(.s-interactive-surface__actions > *) .s-interactive-surface__layout {
96
+ grid-template-columns: minmax(0, 1fr);
97
+ }
98
+
99
+ .s-interactive-surface[data-actions-layout='stacked'] .s-interactive-surface__actions {
100
+ @apply w-full;
101
+ }
102
+
103
+ @container (max-width: 32rem) {
104
+ .s-interactive-surface[data-actions-layout='responsive'] .s-interactive-surface__layout {
105
+ grid-template-columns: minmax(0, 1fr);
106
+ }
107
+
108
+ .s-interactive-surface[data-actions-layout='responsive'] .s-interactive-surface__actions {
109
+ @apply w-full;
110
+ }
111
+ }
62
112
 
63
113
  .s-interactive-surface[data-mode='action']:not([data-disabled='true']),
64
114
  .s-interactive-surface[data-mode='link']:not([data-disabled='true']) {
@@ -20,7 +20,8 @@ export type InteractiveSurfaceDensity = 'compact' | 'comfortable'
20
20
  export type InteractiveSurfacePadding = 'none' | 'sm' | 'md'
21
21
  export type InteractiveSurfaceRadius = 'md' | 'lg' | 'xl'
22
22
  export type InteractiveSurfaceFill = 'content' | 'container'
23
- export type InteractiveSurfaceAppearance = 'card' | 'plain'
23
+ export type InteractiveSurfaceAppearance = 'card' | 'plain' | 'bare'
24
+ export type InteractiveSurfaceActionsLayout = 'responsive' | 'inline' | 'stacked'
24
25
 
25
26
  /**
26
27
  * Единственный semantic-контракт поверхности: passive, action или ровно одна link-target.
@@ -60,6 +61,7 @@ export interface Props {
60
61
  radius?: InteractiveSurfaceRadius
61
62
  fill?: InteractiveSurfaceFill
62
63
  appearance?: InteractiveSurfaceAppearance
64
+ actionsLayout?: InteractiveSurfaceActionsLayout
63
65
  contentAlign?: 'start' | 'center'
64
66
  /** Полный binding явного STooltip trigger, направляемый на activation owner. / Complete explicit STooltip trigger binding routed to the activation owner. */
65
67
  tooltipTrigger?: STooltipTriggerBinding
@@ -73,6 +75,7 @@ const props = withDefaults(defineProps<Props>(), {
73
75
  radius: 'lg',
74
76
  fill: 'content',
75
77
  appearance: 'card',
78
+ actionsLayout: 'inline',
76
79
  contentAlign: 'start',
77
80
  tooltipTrigger: undefined,
78
81
  })
@@ -80,9 +83,11 @@ const props = withDefaults(defineProps<Props>(), {
80
83
  const emit = defineEmits<{
81
84
  activate: [event: MouseEvent]
82
85
  }>()
83
- defineSlots<{
86
+ const slots = defineSlots<{
84
87
  /** Пассивное flow-содержимое поверхности. / Passive flow content of the surface. */
85
88
  default?: () => VNodeChild
89
+ /** Sibling controls inside the same visual chrome and outside the primary activation owner. */
90
+ actions?: () => VNodeChild
86
91
  }>()
87
92
 
88
93
  const ownedAttrs = useOwnedAttrs({ component: 'SInteractiveSurface', owner: 'interactive surface owner' })
@@ -297,40 +302,49 @@ function activateAction(event: MouseEvent): void {
297
302
  :data-radius="radius"
298
303
  :data-fill="fill"
299
304
  :data-appearance="appearance"
305
+ :data-actions-layout="actionsLayout"
306
+ :data-has-actions="Boolean(slots.actions) || undefined"
300
307
  :data-selected="selected || undefined"
301
308
  :data-pressed="surfacePressed"
302
309
  :data-disabled="surfaceDisabled || undefined"
303
310
  :data-content-align="contentAlign"
304
311
  >
305
- <button
306
- v-if="surfaceMode === 'action'"
307
- v-bind="activationBindings()"
308
- class="s-interactive-surface__activation"
309
- type="button"
310
- :disabled="surfaceDisabled"
311
- :aria-label="resolvedAccessibleLabel"
312
- :aria-pressed="surfacePressed"
313
- @click="activateAction"
314
- >
315
- <span class="s-interactive-surface__accessible-label">{{ resolvedAccessibleLabel }}</span>
316
- </button>
317
- <component
318
- v-else-if="surfaceMode === 'link'"
319
- :is="linkComponent"
320
- v-bind="linkBindings()"
321
- class="s-interactive-surface__activation"
322
- :aria-label="resolvedAccessibleLabel"
323
- :aria-current="selected ? 'page' : undefined"
324
- :aria-disabled="surfaceDisabled || undefined"
325
- :tabindex="surfaceDisabled ? -1 : undefined"
326
- @click="activateLink"
327
- >
328
- <span class="s-interactive-surface__accessible-label">{{ resolvedAccessibleLabel }}</span>
329
- </component>
330
- <div class="s-interactive-surface__content" data-passive-content="">
331
- <PassiveContentBoundary :contract="passiveContentContract">
332
- <slot />
333
- </PassiveContentBoundary>
312
+ <div class="s-interactive-surface__layout">
313
+ <div class="s-interactive-surface__primary">
314
+ <button
315
+ v-if="surfaceMode === 'action'"
316
+ v-bind="activationBindings()"
317
+ class="s-interactive-surface__activation"
318
+ type="button"
319
+ :disabled="surfaceDisabled"
320
+ :aria-label="resolvedAccessibleLabel"
321
+ :aria-pressed="surfacePressed"
322
+ @click="activateAction"
323
+ >
324
+ <span class="s-interactive-surface__accessible-label">{{ resolvedAccessibleLabel }}</span>
325
+ </button>
326
+ <component
327
+ v-else-if="surfaceMode === 'link'"
328
+ :is="linkComponent"
329
+ v-bind="linkBindings()"
330
+ class="s-interactive-surface__activation"
331
+ :aria-label="resolvedAccessibleLabel"
332
+ :aria-current="selected ? 'page' : undefined"
333
+ :aria-disabled="surfaceDisabled || undefined"
334
+ :tabindex="surfaceDisabled ? -1 : undefined"
335
+ @click="activateLink"
336
+ >
337
+ <span class="s-interactive-surface__accessible-label">{{ resolvedAccessibleLabel }}</span>
338
+ </component>
339
+ <div class="s-interactive-surface__content" data-passive-content="">
340
+ <PassiveContentBoundary :contract="passiveContentContract">
341
+ <slot />
342
+ </PassiveContentBoundary>
343
+ </div>
344
+ </div>
345
+ <div v-if="$slots.actions" class="s-interactive-surface__actions">
346
+ <slot name="actions" />
347
+ </div>
334
348
  </div>
335
349
  </div>
336
350
  </template>
@@ -338,7 +338,7 @@ provide(listboxContextKey, {
338
338
  },
339
339
  })
340
340
 
341
- function focusOption(target: ListboxOptionRegistration, options = enabledRegistrations()): void {
341
+ function focusOption(target: ListboxOptionRegistration): void {
342
342
  for (const option of registrations) option.element.tabIndex = -1
343
343
  target.element.tabIndex = 0
344
344
  target.element.focus()
@@ -451,7 +451,7 @@ function handleKeydown(event: KeyboardEvent): void {
451
451
  const match = searchOrder.find((option) => option.label().toLocaleLowerCase().startsWith(query))
452
452
  if (match) {
453
453
  event.preventDefault()
454
- focusOption(match, options)
454
+ focusOption(match)
455
455
  }
456
456
  return
457
457
  }
@@ -467,7 +467,7 @@ function handleKeydown(event: KeyboardEvent): void {
467
467
  const target = options[nextIndex]
468
468
  if (!target) return
469
469
  event.preventDefault()
470
- focusOption(target, options)
470
+ focusOption(target)
471
471
  }
472
472
 
473
473
  function focusSelectedOrFirst(): void {
@@ -476,7 +476,7 @@ function focusSelectedOrFirst(): void {
476
476
  }
477
477
  const options = enabledRegistrations()
478
478
  const target = options.find((option) => option.selected()) ?? options[0]
479
- if (target) focusOption(target, options)
479
+ if (target) focusOption(target)
480
480
  }
481
481
 
482
482
  onBeforeUnmount(() => { if (typeaheadTimer) clearTimeout(typeaheadTimer) })
@@ -13,7 +13,7 @@
13
13
  <template #default="field">
14
14
  <span class="s-switch-control">
15
15
  <input
16
- ref="inputEl"
16
+ :ref="setInputElement"
17
17
  v-bind="ownedAttrs.bindings()"
18
18
  :id="field.controlId"
19
19
  type="checkbox"
@@ -75,10 +75,10 @@ const ownedAttrs = useOwnedAttrs({ component: 'SSwitch', owner: 'native switch i
75
75
  useInteractiveLeafRegistration({ owner: 'SSwitch' })
76
76
  const {
77
77
  checkedValue,
78
- inputEl,
79
78
  inputId,
80
79
  onChange,
81
80
  onClick,
81
+ setInputElement,
82
82
  } = useBinaryInput({
83
83
  owner: 'SSwitch',
84
84
  inputName: 'switch',
@@ -1,6 +1,10 @@
1
1
  @reference "../../../styles/reference.css";
2
2
 
3
- .s-action-card { @apply grid min-h-0 min-w-0 items-start gap-2; grid-template-columns: minmax(0, 1fr) auto; }
3
+ .s-action-card { @apply min-h-0 min-w-0; }
4
4
  .s-action-card[data-fill='container'] { @apply h-full w-full; }
5
5
  .s-action-card__content { @apply flex min-h-0 min-w-0 w-full flex-1 flex-col; }
6
- .s-action-card__actions { @apply flex shrink-0 items-center gap-1; }
6
+ .s-action-card__actions { @apply flex min-w-0 max-w-full shrink-0 items-center gap-1; }
7
+ .s-action-card[data-actions-layout='responsive'] .s-action-card__actions,
8
+ .s-action-card[data-actions-layout='stacked'] .s-action-card__actions {
9
+ @apply w-full;
10
+ }
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
- import type { InteractiveSurfaceContract, InteractiveSurfaceDensity, InteractiveSurfaceFill, InteractiveSurfacePadding, InteractiveSurfaceRadius, InteractiveSurfaceTone } from '../controls/SInteractiveSurface.vue'
2
+ import type { VNodeChild } from 'vue'
3
+ import type { InteractiveSurfaceActionsLayout, InteractiveSurfaceContract, InteractiveSurfaceDensity, InteractiveSurfaceFill, InteractiveSurfacePadding, InteractiveSurfaceRadius, InteractiveSurfaceTone } from '../controls/SInteractiveSurface.vue'
3
4
  import { useOwnedAttrs } from '../../../internal/ownedAttrs'
4
5
  import SInteractiveSurface from '../controls/SInteractiveSurface.vue'
5
6
 
@@ -13,6 +14,7 @@ export interface Props {
13
14
  padding?: InteractiveSurfacePadding
14
15
  radius?: InteractiveSurfaceRadius
15
16
  fill?: InteractiveSurfaceFill
17
+ actionsLayout?: InteractiveSurfaceActionsLayout
16
18
  }
17
19
 
18
20
  withDefaults(defineProps<Props>(), {
@@ -22,14 +24,19 @@ withDefaults(defineProps<Props>(), {
22
24
  padding: 'md',
23
25
  radius: 'lg',
24
26
  fill: 'content',
27
+ actionsLayout: 'inline',
25
28
  })
26
29
 
27
30
  const emit = defineEmits<{ activate: [event: MouseEvent] }>()
31
+ defineSlots<{
32
+ default?: () => VNodeChild
33
+ actions?: () => VNodeChild
34
+ }>()
28
35
  const ownedAttrs = useOwnedAttrs({ component: 'SActionCard', owner: 'action card root' })
29
36
  </script>
30
37
 
31
38
  <template>
32
- <div v-bind="ownedAttrs.bindings()" class="s-action-card" :data-has-actions="Boolean($slots.actions) || undefined" :data-fill="fill" :data-mode="surface.mode">
39
+ <div v-bind="ownedAttrs.bindings()" class="s-action-card" :data-has-actions="Boolean($slots.actions) || undefined" :data-fill="fill" :data-mode="surface.mode" :data-actions-layout="actionsLayout">
33
40
  <SInteractiveSurface
34
41
  :surface="surface"
35
42
  :selected="selected"
@@ -38,11 +45,14 @@ const ownedAttrs = useOwnedAttrs({ component: 'SActionCard', owner: 'action card
38
45
  :padding="padding"
39
46
  :radius="radius"
40
47
  :fill="fill"
48
+ :actions-layout="actionsLayout"
41
49
  @activate="emit('activate', $event)"
42
50
  >
43
51
  <div class="s-action-card__content"><slot /></div>
52
+ <template v-if="$slots.actions" #actions>
53
+ <div class="s-action-card__actions"><slot name="actions" /></div>
54
+ </template>
44
55
  </SInteractiveSurface>
45
- <div v-if="$slots.actions" class="s-action-card__actions"><slot name="actions" /></div>
46
56
  </div>
47
57
  </template>
48
58
 
@@ -1,9 +1,8 @@
1
1
  @reference "../../../styles/reference.css";
2
2
 
3
3
  .s-action-list-item {
4
- @apply grid min-h-12 min-w-0 items-center gap-2;
4
+ @apply min-h-12 min-w-0;
5
5
  height: max-content;
6
- grid-template-columns: minmax(0, 1fr) auto;
7
6
  }
8
7
 
9
8
  .s-action-list-item[data-height-mode='fill'] {
@@ -94,7 +93,7 @@
94
93
  .s-action-list-item[data-density='compact'] .s-action-list-item__icon { @apply h-7 w-7; }
95
94
 
96
95
  .s-action-list-item[data-trailing-layout='stacked'] {
97
- grid-template-columns: minmax(0, 1fr);
96
+ @apply w-full;
98
97
  }
99
98
 
100
99
  .s-action-list-item[data-trailing-layout='stacked'] .s-action-list-item__trailing {
@@ -103,12 +102,12 @@
103
102
  }
104
103
 
105
104
  @container (max-width: 32rem) {
106
- .s-action-list-item[data-trailing-layout='responsive'] {
107
- grid-template-columns: minmax(0, 1fr);
108
- }
109
-
110
105
  .s-action-list-item[data-trailing-layout='responsive'] .s-action-list-item__trailing {
111
- @apply w-full justify-end pb-2 pr-2;
106
+ @apply w-full flex-wrap justify-end gap-y-1 pb-2 pr-2;
112
107
  padding-left: calc(3.5rem + var(--s-action-list-item-indent));
113
108
  }
109
+
110
+ .s-action-list-item[data-trailing-layout='responsive'] .s-action-list-item__trailing > * {
111
+ max-width: 100%;
112
+ }
114
113
  }
@@ -194,6 +194,7 @@ function emitAction(event: MouseEvent): void {
194
194
  :padding="surfacePadding"
195
195
  :radius="surfaceRadius"
196
196
  :fill="heightMode === 'fill' ? 'container' : 'content'"
197
+ :actions-layout="trailingLayout"
197
198
  @activate="emit('activate', $event)"
198
199
  >
199
200
  <div class="s-action-list-item__main" :class="{ 's-action-list-item__main--static': resolvedMode === 'static' }">
@@ -212,25 +213,27 @@ function emitAction(event: MouseEvent): void {
212
213
  </span>
213
214
  </span>
214
215
  </div>
216
+ <template v-if="hasTrailing" #actions>
217
+ <div class="s-action-list-item__trailing">
218
+ <slot name="status" />
219
+ <SBadge v-if="statusLabel" :severity="statusSeverity">{{ statusLabel }}</SBadge>
220
+ <SBadge v-for="badge in validatedBadges" :key="badge.id" :severity="badge.severity">
221
+ {{ badge.label }}
222
+ </SBadge>
223
+ <SButton
224
+ v-if="actionLabel"
225
+ size="sm"
226
+ :label="actionLabel"
227
+ :leading-icon="actionIcon"
228
+ :severity="actionSeverity"
229
+ :disabled="surfaceDisabled || actionDisabled"
230
+ @click="emitAction"
231
+ />
232
+ <slot name="actions" :disabled="surfaceDisabled || actionDisabled" :emit-action="emitAction" />
233
+ <component :is="trailingIcon" v-if="trailingIcon" class="s-action-list-item__trailing-icon" aria-hidden="true" />
234
+ </div>
235
+ </template>
215
236
  </SActionCard>
216
- <div v-if="hasTrailing" class="s-action-list-item__trailing">
217
- <slot name="status" />
218
- <SBadge v-if="statusLabel" :severity="statusSeverity">{{ statusLabel }}</SBadge>
219
- <SBadge v-for="badge in validatedBadges" :key="badge.id" :severity="badge.severity">
220
- {{ badge.label }}
221
- </SBadge>
222
- <SButton
223
- v-if="actionLabel"
224
- size="sm"
225
- :label="actionLabel"
226
- :leading-icon="actionIcon"
227
- :severity="actionSeverity"
228
- :disabled="surfaceDisabled || actionDisabled"
229
- @click="emitAction"
230
- />
231
- <slot name="actions" :disabled="surfaceDisabled || actionDisabled" :emit-action="emitAction" />
232
- <component :is="trailingIcon" v-if="trailingIcon" class="s-action-list-item__trailing-icon" aria-hidden="true" />
233
- </div>
234
237
  </div>
235
238
  </template>
236
239
 
@@ -0,0 +1,149 @@
1
+ @reference "../../../styles/reference.css";
2
+
3
+ .s-chart {
4
+ @apply m-0 grid min-w-0 w-full text-surface-700 dark:text-surface-200;
5
+ gap: var(--s-space-content-gap);
6
+ }
7
+
8
+ .s-chart__viewport {
9
+ @apply relative min-w-0 w-full overflow-hidden rounded-md;
10
+ background: var(--s-chart-surface);
11
+ }
12
+
13
+ .s-chart[data-size='compact'] .s-chart__viewport { aspect-ratio: 15 / 4; }
14
+ .s-chart[data-size='sm'] .s-chart__viewport { aspect-ratio: 8 / 3; }
15
+ .s-chart[data-size='md'] .s-chart__viewport { aspect-ratio: 20 / 9; }
16
+ .s-chart[data-size='lg'] .s-chart__viewport { aspect-ratio: 25 / 12; }
17
+
18
+ .s-chart[data-empty='true'] .s-chart__viewport { aspect-ratio: auto; }
19
+ .s-chart[data-size='compact'][data-empty='true'] .s-chart__viewport { min-height: 6rem; }
20
+ .s-chart[data-size='sm'][data-empty='true'] .s-chart__viewport { min-height: 10rem; }
21
+ .s-chart[data-size='md'][data-empty='true'] .s-chart__viewport { min-height: 14rem; }
22
+ .s-chart[data-size='lg'][data-empty='true'] .s-chart__viewport { min-height: 18rem; }
23
+
24
+ .s-chart__svg { @apply block h-full w-full overflow-visible; }
25
+
26
+ .s-chart__grid line {
27
+ stroke: var(--s-chart-grid);
28
+ stroke-width: 1;
29
+ vector-effect: non-scaling-stroke;
30
+ }
31
+
32
+ .s-chart__axis line {
33
+ stroke: var(--s-chart-axis);
34
+ stroke-width: 1;
35
+ vector-effect: non-scaling-stroke;
36
+ }
37
+
38
+ .s-chart__axis text,
39
+ .s-chart__data-label {
40
+ fill: var(--s-chart-label);
41
+ font-family: var(--font-sans);
42
+ font-size: 11px;
43
+ }
44
+
45
+ .s-chart[data-size='compact'] .s-chart__axis text,
46
+ .s-chart[data-size='compact'] .s-chart__data-label { font-size: 9px; }
47
+
48
+ .s-chart__axis .s-chart__axis-title {
49
+ fill: var(--s-chart-axis-title);
50
+ font-weight: 600;
51
+ }
52
+
53
+ .s-chart__line {
54
+ fill: none;
55
+ stroke: var(--s-chart-tone);
56
+ stroke-linecap: round;
57
+ stroke-linejoin: round;
58
+ stroke-width: 2.5;
59
+ vector-effect: non-scaling-stroke;
60
+ }
61
+
62
+ .s-chart[data-size='compact'] .s-chart__line { stroke-width: 2; }
63
+
64
+ .s-chart__area {
65
+ fill: color-mix(in srgb, var(--s-chart-tone) 20%, transparent);
66
+ stroke: none;
67
+ }
68
+
69
+ .s-chart__marker {
70
+ fill: var(--s-chart-surface);
71
+ stroke: var(--s-chart-tone);
72
+ stroke-width: 2.5;
73
+ vector-effect: non-scaling-stroke;
74
+ }
75
+
76
+ .s-chart__bar,
77
+ .s-chart__slice {
78
+ fill: var(--s-chart-tone);
79
+ stroke: var(--s-chart-surface);
80
+ stroke-width: 1;
81
+ vector-effect: non-scaling-stroke;
82
+ transition: opacity var(--s-motion-fast) ease;
83
+ }
84
+
85
+ .s-chart__bar:hover,
86
+ .s-chart__slice:hover { opacity: 0.82; }
87
+
88
+ .s-chart__radial-label {
89
+ fill: var(--s-chart-label-on-series);
90
+ font-family: var(--font-sans);
91
+ font-size: 11px;
92
+ font-weight: 700;
93
+ paint-order: stroke;
94
+ stroke: color-mix(in srgb, var(--color-surface-950) 28%, transparent);
95
+ stroke-width: 2px;
96
+ stroke-linejoin: round;
97
+ }
98
+
99
+ .s-chart__center {
100
+ fill: var(--s-chart-center-label);
101
+ font-family: var(--font-sans);
102
+ font-size: 20px;
103
+ font-weight: 700;
104
+ }
105
+
106
+ .s-chart__center-label {
107
+ fill: var(--s-chart-label);
108
+ font-size: 10px;
109
+ font-weight: 500;
110
+ }
111
+
112
+ .s-chart__legend {
113
+ @apply flex min-w-0 flex-wrap items-center text-xs text-surface-600 dark:text-surface-300;
114
+ gap: var(--s-space-content-gap);
115
+ }
116
+
117
+ .s-chart__legend[data-position='top'] { @apply justify-start; }
118
+ .s-chart__legend[data-position='bottom'] { @apply justify-center; }
119
+ .s-chart__legend-item { @apply inline-flex min-w-0 items-center gap-1.5; }
120
+
121
+ .s-chart__legend-swatch {
122
+ @apply block h-2.5 w-2.5 shrink-0 rounded-full;
123
+ background: var(--s-chart-tone);
124
+ }
125
+
126
+ .s-chart__data-table { @apply w-full border-collapse overflow-hidden rounded-md text-left text-sm; }
127
+
128
+ .s-chart__data-table th,
129
+ .s-chart__data-table td { @apply border-b border-surface-200 px-3 py-2 dark:border-surface-700; }
130
+
131
+ .s-chart__data-table thead th {
132
+ @apply bg-surface-100 font-semibold text-surface-700 dark:bg-surface-800 dark:text-surface-200;
133
+ }
134
+
135
+ .s-chart__data-table tbody th { @apply font-medium; }
136
+ .s-chart__data-table tbody td { @apply tabular-nums; }
137
+ .s-chart__data-table--assistive { @apply sr-only; }
138
+
139
+ [data-s-chart-tone='series-1'] { --s-chart-tone: var(--s-chart-series-1); }
140
+ [data-s-chart-tone='series-2'] { --s-chart-tone: var(--s-chart-series-2); }
141
+ [data-s-chart-tone='series-3'] { --s-chart-tone: var(--s-chart-series-3); }
142
+ [data-s-chart-tone='series-4'] { --s-chart-tone: var(--s-chart-series-4); }
143
+ [data-s-chart-tone='series-5'] { --s-chart-tone: var(--s-chart-series-5); }
144
+ [data-s-chart-tone='series-6'] { --s-chart-tone: var(--s-chart-series-6); }
145
+ [data-s-chart-tone='series-7'] { --s-chart-tone: var(--s-chart-series-7); }
146
+ [data-s-chart-tone='series-8'] { --s-chart-tone: var(--s-chart-series-8); }
147
+ [data-s-chart-tone='success'] { --s-chart-tone: var(--color-success-500); }
148
+ [data-s-chart-tone='warn'] { --s-chart-tone: var(--color-warn-500); }
149
+ [data-s-chart-tone='danger'] { --s-chart-tone: var(--color-danger-500); }