@aiaiai-pt/design-system 0.4.4 → 0.5.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.
@@ -0,0 +1,101 @@
1
+ <!--
2
+ @component MapPopup
3
+
4
+ Tooltip/popup anchored to a map coordinate via OL Overlay.
5
+ Styled with DS card tokens. Used internally by map components
6
+ but can be composed directly for custom popups.
7
+ Consumes --map-popup-* tokens from components.css.
8
+
9
+ @example Inside MapCluster
10
+ <MapCluster {markers} let:popup>
11
+ <MapPopup>{popup.label}</MapPopup>
12
+ </MapCluster>
13
+ -->
14
+ <script>
15
+ let {
16
+ /** @type {boolean} */
17
+ visible = false,
18
+ /** @type {import('svelte').Snippet | undefined} */
19
+ children = undefined,
20
+ /** @type {(() => void) | undefined} */
21
+ onclose = undefined,
22
+ /** @type {string} */
23
+ class: className = '',
24
+ ...rest
25
+ } = $props();
26
+ </script>
27
+
28
+ {#if visible}
29
+ <div class="map-popup {className}" {...rest}>
30
+ <div class="map-popup-content">
31
+ {#if children}{@render children()}{/if}
32
+ </div>
33
+ {#if onclose}
34
+ <button class="map-popup-close" onclick={onclose} aria-label="Close">
35
+ <svg viewBox="0 0 256 256" aria-hidden="true">
36
+ <line x1="80" y1="80" x2="176" y2="176" stroke="currentColor" stroke-width="16" stroke-linecap="round" />
37
+ <line x1="176" y1="80" x2="80" y2="176" stroke="currentColor" stroke-width="16" stroke-linecap="round" />
38
+ </svg>
39
+ </button>
40
+ {/if}
41
+ <div class="map-popup-arrow" aria-hidden="true"></div>
42
+ </div>
43
+ {/if}
44
+
45
+ <style>
46
+ .map-popup {
47
+ position: relative;
48
+ background: var(--map-popup-bg);
49
+ border: var(--map-popup-border);
50
+ border-radius: var(--map-popup-radius);
51
+ box-shadow: var(--map-popup-shadow);
52
+ padding: var(--map-popup-padding);
53
+ max-width: var(--map-popup-max-width);
54
+ width: max-content;
55
+ }
56
+
57
+ .map-popup-content {
58
+ font-family: var(--type-body-sm-font);
59
+ font-size: var(--type-body-sm-size);
60
+ color: var(--color-text);
61
+ line-height: var(--type-body-sm-line-height);
62
+ }
63
+
64
+ .map-popup-close {
65
+ position: absolute;
66
+ top: var(--space-2xs);
67
+ right: var(--space-2xs);
68
+ display: flex;
69
+ align-items: center;
70
+ justify-content: center;
71
+ width: var(--icon-size-md);
72
+ height: var(--icon-size-md);
73
+ border: none;
74
+ border-radius: var(--radius-sm);
75
+ background: transparent;
76
+ color: var(--color-text-muted);
77
+ cursor: pointer;
78
+ }
79
+
80
+ .map-popup-close:hover {
81
+ background: var(--color-surface-secondary);
82
+ color: var(--color-text);
83
+ }
84
+
85
+ .map-popup-close svg {
86
+ width: var(--icon-size-xs);
87
+ height: var(--icon-size-xs);
88
+ }
89
+
90
+ .map-popup-arrow {
91
+ position: absolute;
92
+ bottom: calc(-1 * var(--map-popup-arrow-size));
93
+ left: 50%;
94
+ transform: translateX(-50%);
95
+ width: 0;
96
+ height: 0;
97
+ border-left: var(--map-popup-arrow-size) solid transparent;
98
+ border-right: var(--map-popup-arrow-size) solid transparent;
99
+ border-top: var(--map-popup-arrow-size) solid var(--map-popup-bg);
100
+ }
101
+ </style>
@@ -0,0 +1,30 @@
1
+ export default MapPopup;
2
+ type MapPopup = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ /**
7
+ * MapPopup
8
+ *
9
+ * Tooltip/popup anchored to a map coordinate via OL Overlay.
10
+ * Styled with DS card tokens. Used internally by map components
11
+ * but can be composed directly for custom popups.
12
+ * Consumes --map-popup-* tokens from components.css.
13
+ *
14
+ * @example Inside MapCluster
15
+ * <MapCluster {markers} let:popup>
16
+ * <MapPopup>{popup.label}</MapPopup>
17
+ * </MapCluster>
18
+ */
19
+ declare const MapPopup: import("svelte").Component<{
20
+ visible?: boolean;
21
+ children?: any;
22
+ onclose?: any;
23
+ class?: string;
24
+ } & Record<string, any>, {}, "">;
25
+ type $$ComponentProps = {
26
+ visible?: boolean;
27
+ children?: any;
28
+ onclose?: any;
29
+ class?: string;
30
+ } & Record<string, any>;
@@ -55,11 +55,9 @@
55
55
  const hintId = $derived(`${selectId}-hint`);
56
56
  const hasHint = $derived(!!error || !!help);
57
57
 
58
- let mounted = false;
59
- $effect(() => {
60
- if (!mounted) { mounted = true; return; }
58
+ function handleChange() {
61
59
  if (onchange && value !== undefined) onchange(value);
62
- });
60
+ }
63
61
  </script>
64
62
 
65
63
  <div class="input-group {className}">
@@ -76,6 +74,7 @@
76
74
  aria-describedby={hasHint ? hintId : undefined}
77
75
  {disabled}
78
76
  bind:value
77
+ onchange={handleChange}
79
78
  {...rest}
80
79
  >
81
80
  {#if placeholder}
@@ -0,0 +1,195 @@
1
+ <!--
2
+ @component StatCard
3
+
4
+ KPI card with large value, label, optional trend indicator and icon.
5
+ Consumes --stat-* tokens from components.css.
6
+
7
+ @example Basic
8
+ <StatCard label="TOTAL EQUIPMENT" value="1,247" />
9
+
10
+ @example With trend
11
+ <StatCard label="ACTIVE" value="892" trend={12.4} trendLabel="vs last month" />
12
+
13
+ @example With icon
14
+ <StatCard label="OVERDUE" value="23" variant="error">
15
+ {#snippet icon()}<PhWarning size={24} />{/snippet}
16
+ </StatCard>
17
+ -->
18
+ <script>
19
+ import Skeleton from './Skeleton.svelte';
20
+
21
+ /**
22
+ * @typedef {'neutral' | 'success' | 'warning' | 'error' | 'info'} Variant
23
+ */
24
+
25
+ let {
26
+ /** @type {string} */
27
+ value = '',
28
+ /** @type {string} */
29
+ label = '',
30
+ /** @type {Variant} */
31
+ variant = 'neutral',
32
+ /** @type {number | undefined} — percentage change; positive = up, negative = down, 0 = neutral */
33
+ trend = undefined,
34
+ /** @type {string | undefined} */
35
+ trendLabel = undefined,
36
+ /** @type {boolean} */
37
+ loading = false,
38
+ /** @type {import('svelte').Snippet | undefined} */
39
+ icon = undefined,
40
+ /** @type {string} */
41
+ class: className = '',
42
+ ...rest
43
+ } = $props();
44
+
45
+ const trendDirection = $derived(
46
+ trend === undefined ? 'none'
47
+ : trend > 0 ? 'up'
48
+ : trend < 0 ? 'down'
49
+ : 'neutral'
50
+ );
51
+
52
+ const trendText = $derived(
53
+ trend === undefined ? ''
54
+ : `${trend > 0 ? '+' : ''}${trend.toFixed(1)}%`
55
+ );
56
+ </script>
57
+
58
+ <div class="stat stat-{variant} {className}" {...rest}>
59
+ {#if loading}
60
+ <div class="stat-loading">
61
+ <Skeleton width="60%" height="36px" />
62
+ <Skeleton width="80%" height="12px" />
63
+ </div>
64
+ {:else}
65
+ <div class="stat-content">
66
+ <div class="stat-main">
67
+ <span class="stat-label">{label}</span>
68
+ <span class="stat-value">{value}</span>
69
+ {#if trend !== undefined}
70
+ <span class="stat-trend stat-trend-{trendDirection}">
71
+ {#if trendDirection === 'up'}
72
+ <svg class="stat-trend-icon" viewBox="0 0 256 256" aria-hidden="true">
73
+ <polyline points="64,192 128,128 192,192" fill="none" stroke="currentColor" stroke-width="24" stroke-linecap="round" stroke-linejoin="round" />
74
+ <line x1="128" y1="128" x2="128" y2="224" stroke="currentColor" stroke-width="24" stroke-linecap="round" />
75
+ </svg>
76
+ {:else if trendDirection === 'down'}
77
+ <svg class="stat-trend-icon" viewBox="0 0 256 256" aria-hidden="true">
78
+ <polyline points="64,64 128,128 192,64" fill="none" stroke="currentColor" stroke-width="24" stroke-linecap="round" stroke-linejoin="round" />
79
+ <line x1="128" y1="32" x2="128" y2="128" stroke="currentColor" stroke-width="24" stroke-linecap="round" />
80
+ </svg>
81
+ {:else}
82
+ <svg class="stat-trend-icon" viewBox="0 0 256 256" aria-hidden="true">
83
+ <line x1="40" y1="128" x2="216" y2="128" stroke="currentColor" stroke-width="24" stroke-linecap="round" />
84
+ </svg>
85
+ {/if}
86
+ <span>{trendText}</span>
87
+ {#if trendLabel}
88
+ <span class="stat-trend-label">{trendLabel}</span>
89
+ {/if}
90
+ </span>
91
+ {/if}
92
+ </div>
93
+ {#if icon}
94
+ <span class="stat-icon" aria-hidden="true">
95
+ {@render icon()}
96
+ </span>
97
+ {/if}
98
+ </div>
99
+ {/if}
100
+ </div>
101
+
102
+ <style>
103
+ .stat {
104
+ display: flex;
105
+ padding: var(--stat-padding);
106
+ border: var(--stat-border);
107
+ border-radius: var(--stat-radius);
108
+ background: var(--stat-bg);
109
+ min-width: 0;
110
+ }
111
+
112
+ .stat-loading {
113
+ display: flex;
114
+ flex-direction: column;
115
+ gap: var(--space-md);
116
+ width: 100%;
117
+ }
118
+
119
+ .stat-content {
120
+ display: flex;
121
+ align-items: flex-start;
122
+ justify-content: space-between;
123
+ gap: var(--space-md);
124
+ width: 100%;
125
+ }
126
+
127
+ .stat-main {
128
+ display: flex;
129
+ flex-direction: column;
130
+ gap: var(--space-2xs);
131
+ min-width: 0;
132
+ }
133
+
134
+ .stat-label {
135
+ font-family: var(--stat-label-font);
136
+ font-size: var(--stat-label-size);
137
+ letter-spacing: var(--stat-label-tracking);
138
+ color: var(--stat-label-color);
139
+ }
140
+
141
+ .stat-value {
142
+ font-family: var(--stat-value-font);
143
+ font-size: var(--stat-value-size);
144
+ font-weight: var(--stat-value-weight);
145
+ letter-spacing: var(--stat-value-tracking);
146
+ color: var(--color-text);
147
+ line-height: 1;
148
+ }
149
+
150
+ /* ─── Variant accent on value ─── */
151
+ .stat-success .stat-value { color: var(--color-success); }
152
+ .stat-warning .stat-value { color: var(--color-warning); }
153
+ .stat-error .stat-value { color: var(--color-destructive); }
154
+ .stat-info .stat-value { color: var(--color-info); }
155
+
156
+ /* ─── Trend ─── */
157
+ .stat-trend {
158
+ display: inline-flex;
159
+ align-items: center;
160
+ gap: var(--space-2xs);
161
+ font-family: var(--stat-trend-font);
162
+ font-size: var(--stat-trend-size);
163
+ margin-top: var(--space-2xs);
164
+ }
165
+
166
+ .stat-trend-up { color: var(--stat-trend-up-color); }
167
+ .stat-trend-down { color: var(--stat-trend-down-color); }
168
+ .stat-trend-neutral { color: var(--stat-trend-neutral-color); }
169
+
170
+ .stat-trend-icon {
171
+ width: var(--icon-size-xs);
172
+ height: var(--icon-size-xs);
173
+ flex-shrink: 0;
174
+ }
175
+
176
+ .stat-trend-label {
177
+ color: var(--color-text-muted);
178
+ }
179
+
180
+ /* ─── Icon ─── */
181
+ .stat-icon {
182
+ display: flex;
183
+ align-items: center;
184
+ justify-content: center;
185
+ width: var(--stat-icon-size);
186
+ height: var(--stat-icon-size);
187
+ color: var(--stat-icon-color);
188
+ flex-shrink: 0;
189
+ }
190
+
191
+ .stat-icon :global(svg) {
192
+ width: 100%;
193
+ height: 100%;
194
+ }
195
+ </style>
@@ -0,0 +1,42 @@
1
+ export default StatCard;
2
+ type StatCard = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ /**
7
+ * StatCard
8
+ *
9
+ * KPI card with large value, label, optional trend indicator and icon.
10
+ * Consumes --stat-* tokens from components.css.
11
+ *
12
+ * @example Basic
13
+ * <StatCard label="TOTAL EQUIPMENT" value="1,247" />
14
+ *
15
+ * @example With trend
16
+ * <StatCard label="ACTIVE" value="892" trend={12.4} trendLabel="vs last month" />
17
+ *
18
+ * @example With icon
19
+ * <StatCard label="OVERDUE" value="23" variant="error">
20
+ * {#snippet icon()}<PhWarning size={24} />{/snippet}
21
+ * </StatCard>
22
+ */
23
+ declare const StatCard: import("svelte").Component<{
24
+ value?: string;
25
+ label?: string;
26
+ variant?: string;
27
+ trend?: any;
28
+ trendLabel?: any;
29
+ loading?: boolean;
30
+ icon?: any;
31
+ class?: string;
32
+ } & Record<string, any>, {}, "">;
33
+ type $$ComponentProps = {
34
+ value?: string;
35
+ label?: string;
36
+ variant?: string;
37
+ trend?: any;
38
+ trendLabel?: any;
39
+ loading?: boolean;
40
+ icon?: any;
41
+ class?: string;
42
+ } & Record<string, any>;
@@ -0,0 +1,39 @@
1
+ <!--
2
+ @component StatGrid
3
+
4
+ Responsive grid for StatCard components. Wraps CardGrid with
5
+ stat-appropriate defaults (4 columns, stat-grid gap).
6
+
7
+ @example
8
+ <StatGrid>
9
+ <StatCard label="TOTAL" value="1,247" />
10
+ <StatCard label="ACTIVE" value="892" variant="success" />
11
+ <StatCard label="OVERDUE" value="23" variant="error" />
12
+ <StatCard label="SCHEDULED" value="156" variant="info" />
13
+ </StatGrid>
14
+ -->
15
+ <script>
16
+ import CardGrid from './CardGrid.svelte';
17
+
18
+ let {
19
+ /** @type {'2' | '3' | '4'} */
20
+ columns = '4',
21
+ /** @type {string} */
22
+ class: className = '',
23
+ /** @type {import('svelte').Snippet | undefined} */
24
+ children = undefined,
25
+ ...rest
26
+ } = $props();
27
+ </script>
28
+
29
+ <CardGrid {columns} class="stat-grid {className}" {...rest}>
30
+ {#if children}
31
+ {@render children()}
32
+ {/if}
33
+ </CardGrid>
34
+
35
+ <style>
36
+ :global(.stat-grid) {
37
+ gap: var(--stat-grid-gap);
38
+ }
39
+ </style>
@@ -0,0 +1,29 @@
1
+ export default StatGrid;
2
+ type StatGrid = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ /**
7
+ * StatGrid
8
+ *
9
+ * Responsive grid for StatCard components. Wraps CardGrid with
10
+ * stat-appropriate defaults (4 columns, stat-grid gap).
11
+ *
12
+ * @example
13
+ * <StatGrid>
14
+ * <StatCard label="TOTAL" value="1,247" />
15
+ * <StatCard label="ACTIVE" value="892" variant="success" />
16
+ * <StatCard label="OVERDUE" value="23" variant="error" />
17
+ * <StatCard label="SCHEDULED" value="156" variant="info" />
18
+ * </StatGrid>
19
+ */
20
+ declare const StatGrid: import("svelte").Component<{
21
+ columns?: string;
22
+ class?: string;
23
+ children?: any;
24
+ } & Record<string, any>, {}, "">;
25
+ type $$ComponentProps = {
26
+ columns?: string;
27
+ class?: string;
28
+ children?: any;
29
+ } & Record<string, any>;
@@ -50,3 +50,15 @@ export { default as LogViewer } from "./LogViewer.svelte";
50
50
  export { default as DataTable } from "./DataTable.svelte";
51
51
  export { default as Pagination } from "./Pagination.svelte";
52
52
  export { default as Breadcrumb } from "./Breadcrumb.svelte";
53
+ export { default as DatePicker } from "./DatePicker.svelte";
54
+ export { default as DateTimePicker } from "./DateTimePicker.svelte";
55
+ export { default as DateRangePicker } from "./DateRangePicker.svelte";
56
+ export { default as FilterPanel } from "./FilterPanel.svelte";
57
+ export { default as StatCard } from "./StatCard.svelte";
58
+ export { default as StatGrid } from "./StatGrid.svelte";
59
+ export { default as MapDisplay } from "./MapDisplay.svelte";
60
+ export { default as MapPicker } from "./MapPicker.svelte";
61
+ export { default as MapCluster } from "./MapCluster.svelte";
62
+ export { default as MapHeatmap } from "./MapHeatmap.svelte";
63
+ export { default as MapPopup } from "./MapPopup.svelte";
64
+ export { default as Calendar } from "./Calendar.svelte";
@@ -44,6 +44,9 @@ export { default as MenuSeparator } from "./MenuSeparator.svelte";
44
44
 
45
45
  // Form controls — composite
46
46
  export { default as Combobox } from "./Combobox.svelte";
47
+ export { default as DatePicker } from "./DatePicker.svelte";
48
+ export { default as DateTimePicker } from "./DateTimePicker.svelte";
49
+ export { default as DateRangePicker } from "./DateRangePicker.svelte";
47
50
 
48
51
  // Search
49
52
  export { default as SearchInput } from "./SearchInput.svelte";
@@ -82,3 +85,17 @@ export { default as LogViewer } from "./LogViewer.svelte";
82
85
  export { default as DataTable } from "./DataTable.svelte";
83
86
  export { default as Pagination } from "./Pagination.svelte";
84
87
  export { default as Breadcrumb } from "./Breadcrumb.svelte";
88
+
89
+ // Stats
90
+ export { default as StatCard } from "./StatCard.svelte";
91
+ export { default as StatGrid } from "./StatGrid.svelte";
92
+
93
+ // Maps (OpenLayers)
94
+ export { default as MapDisplay } from "./MapDisplay.svelte";
95
+ export { default as MapPicker } from "./MapPicker.svelte";
96
+ export { default as MapCluster } from "./MapCluster.svelte";
97
+ export { default as MapHeatmap } from "./MapHeatmap.svelte";
98
+ export { default as MapPopup } from "./MapPopup.svelte";
99
+
100
+ // Scheduling
101
+ export { default as Calendar } from "./Calendar.svelte";
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Shared utilities for map components.
3
+ * Not exported from index.js — internal to map components only.
4
+ */
5
+ /**
6
+ * @typedef {{ type: 'osm' }} OsmSource
7
+ * @typedef {{ type: 'xyz', url: string, attributions?: string, maxZoom?: number }} XyzSource
8
+ * @typedef {{ type: 'stadia', layer: string, retina?: boolean, apiKey?: string }} StadiaSource
9
+ * @typedef {OsmSource | XyzSource | StadiaSource} TileSourceConfig
10
+ */
11
+ /**
12
+ * Creates an OL tile layer from a config object.
13
+ *
14
+ * @param {TileSourceConfig} config
15
+ * @returns {Promise<import('ol/layer/Tile.js').default>}
16
+ */
17
+ export function createTileLayer(config: TileSourceConfig): Promise<import("ol/layer/Tile.js").default>;
18
+ /**
19
+ * @typedef {object} MapStyles
20
+ * @property {import('ol/style/Style.js').default} marker
21
+ * @property {import('ol/style/Style.js').default} polygon
22
+ * @property {(size: number) => import('ol/style/Style.js').default} cluster
23
+ * @property {() => void} refresh — re-read CSS tokens, mutate styles in place
24
+ */
25
+ /**
26
+ * Creates OL styles from DS tokens. Returns mutable style objects
27
+ * with a refresh() method that re-reads CSS vars and updates the
28
+ * styles in place — call after theme change, then source.changed().
29
+ *
30
+ * @param {Element} el
31
+ * @returns {Promise<MapStyles>}
32
+ */
33
+ export function createMapStyles(el: Element): Promise<MapStyles>;
34
+ /**
35
+ * Watches for theme changes (data-theme attribute, class changes on <html>,
36
+ * OS prefers-color-scheme). Calls onchange when a theme switch is detected.
37
+ * Returns a dispose function.
38
+ *
39
+ * @param {() => void} onchange
40
+ * @returns {() => void} dispose
41
+ */
42
+ export function watchTheme(onchange: () => void): () => void;
43
+ /**
44
+ * Renders a visible error state inside a map container.
45
+ *
46
+ * @param {HTMLElement} el
47
+ * @param {string} component
48
+ * @param {Error} error
49
+ */
50
+ export function renderMapError(el: HTMLElement, component: string, error: Error): void;
51
+ /**
52
+ * @param {Element} el
53
+ * @param {string} prop
54
+ * @param {string} fallback
55
+ * @returns {string}
56
+ */
57
+ export function cssVar(el: Element, prop: string, fallback: string): string;
58
+ /**
59
+ * Resolves a CSS custom property to a pixel number via probe element.
60
+ *
61
+ * @param {Element} el
62
+ * @param {string} prop
63
+ * @param {number} fallback
64
+ * @returns {number}
65
+ */
66
+ export function cssPx(el: Element, prop: string, fallback: number): number;
67
+ /**
68
+ * Reads heatmap gradient stops from CSS custom properties.
69
+ * Resolves color-mix() and other CSS color expressions to concrete
70
+ * rgb values that OL's canvas gradient can consume.
71
+ *
72
+ * @param {Element} el
73
+ * @returns {string[]}
74
+ */
75
+ export function getHeatmapGradient(el: Element): string[];
76
+ export type OsmSource = {
77
+ type: "osm";
78
+ };
79
+ export type XyzSource = {
80
+ type: "xyz";
81
+ url: string;
82
+ attributions?: string;
83
+ maxZoom?: number;
84
+ };
85
+ export type StadiaSource = {
86
+ type: "stadia";
87
+ layer: string;
88
+ retina?: boolean;
89
+ apiKey?: string;
90
+ };
91
+ export type TileSourceConfig = OsmSource | XyzSource | StadiaSource;
92
+ export type MapStyles = {
93
+ marker: import("ol/style/Style.js").default;
94
+ polygon: import("ol/style/Style.js").default;
95
+ cluster: (size: number) => import("ol/style/Style.js").default;
96
+ /**
97
+ * — re-read CSS tokens, mutate styles in place
98
+ */
99
+ refresh: () => void;
100
+ };