@tak-ps/vue-tabler 4.8.0 → 4.9.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/CHANGELOG.md CHANGED
@@ -10,6 +10,10 @@
10
10
 
11
11
  ## Version History
12
12
 
13
+ ### v4.9.0
14
+
15
+ - :tada: Introduce Tabler Badge
16
+
13
17
  ### v4.8.0
14
18
 
15
19
  - :rocket: Allow setting button colour via prop
@@ -0,0 +1,136 @@
1
+ <template>
2
+ <span
3
+ class='badge tabler-badge'
4
+ :style='badgeStyle'
5
+ >
6
+ <slot />
7
+ </span>
8
+ </template>
9
+
10
+ <script setup lang='ts'>
11
+ import { computed } from 'vue';
12
+
13
+ export interface BadgeProps {
14
+ backgroundColor?: string;
15
+ borderColor?: string;
16
+ textColor?: string;
17
+ }
18
+
19
+ const props = withDefaults(defineProps<BadgeProps>(), {
20
+ backgroundColor: 'rgba(34, 197, 94, 0.2)',
21
+ borderColor: undefined,
22
+ textColor: '#ffffff',
23
+ });
24
+
25
+ type ParsedColor = {
26
+ red: number;
27
+ green: number;
28
+ blue: number;
29
+ alpha?: number;
30
+ };
31
+
32
+ const HEX_COLOR = /^#([\da-f]{3}|[\da-f]{4}|[\da-f]{6}|[\da-f]{8})$/i;
33
+ const RGB_COLOR = /^rgba?\(([^)]+)\)$/i;
34
+
35
+ function clampChannel(value: number): number {
36
+ return Math.max(0, Math.min(255, Math.round(value)));
37
+ }
38
+
39
+ function clampAlpha(value: number): number {
40
+ return Math.max(0, Math.min(1, value));
41
+ }
42
+
43
+ function parseHexColor(value: string): ParsedColor | null {
44
+ const match = value.match(HEX_COLOR);
45
+ if (!match) return null;
46
+
47
+ const hex = match[1];
48
+
49
+ if (hex.length === 3 || hex.length === 4) {
50
+ const red = Number.parseInt(`${hex[0]}${hex[0]}`, 16);
51
+ const green = Number.parseInt(`${hex[1]}${hex[1]}`, 16);
52
+ const blue = Number.parseInt(`${hex[2]}${hex[2]}`, 16);
53
+ const alpha = hex.length === 4
54
+ ? clampAlpha(Number.parseInt(`${hex[3]}${hex[3]}`, 16) / 255)
55
+ : undefined;
56
+
57
+ return { red, green, blue, alpha };
58
+ }
59
+
60
+ const red = Number.parseInt(hex.slice(0, 2), 16);
61
+ const green = Number.parseInt(hex.slice(2, 4), 16);
62
+ const blue = Number.parseInt(hex.slice(4, 6), 16);
63
+ const alpha = hex.length === 8
64
+ ? clampAlpha(Number.parseInt(hex.slice(6, 8), 16) / 255)
65
+ : undefined;
66
+
67
+ return { red, green, blue, alpha };
68
+ }
69
+
70
+ function parseRgbColor(value: string): ParsedColor | null {
71
+ const match = value.match(RGB_COLOR);
72
+ if (!match) return null;
73
+
74
+ const parts = match[1].split(',').map((part) => part.trim());
75
+ if (parts.length < 3 || parts.length > 4) return null;
76
+
77
+ const red = Number(parts[0]);
78
+ const green = Number(parts[1]);
79
+ const blue = Number(parts[2]);
80
+ const alpha = parts[3] === undefined ? undefined : Number(parts[3]);
81
+
82
+ if ([red, green, blue, alpha].some((part) => part !== undefined && Number.isNaN(part))) {
83
+ return null;
84
+ }
85
+
86
+ return {
87
+ red: clampChannel(red),
88
+ green: clampChannel(green),
89
+ blue: clampChannel(blue),
90
+ alpha: alpha === undefined ? undefined : clampAlpha(alpha),
91
+ };
92
+ }
93
+
94
+ function parseColor(value: string): ParsedColor | null {
95
+ return parseHexColor(value) || parseRgbColor(value);
96
+ }
97
+
98
+ function darkenColor(value: string, amount = 0.2): string {
99
+ const parsed = parseColor(value);
100
+ if (!parsed) return value;
101
+
102
+ const factor = 1 - amount;
103
+ const next = {
104
+ red: clampChannel(parsed.red * factor),
105
+ green: clampChannel(parsed.green * factor),
106
+ blue: clampChannel(parsed.blue * factor),
107
+ alpha: parsed.alpha,
108
+ };
109
+
110
+ if (next.alpha === undefined) {
111
+ return `rgb(${next.red}, ${next.green}, ${next.blue})`;
112
+ }
113
+
114
+ return `rgba(${next.red}, ${next.green}, ${next.blue}, ${next.alpha})`;
115
+ }
116
+
117
+ const resolvedBorderColor = computed(() => {
118
+ if (props.borderColor) return props.borderColor;
119
+ return darkenColor(props.backgroundColor, 0.2);
120
+ });
121
+
122
+ const badgeStyle = computed(() => {
123
+ return {
124
+ backgroundColor: props.backgroundColor,
125
+ borderColor: resolvedBorderColor.value,
126
+ color: props.textColor,
127
+ };
128
+ });
129
+ </script>
130
+
131
+ <style scoped>
132
+ .tabler-badge {
133
+ border-style: solid;
134
+ border-width: 1px;
135
+ }
136
+ </style>
package/lib.ts CHANGED
@@ -10,6 +10,7 @@ export { default as TablerEnum } from './components/input/Enum.vue';
10
10
 
