@redseed/redseed-ui-vue3 8.32.2 → 8.33.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.
package/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export * from './src/components/Button'
|
|
|
12
12
|
export * from './src/components/ButtonGroup'
|
|
13
13
|
export * from './src/components/Card'
|
|
14
14
|
export * from './src/components/CardGroup'
|
|
15
|
+
export * from './src/components/Chart'
|
|
15
16
|
export * from './src/components/Comment'
|
|
16
17
|
export * from './src/components/Disclosure'
|
|
17
18
|
export * from './src/components/Drawer'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redseed/redseed-ui-vue3",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.33.0",
|
|
4
4
|
"description": "RedSeed UI Vue 3 components",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": "https://github.com/redseedtraining/redseed-ui",
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"license": "ISC",
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@heroicons/vue": "2.2.0",
|
|
15
|
+
"apexcharts": "5.13.0",
|
|
16
|
+
"vue3-apexcharts": "1.11.1",
|
|
15
17
|
"@vueuse/components": "14.2.1",
|
|
16
18
|
"@vueuse/core": "14.2.1",
|
|
17
19
|
"@vueuse/integrations": "14.2.1",
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
// ApexCharts touches window/DOM, so this component renders client-side only
|
|
3
|
+
// (guarded by the `mounted` ref). VueApexCharts is imported and used locally
|
|
4
|
+
// so consumers don't need to install the global plugin via app.use().
|
|
5
|
+
import { ref, computed, onMounted } from 'vue'
|
|
6
|
+
import VueApexCharts from 'vue3-apexcharts'
|
|
7
|
+
import { resolveTheme, baseChartOptions, deepMergeOptions } from './chartTheme'
|
|
8
|
+
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
// ApexCharts bar series shape: [{ name, data: [...] }]
|
|
11
|
+
series: {
|
|
12
|
+
type: Array,
|
|
13
|
+
default: () => [],
|
|
14
|
+
},
|
|
15
|
+
// x-axis category labels
|
|
16
|
+
categories: {
|
|
17
|
+
type: Array,
|
|
18
|
+
default: () => [],
|
|
19
|
+
},
|
|
20
|
+
height: {
|
|
21
|
+
type: [Number, String],
|
|
22
|
+
default: 350,
|
|
23
|
+
},
|
|
24
|
+
// Stack the series instead of grouping them side by side
|
|
25
|
+
stacked: {
|
|
26
|
+
type: Boolean,
|
|
27
|
+
default: false,
|
|
28
|
+
},
|
|
29
|
+
// Escape hatch — deep-merged over the rsui defaults
|
|
30
|
+
options: {
|
|
31
|
+
type: Object,
|
|
32
|
+
default: () => ({}),
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const root = ref(null)
|
|
37
|
+
const mounted = ref(false)
|
|
38
|
+
const theme = ref(resolveTheme(null))
|
|
39
|
+
|
|
40
|
+
onMounted(() => {
|
|
41
|
+
theme.value = resolveTheme(root.value)
|
|
42
|
+
mounted.value = true
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const chartOptions = computed(() =>
|
|
46
|
+
deepMergeOptions(
|
|
47
|
+
baseChartOptions({
|
|
48
|
+
type: 'bar',
|
|
49
|
+
theme: theme.value,
|
|
50
|
+
height: props.height,
|
|
51
|
+
categories: props.categories,
|
|
52
|
+
stacked: props.stacked,
|
|
53
|
+
}),
|
|
54
|
+
props.options,
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
</script>
|
|
58
|
+
<template>
|
|
59
|
+
<div ref="root" class="rsui-chart">
|
|
60
|
+
<VueApexCharts
|
|
61
|
+
v-if="mounted"
|
|
62
|
+
type="bar"
|
|
63
|
+
:height="height"
|
|
64
|
+
:series="series"
|
|
65
|
+
:options="chartOptions"
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
</template>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
// ApexCharts touches window/DOM, so this component renders client-side only
|
|
3
|
+
// (guarded by the `mounted` ref). VueApexCharts is imported and used locally
|
|
4
|
+
// so consumers don't need to install the global plugin via app.use().
|
|
5
|
+
import { ref, computed, onMounted } from 'vue'
|
|
6
|
+
import VueApexCharts from 'vue3-apexcharts'
|
|
7
|
+
import { resolveTheme, baseChartOptions, deepMergeOptions } from './chartTheme'
|
|
8
|
+
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
// Donut series is a flat array of numbers, one per slice
|
|
11
|
+
series: {
|
|
12
|
+
type: Array,
|
|
13
|
+
default: () => [],
|
|
14
|
+
},
|
|
15
|
+
// Slice labels, parallel to `series`
|
|
16
|
+
labels: {
|
|
17
|
+
type: Array,
|
|
18
|
+
default: () => [],
|
|
19
|
+
},
|
|
20
|
+
height: {
|
|
21
|
+
type: [Number, String],
|
|
22
|
+
default: 350,
|
|
23
|
+
},
|
|
24
|
+
// Escape hatch — deep-merged over the rsui defaults
|
|
25
|
+
options: {
|
|
26
|
+
type: Object,
|
|
27
|
+
default: () => ({}),
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const root = ref(null)
|
|
32
|
+
const mounted = ref(false)
|
|
33
|
+
const theme = ref(resolveTheme(null))
|
|
34
|
+
|
|
35
|
+
onMounted(() => {
|
|
36
|
+
theme.value = resolveTheme(root.value)
|
|
37
|
+
mounted.value = true
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const chartOptions = computed(() =>
|
|
41
|
+
deepMergeOptions(
|
|
42
|
+
baseChartOptions({
|
|
43
|
+
type: 'donut',
|
|
44
|
+
theme: theme.value,
|
|
45
|
+
height: props.height,
|
|
46
|
+
labels: props.labels,
|
|
47
|
+
}),
|
|
48
|
+
props.options,
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
</script>
|
|
52
|
+
<template>
|
|
53
|
+
<div ref="root" class="rsui-chart">
|
|
54
|
+
<VueApexCharts
|
|
55
|
+
v-if="mounted"
|
|
56
|
+
type="donut"
|
|
57
|
+
:height="height"
|
|
58
|
+
:series="series"
|
|
59
|
+
:options="chartOptions"
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
</template>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
// ApexCharts touches window/DOM, so this component renders client-side only
|
|
3
|
+
// (guarded by the `mounted` ref). VueApexCharts is imported and used locally
|
|
4
|
+
// so consumers don't need to install the global plugin via app.use().
|
|
5
|
+
import { ref, computed, onMounted } from 'vue'
|
|
6
|
+
import VueApexCharts from 'vue3-apexcharts'
|
|
7
|
+
import { resolveTheme, baseChartOptions, deepMergeOptions } from './chartTheme'
|
|
8
|
+
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
// ApexCharts line series shape: [{ name, data: [...] }]
|
|
11
|
+
series: {
|
|
12
|
+
type: Array,
|
|
13
|
+
default: () => [],
|
|
14
|
+
},
|
|
15
|
+
// x-axis category labels
|
|
16
|
+
categories: {
|
|
17
|
+
type: Array,
|
|
18
|
+
default: () => [],
|
|
19
|
+
},
|
|
20
|
+
height: {
|
|
21
|
+
type: [Number, String],
|
|
22
|
+
default: 350,
|
|
23
|
+
},
|
|
24
|
+
// Escape hatch — deep-merged over the rsui defaults
|
|
25
|
+
options: {
|
|
26
|
+
type: Object,
|
|
27
|
+
default: () => ({}),
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const root = ref(null)
|
|
32
|
+
const mounted = ref(false)
|
|
33
|
+
const theme = ref(resolveTheme(null))
|
|
34
|
+
|
|
35
|
+
onMounted(() => {
|
|
36
|
+
theme.value = resolveTheme(root.value)
|
|
37
|
+
mounted.value = true
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const chartOptions = computed(() =>
|
|
41
|
+
deepMergeOptions(
|
|
42
|
+
baseChartOptions({
|
|
43
|
+
type: 'line',
|
|
44
|
+
theme: theme.value,
|
|
45
|
+
height: props.height,
|
|
46
|
+
categories: props.categories,
|
|
47
|
+
}),
|
|
48
|
+
props.options,
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
</script>
|
|
52
|
+
<template>
|
|
53
|
+
<div ref="root" class="rsui-chart">
|
|
54
|
+
<VueApexCharts
|
|
55
|
+
v-if="mounted"
|
|
56
|
+
type="line"
|
|
57
|
+
:height="height"
|
|
58
|
+
:series="series"
|
|
59
|
+
:options="chartOptions"
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
</template>
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { mergeWith } from 'lodash'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared theming for the rsui chart components (LineChart / BarChart /
|
|
5
|
+
* DoughnutChart). These wrap ApexCharts, which renders its own SVG and is
|
|
6
|
+
* styled through JS options rather than CSS classes — so the rsui look lives
|
|
7
|
+
* here as an options object built from the library's design tokens.
|
|
8
|
+
*
|
|
9
|
+
* Tokens are CSS custom properties (HSLA) defined in the tailwindcss package.
|
|
10
|
+
* We read them at runtime via getComputedStyle so charts automatically follow
|
|
11
|
+
* dark mode and any per-app brand overrides. The static fallbacks below mirror
|
|
12
|
+
* the token values in `packages/tailwindcss/colors.css` and cover environments
|
|
13
|
+
* where getComputedStyle returns nothing (SSR, happy-dom tests, pre-mount).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// Default categorical/series palette, in order. First six are the semantic
|
|
17
|
+
// brand/info/success/warning/error/ai colours; the remaining shades extend the
|
|
18
|
+
// palette when a chart has more than six series.
|
|
19
|
+
const PALETTE_TOKENS = [
|
|
20
|
+
{ name: '--Colors-Brand-500', fallback: 'hsla(271, 48%, 24%, 1)' },
|
|
21
|
+
{ name: '--Colors-RedSeed-Blue-500', fallback: 'hsla(205, 100%, 40%, 1)' },
|
|
22
|
+
{ name: '--Colors-RedSeed-Green-500', fallback: 'hsla(162, 43%, 44%, 1)' },
|
|
23
|
+
{ name: '--Colors-RedSeed-Yellow-500', fallback: 'hsla(38, 100%, 51%, 1)' },
|
|
24
|
+
{ name: '--Colors-RedSeed-Red-500', fallback: 'hsla(0, 85%, 44%, 1)' },
|
|
25
|
+
{ name: '--Colors-RedSeed-Purple-500', fallback: 'hsla(271, 70%, 60%, 1)' },
|
|
26
|
+
{ name: '--Colors-Brand-400', fallback: 'hsla(271, 23%, 39%, 1)' },
|
|
27
|
+
{ name: '--Colors-RedSeed-Blue-600', fallback: 'hsla(205, 100%, 36%, 1)' },
|
|
28
|
+
{ name: '--Colors-RedSeed-Green-600', fallback: 'hsla(162, 43%, 40%, 1)' },
|
|
29
|
+
{ name: '--Colors-RedSeed-Yellow-600', fallback: 'hsla(38, 96%, 46%, 1)' },
|
|
30
|
+
{ name: '--Colors-RedSeed-Red-600', fallback: 'hsla(0, 85%, 40%, 1)' },
|
|
31
|
+
{ name: '--Colors-RedSeed-Purple-600', fallback: 'hsla(271, 55%, 54%, 1)' },
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
const TEXT_TOKEN = { name: '--Colors-Grey-900', fallback: 'hsla(220, 24%, 12%, 1)' }
|
|
35
|
+
const TEXT_MUTED_TOKEN = { name: '--Colors-Grey-700', fallback: 'hsla(220, 11%, 29%, 1)' }
|
|
36
|
+
const GRID_TOKEN = { name: '--Colors-Grey-200', fallback: 'hsla(220, 5%, 92%, 1)' }
|
|
37
|
+
const FONT_TOKEN = { name: '--font-redseed', fallback: "'Inter', sans-serif" }
|
|
38
|
+
|
|
39
|
+
function readToken(el, token) {
|
|
40
|
+
if (typeof window === 'undefined' || !el) return token.fallback
|
|
41
|
+
const value = getComputedStyle(el).getPropertyValue(token.name).trim()
|
|
42
|
+
return value || token.fallback
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the rsui design tokens into concrete colour/font values.
|
|
47
|
+
* Pass the chart's root element so per-subtree CSS-var overrides apply;
|
|
48
|
+
* falls back to the static palette when no element is available.
|
|
49
|
+
*/
|
|
50
|
+
export function resolveTheme(el) {
|
|
51
|
+
return {
|
|
52
|
+
colors: PALETTE_TOKENS.map((token) => readToken(el, token)),
|
|
53
|
+
text: readToken(el, TEXT_TOKEN),
|
|
54
|
+
textMuted: readToken(el, TEXT_MUTED_TOKEN),
|
|
55
|
+
grid: readToken(el, GRID_TOKEN),
|
|
56
|
+
fontFamily: readToken(el, FONT_TOKEN),
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Build the rsui ApexCharts defaults for a given chart type. Consumers layer
|
|
62
|
+
* their own `options` over the result via deepMergeOptions().
|
|
63
|
+
*
|
|
64
|
+
* @param {'line'|'bar'|'donut'} type
|
|
65
|
+
* @param {object} theme result of resolveTheme()
|
|
66
|
+
* @param {number|string} height
|
|
67
|
+
* @param {string[]} [categories] x-axis labels (line/bar)
|
|
68
|
+
* @param {string[]} [labels] slice labels (donut)
|
|
69
|
+
* @param {boolean} [stacked] bar only
|
|
70
|
+
*/
|
|
71
|
+
export function baseChartOptions({ type, theme, height, categories, labels, stacked = false }) {
|
|
72
|
+
const options = {
|
|
73
|
+
chart: {
|
|
74
|
+
type,
|
|
75
|
+
height,
|
|
76
|
+
fontFamily: theme.fontFamily,
|
|
77
|
+
foreColor: theme.textMuted,
|
|
78
|
+
toolbar: { show: false },
|
|
79
|
+
zoom: { enabled: false },
|
|
80
|
+
},
|
|
81
|
+
colors: theme.colors,
|
|
82
|
+
dataLabels: { enabled: false },
|
|
83
|
+
legend: {
|
|
84
|
+
position: 'bottom',
|
|
85
|
+
horizontalAlign: 'center',
|
|
86
|
+
fontFamily: theme.fontFamily,
|
|
87
|
+
labels: { colors: theme.text },
|
|
88
|
+
markers: { strokeWidth: 0 },
|
|
89
|
+
itemMargin: { horizontal: 8, vertical: 4 },
|
|
90
|
+
},
|
|
91
|
+
tooltip: {
|
|
92
|
+
followCursor: true,
|
|
93
|
+
style: { fontFamily: theme.fontFamily },
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (type === 'line') {
|
|
98
|
+
options.chart.stacked = false
|
|
99
|
+
options.stroke = { curve: 'smooth', width: 3 }
|
|
100
|
+
options.markers = { size: 0, hover: { size: 5 } }
|
|
101
|
+
options.grid = { borderColor: theme.grid, strokeDashArray: 4 }
|
|
102
|
+
options.xaxis = axis(categories, theme)
|
|
103
|
+
options.yaxis = { labels: { style: { colors: theme.textMuted } } }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (type === 'bar') {
|
|
107
|
+
options.chart.stacked = !!stacked
|
|
108
|
+
options.stroke = { show: stacked, width: stacked ? 1 : 0, colors: ['transparent'] }
|
|
109
|
+
options.plotOptions = {
|
|
110
|
+
bar: {
|
|
111
|
+
borderRadius: 6,
|
|
112
|
+
borderRadiusApplication: stacked ? 'around' : 'end',
|
|
113
|
+
columnWidth: '55%',
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
options.grid = { borderColor: theme.grid, strokeDashArray: 4 }
|
|
117
|
+
options.xaxis = axis(categories, theme)
|
|
118
|
+
options.yaxis = { labels: { style: { colors: theme.textMuted } } }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (type === 'donut') {
|
|
122
|
+
options.labels = labels || []
|
|
123
|
+
options.stroke = { width: 0 }
|
|
124
|
+
options.plotOptions = {
|
|
125
|
+
pie: {
|
|
126
|
+
donut: {
|
|
127
|
+
size: '68%',
|
|
128
|
+
labels: {
|
|
129
|
+
show: true,
|
|
130
|
+
total: {
|
|
131
|
+
show: true,
|
|
132
|
+
color: theme.textMuted,
|
|
133
|
+
fontFamily: theme.fontFamily,
|
|
134
|
+
},
|
|
135
|
+
value: { color: theme.text, fontFamily: theme.fontFamily },
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return options
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function axis(categories, theme) {
|
|
146
|
+
return {
|
|
147
|
+
categories: categories || [],
|
|
148
|
+
labels: { style: { colors: theme.textMuted } },
|
|
149
|
+
axisBorder: { color: theme.grid },
|
|
150
|
+
axisTicks: { color: theme.grid },
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Arrays from the consumer's options replace defaults wholesale; plain objects
|
|
155
|
+
// continue to deep-merge so partial overrides (e.g. grid.borderColor) still work.
|
|
156
|
+
const replaceArrays = (objValue, srcValue) => (Array.isArray(srcValue) ? srcValue : undefined)
|
|
157
|
+
|
|
158
|
+
/** Deep-merge a consumer `options` object over the rsui defaults. */
|
|
159
|
+
export function deepMergeOptions(base, overrides) {
|
|
160
|
+
return mergeWith({}, base, overrides || {}, replaceArrays)
|
|
161
|
+
}
|