11
11
  export { default as TablerAlert } from './components/Alert.vue'
12
12
  export { default as TablerInlineAlert } from './components/InlineAlert.vue'
13
+ export { default as TablerBadge } from './components/Badge.vue'
13
14
  export { default as TablerButton } from './components/Button.vue'
14
15
  export { default as TablerRefreshButton } from './components/RefreshButton.vue'
15
16
  export { default as TablerIconButton } from './components/IconButton.vue'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tak-ps/vue-tabler",
3
3
  "type": "module",
4
- "version": "4.8.0",
4
+ "version": "4.9.0",
5
5
  "lib": "lib.ts",
6
6
  "main": "lib.ts",
7
7
  "module": "lib.ts",
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import Badge from '../components/Badge.vue'
4
+
5
+ describe('TablerBadge', () => {
6
+ it('renders the default slot content', () => {
7
+ const wrapper = mount(Badge, {
8
+ slots: {
9
+ default: 'Allowed',
10
+ },
11
+ })
12
+
13
+ expect(wrapper.get('span').text().trim()).toBe('Allowed')
14
+ })
15
+
16
+ it('applies the default badge colors', () => {
17
+ const wrapper = mount(Badge)
18
+ const badge = wrapper.get('span').element as HTMLSpanElement
19
+
20
+ expect(badge.style.backgroundColor).toBe('rgba(34, 197, 94, 0.2)')
21
+ expect(badge.style.borderColor).toBe('rgba(27, 158, 75, 0.2)')
22
+ expect(badge.style.color).toBe('rgb(255, 255, 255)')
23
+ })
24
+
25
+ it('derives a darker border color from the provided background color', () => {
26
+ const wrapper = mount(Badge, {
27
+ props: {
28
+ backgroundColor: '#336699',
29
+ },
30
+ })
31
+
32
+ const badge = wrapper.get('span').element as HTMLSpanElement
33
+
34
+ expect(badge.style.backgroundColor).toBe('rgb(51, 102, 153)')
35
+ expect(badge.style.borderColor).toBe('rgb(41, 82, 122)')
36
+ })
37
+
38
+ it('prefers explicit border and text colors when provided', () => {
39
+ const wrapper = mount(Badge, {
40
+ props: {
41
+ backgroundColor: 'rgba(1, 2, 3, 0.4)',
42
+ borderColor: '#123456',
43
+ textColor: '#abcdef',
44
+ },
45
+ })
46
+
47
+ const badge = wrapper.get('span').element as HTMLSpanElement
48
+
49
+ expect(badge.style.borderColor).toBe('rgb(18, 52, 86)')
50
+ expect(badge.style.color).toBe('rgb(171, 205, 239)')
51
+ })
52
+ })
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import Button from '../components/Button.vue'
4
+
5
+ describe('TablerButton', () => {
6
+ it('renders the default slot content', () => {
7
+ const wrapper = mount(Button, {
8
+ slots: {
9
+ default: 'Click Me',
10
+ },
11
+ })
12
+
13
+ expect(wrapper.get('button').text().trim()).toBe('Click Me')
14
+ })
15
+
16
+ it('sets the disabled attribute when disabled is true', () => {
17
+ const wrapper = mount(Button, {
18
+ props: {
19
+ disabled: true,
20
+ },
21
+ })
22
+
23
+ expect(wrapper.get('button').attributes('disabled')).toBeDefined()
24
+ expect((wrapper.get('button').element as HTMLButtonElement).disabled).toBe(true)
25
+ })
26
+
27
+ it('applies the provided background color', () => {
28
+ const wrapper = mount(Button, {
29
+ props: {
30
+ color: 'red',
31
+ },
32
+ })
33
+
34
+ expect((wrapper.get('button').element as HTMLButtonElement).style.backgroundColor).toBe('red')
35
+ })
36
+
37
+ it('does not apply a background color when color is omitted', () => {
38
+ const wrapper = mount(Button)
39
+
40
+ expect((wrapper.get('button').element as HTMLButtonElement).style.backgroundColor).toBe('')
41
+ })
42
+ })