@nexxtmove/ui 0.1.29 → 0.1.31

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/dist/nuxt.js CHANGED
@@ -1,25 +1,28 @@
1
- import { defineNuxtModule as s, addComponent as c, addTemplate as i } from "@nuxt/kit";
2
- const a = "components/AnimatedNumber/AnimatedNumber.vue", m = "components/Button/Button.vue", u = "components/Calendar/Calendar.vue", p = "components/Chip/Chip.vue", x = "components/CustomerJourneyTile/CustomerJourneyTile.vue", l = "components/DatePicker/DatePicker.vue", r = "components/DropdownButton/DropdownButton.vue", d = "components/Header/Header.vue", v = "components/Icon/Icon.vue", N = "components/InfoBlock/InfoBlock.vue", T = "components/ProgressBar/ProgressBar.vue", S = "components/SocialIcons/SocialIcons.vue", C = "components/SocialMediaCustomTemplate/SocialMediaCustomTemplate.vue", f = "components/SocialMediaTemplate/SocialMediaTemplate.vue", B = "components/SocialMediaType/SocialMediaType.vue", h = "components/StepperHeader/StepperHeader.vue", M = "components/TimelineEvent/TimelineEvent.vue", P = "components/TimelinePhaseblock/TimelinePhaseblock.vue", b = "components/Tooltip/Tooltip.vue", k = {
3
- NexxtAnimatedNumber: a,
4
- NexxtButton: m,
1
+ import { defineNuxtModule as c, addComponent as s, addTemplate as a } from "@nuxt/kit";
2
+ const i = "components/AnimatedNumber/AnimatedNumber.vue", m = "components/Avatar/Avatar.vue", x = "components/Button/Button.vue", u = "components/Calendar/Calendar.vue", p = "components/Checkbox/Checkbox.vue", l = "components/Chip/Chip.vue", r = "components/CustomerJourneyTile/CustomerJourneyTile.vue", d = "components/DatePicker/DatePicker.vue", v = "components/DropdownButton/DropdownButton.vue", N = "components/Header/Header.vue", T = "components/Icon/Icon.vue", C = "components/InfoBlock/InfoBlock.vue", S = "components/ProgressBar/ProgressBar.vue", b = "components/SocialIcons/SocialIcons.vue", f = "components/SocialMediaCustomTemplate/SocialMediaCustomTemplate.vue", h = "components/SocialMediaTemplate/SocialMediaTemplate.vue", k = "components/SocialMediaType/SocialMediaType.vue", B = "components/StepperHeader/StepperHeader.vue", M = "components/Table/Table.vue", P = "components/TimelineEvent/TimelineEvent.vue", I = "components/TimelinePhaseblock/TimelinePhaseblock.vue", y = "components/Tooltip/Tooltip.vue", A = {
3
+ NexxtAnimatedNumber: i,
4
+ NexxtAvatar: m,
5
+ NexxtButton: x,
5
6
  NexxtCalendar: u,
6
- NexxtChip: p,
7
- NexxtCustomerJourneyTile: x,
8
- NexxtDatePicker: l,
9
- NexxtDropdownButton: r,
10
- NexxtHeader: d,
11
- NexxtIcon: v,
12
- NexxtInfoBlock: N,
13
- NexxtProgressBar: T,
14
- NexxtSocialIcons: S,
15
- NexxtSocialMediaCustomTemplate: C,
16
- NexxtSocialMediaTemplate: f,
17
- NexxtSocialMediaType: B,
18
- NexxtStepperHeader: h,
19
- NexxtTimelineEvent: M,
20
- NexxtTimelinePhaseblock: P,
21
- NexxtTooltip: b
22
- }, y = s({
7
+ NexxtCheckbox: p,
8
+ NexxtChip: l,
9
+ NexxtCustomerJourneyTile: r,
10
+ NexxtDatePicker: d,
11
+ NexxtDropdownButton: v,
12
+ NexxtHeader: N,
13
+ NexxtIcon: T,
14
+ NexxtInfoBlock: C,
15
+ NexxtProgressBar: S,
16
+ NexxtSocialIcons: b,
17
+ NexxtSocialMediaCustomTemplate: f,
18
+ NexxtSocialMediaTemplate: h,
19
+ NexxtSocialMediaType: k,
20
+ NexxtStepperHeader: B,
21
+ NexxtTable: M,
22
+ NexxtTimelineEvent: P,
23
+ NexxtTimelinePhaseblock: I,
24
+ NexxtTooltip: y
25
+ }, H = c({
23
26
  meta: {
24
27
  name: "@nexxtmove/ui",
25
28
  configKey: "nexxtmoveUi",
@@ -31,13 +34,13 @@ const a = "components/AnimatedNumber/AnimatedNumber.vue", m = "components/Button
31
34
  addCSS: !0
32
35
  },
33
36
  setup(e, o) {
34
- for (const [t, n] of Object.entries(k))
35
- c({
37
+ for (const [t, n] of Object.entries(A))
38
+ s({
36
39
  name: t,
37
40
  export: "default",
38
41
  filePath: `@nexxtmove/ui/${n}`
39
42
  });
40
- e.addCSS && (i({
43
+ e.addCSS && (a({
41
44
  filename: "nexxtmove-ui.css",
42
45
  getContents: () => `
43
46
  @import "tailwindcss";
@@ -50,5 +53,5 @@ const a = "components/AnimatedNumber/AnimatedNumber.vue", m = "components/Button
50
53
  }
51
54
  });
52
55
  export {
53
- y as default
56
+ H as default
54
57
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nexxtmove/ui",
3
3
  "type": "module",
4
- "version": "0.1.29",
4
+ "version": "0.1.31",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
@@ -0,0 +1,28 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite'
2
+ import Avatar from './Avatar.vue'
3
+
4
+ export default {
5
+ title: 'Components/Atoms/Avatar',
6
+ component: Avatar,
7
+ parameters: {
8
+ design: {
9
+ type: 'figma',
10
+ url: 'https://www.figma.com/design/CdbFJ7qUga6mtjagcPDvYK/Design-System',
11
+ },
12
+ },
13
+ argTypes: {
14
+ size: {
15
+ control: 'select',
16
+ options: ['xs', 'sm', 'md', 'lg', 'xl'],
17
+ },
18
+ },
19
+ } satisfies Meta<typeof Avatar>
20
+
21
+ type Story = StoryObj<typeof Avatar>
22
+
23
+ export const Default: Story = {
24
+ args: {
25
+ name: 'Peter van der Sloot',
26
+ size: 'sm',
27
+ },
28
+ }
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import Avatar from './Avatar.vue'
4
+
5
+ describe('Avatar', () => {
6
+ it('generates 2 character initials from name with multiple words', () => {
7
+ const wrapper = mount(Avatar, {
8
+ props: {
9
+ name: 'Peter Vink',
10
+ },
11
+ })
12
+
13
+ expect(wrapper.text()).toContain('PV')
14
+ })
15
+
16
+ it('generates 2 character initials from name with single word', () => {
17
+ const wrapper = mount(Avatar, {
18
+ props: {
19
+ name: 'Peter',
20
+ },
21
+ })
22
+
23
+ expect(wrapper.text()).toContain('PE')
24
+ })
25
+
26
+ it('generates 2 character initials picking first and last words for names with tussenvoegsels', () => {
27
+ const wrapper = mount(Avatar, {
28
+ props: {
29
+ name: 'Peter van der Sloot',
30
+ },
31
+ })
32
+
33
+ expect(wrapper.text()).toContain('PS')
34
+ })
35
+
36
+ it('handles empty strings gracefully by returning no text', () => {
37
+ const wrapper = mount(Avatar, {
38
+ props: {
39
+ name: '',
40
+ },
41
+ })
42
+
43
+ expect(wrapper.text()).toBe('')
44
+ })
45
+
46
+ it('handles whitespace-only strings gracefully by returning no text', () => {
47
+ const wrapper = mount(Avatar, {
48
+ props: {
49
+ name: ' ',
50
+ },
51
+ })
52
+
53
+ expect(wrapper.text()).toBe('')
54
+ })
55
+ })
@@ -0,0 +1,49 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+
4
+ interface NexxtAvatarProps {
5
+ name: string
6
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
7
+ }
8
+
9
+ defineOptions({
10
+ name: 'NexxtAvatar',
11
+ })
12
+
13
+ const props = withDefaults(defineProps<NexxtAvatarProps>(), {
14
+ size: 'sm',
15
+ })
16
+
17
+ const computedInitials = computed(() => {
18
+ if (!props.name?.trim()) return ''
19
+
20
+ const parts = props.name.trim().split(/\s+/)
21
+ if (parts.length >= 2) {
22
+ const firstInitial = parts[0][0]
23
+ const lastInitial = parts[parts.length - 1][0]
24
+
25
+ return (firstInitial + lastInitial).toUpperCase()
26
+ }
27
+
28
+ return parts[0].substring(0, 2).toUpperCase()
29
+ })
30
+
31
+ const sizeClasses = {
32
+ xs: 'h-6 w-6 text-[10px]',
33
+ sm: 'h-8 w-8 text-sm',
34
+ md: 'h-10 w-10 text-base',
35
+ lg: 'h-12 w-12 text-lg',
36
+ xl: 'h-16 w-16 text-xl',
37
+ }
38
+ </script>
39
+
40
+ <template>
41
+ <div
42
+ :class="[
43
+ 'relative flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-cornflower-blue-50 font-semibold text-cornflower-blue-500',
44
+ sizeClasses[size],
45
+ ]"
46
+ >
47
+ {{ computedInitials }}
48
+ </div>
49
+ </template>
@@ -0,0 +1,35 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite'
2
+ import Checkbox from './Checkbox.vue'
3
+
4
+ export default {
5
+ title: 'Components/Atoms/Checkbox',
6
+ component: Checkbox,
7
+ } satisfies Meta<typeof Checkbox>
8
+
9
+ type Story = StoryObj<typeof Checkbox>
10
+
11
+ export const Default: Story = {
12
+ args: {
13
+ modelValue: false,
14
+ },
15
+ }
16
+
17
+ export const Checked: Story = {
18
+ args: {
19
+ modelValue: true,
20
+ },
21
+ }
22
+
23
+ export const Disabled: Story = {
24
+ args: {
25
+ modelValue: false,
26
+ disabled: true,
27
+ },
28
+ }
29
+
30
+ export const DisabledChecked: Story = {
31
+ args: {
32
+ modelValue: true,
33
+ disabled: true,
34
+ },
35
+ }
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import Checkbox from './Checkbox.vue'
4
+
5
+ describe('Checkbox', () => {
6
+ it('renders a checkbox input', () => {
7
+ const wrapper = mount(Checkbox)
8
+
9
+ expect(wrapper.find('input[type="checkbox"]').exists()).toBe(true)
10
+ })
11
+
12
+ it('is unchecked by default', () => {
13
+ const wrapper = mount(Checkbox)
14
+
15
+ expect((wrapper.find('input').element as HTMLInputElement).checked).toBe(false)
16
+ })
17
+
18
+ it('is checked when modelValue is true', () => {
19
+ const wrapper = mount(Checkbox, { props: { modelValue: true } })
20
+
21
+ expect((wrapper.find('input').element as HTMLInputElement).checked).toBe(true)
22
+ })
23
+
24
+ it('emits update:modelValue when toggled', async () => {
25
+ const wrapper = mount(Checkbox, { props: { modelValue: false } })
26
+
27
+ await wrapper.find('input').setValue(true)
28
+
29
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy()
30
+ })
31
+
32
+ it('disables the input when disabled prop is true', () => {
33
+ const wrapper = mount(Checkbox, { props: { disabled: true } })
34
+
35
+ expect((wrapper.find('input').element as HTMLInputElement).disabled).toBe(true)
36
+ })
37
+
38
+ it('applies opacity class when disabled', () => {
39
+ const wrapper = mount(Checkbox, { props: { disabled: true } })
40
+
41
+ expect(wrapper.find('label').classes()).toContain('opacity-50')
42
+ })
43
+
44
+ it('forwards attributes to the input', () => {
45
+ const wrapper = mount(Checkbox, { attrs: { 'aria-label': 'Accept terms' } })
46
+
47
+ expect(wrapper.find('input').attributes('aria-label')).toBe('Accept terms')
48
+ })
49
+
50
+ it('renders the check icon', () => {
51
+ const wrapper = mount(Checkbox)
52
+
53
+ expect(wrapper.find('i.fa-check').exists()).toBe(true)
54
+ })
55
+ })
@@ -0,0 +1,46 @@
1
+ <script setup lang="ts">
2
+ import { ref, watchEffect } from 'vue'
3
+ import Icon from '../Icon/Icon.vue'
4
+
5
+ interface NexxtCheckboxProps {
6
+ disabled?: boolean
7
+ indeterminate?: boolean
8
+ }
9
+
10
+ defineOptions({
11
+ name: 'NexxtCheckbox',
12
+ })
13
+
14
+ const model = defineModel<boolean>()
15
+ const { indeterminate } = defineProps<NexxtCheckboxProps>()
16
+
17
+ const inputRef = ref<HTMLInputElement>()
18
+ watchEffect(() => {
19
+ if (inputRef.value) inputRef.value.indeterminate = indeterminate ?? false
20
+ })
21
+ </script>
22
+
23
+ <template>
24
+ <label :class="['inline-flex cursor-pointer', disabled ? 'cursor-not-allowed opacity-50' : '']">
25
+ <input
26
+ v-bind="$attrs"
27
+ ref="inputRef"
28
+ type="checkbox"
29
+ class="peer sr-only"
30
+ v-model="model"
31
+ :disabled="disabled"
32
+ />
33
+ <span
34
+ aria-hidden="true"
35
+ :class="[
36
+ 'flex h-5 w-5 shrink-0 items-center justify-center rounded border transition-colors duration-200',
37
+ 'peer-focus-visible:ring-primary peer-focus-visible:ring-2 peer-focus-visible:ring-offset-2',
38
+ model || indeterminate
39
+ ? 'border-primary text-cornflower-blue-600'
40
+ : 'border-gray-300 text-transparent',
41
+ ]"
42
+ >
43
+ <Icon :name="indeterminate ? 'minus' : 'check'" type="solid" class="text-xs" />
44
+ </span>
45
+ </label>
46
+ </template>
@@ -0,0 +1,283 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite'
2
+ import { computed, ref, type ConcreteComponent } from 'vue'
3
+ import Table, { type TableColumn } from './Table.vue'
4
+
5
+ type RowKey = 'name' | 'email' | 'role' | 'status' | 'department' | 'lastLogin'
6
+
7
+ interface TableStoryProps {
8
+ columns: TableColumn<RowKey>[]
9
+ rows: Record<RowKey, string>[]
10
+ selectable?: boolean
11
+ }
12
+
13
+ const meta: Meta<TableStoryProps> = {
14
+ title: 'Components/Atoms/Table',
15
+ component: Table as ConcreteComponent<TableStoryProps>,
16
+ }
17
+
18
+ export default meta
19
+ type Story = StoryObj<TableStoryProps>
20
+
21
+ const columns: TableColumn<RowKey>[] = [
22
+ { key: 'name', label: 'Name', sortable: true },
23
+ { key: 'email', label: 'Email', sortable: true },
24
+ { key: 'department', label: 'Department' },
25
+ { key: 'role', label: 'Role', sortable: true, width: '0.5fr' },
26
+ { key: 'lastLogin', label: 'lastLogin' },
27
+ { key: 'status', label: 'Status', width: '0.5fr' },
28
+ ]
29
+
30
+ const allRows = [
31
+ {
32
+ name: 'Alice Johnson',
33
+ email: 'alice@example.com',
34
+ role: 'Admin',
35
+ status: 'Active',
36
+ department: 'HR',
37
+ lastLogin: '2026-03-25',
38
+ },
39
+ {
40
+ name: 'Bob Smith',
41
+ email: 'bob@example.com',
42
+ role: 'Editor',
43
+ status: 'Active',
44
+ department: 'Marketing',
45
+ lastLogin: '2026-03-27',
46
+ },
47
+ {
48
+ name: 'Carol White',
49
+ email: 'carol@example.com',
50
+ role: 'Viewer',
51
+ status: 'Inactive',
52
+ department: 'Finance',
53
+ lastLogin: '2026-02-12',
54
+ },
55
+ {
56
+ name: 'David Brown',
57
+ email: 'david@example.com',
58
+ role: 'Editor',
59
+ status: 'Active',
60
+ department: 'IT',
61
+ lastLogin: '2026-03-30',
62
+ },
63
+ {
64
+ name: 'Eve Turner',
65
+ email: 'eve@example.com',
66
+ role: 'Admin',
67
+ status: 'Active',
68
+ department: 'HR',
69
+ lastLogin: '2026-03-28',
70
+ },
71
+ {
72
+ name: 'Frank Miller',
73
+ email: 'frank@example.com',
74
+ role: 'Viewer',
75
+ status: 'Active',
76
+ department: 'Support',
77
+ lastLogin: '2026-03-26',
78
+ },
79
+ {
80
+ name: 'Grace Lee',
81
+ email: 'grace@example.com',
82
+ role: 'Editor',
83
+ status: 'Active',
84
+ department: 'Product',
85
+ lastLogin: '2026-03-29',
86
+ },
87
+ {
88
+ name: 'Henry Wilson',
89
+ email: 'henry@example.com',
90
+ role: 'Viewer',
91
+ status: 'Inactive',
92
+ department: 'Finance',
93
+ lastLogin: '2026-01-10',
94
+ },
95
+ {
96
+ name: 'Ivy Clark',
97
+ email: 'ivy@example.com',
98
+ role: 'Admin',
99
+ status: 'Active',
100
+ department: 'IT',
101
+ lastLogin: '2026-03-20',
102
+ },
103
+ {
104
+ name: 'Jack Davis',
105
+ email: 'jack@example.com',
106
+ role: 'Editor',
107
+ status: 'Active',
108
+ department: 'Marketing',
109
+ lastLogin: '2026-03-23',
110
+ },
111
+ {
112
+ name: 'Karen Harris',
113
+ email: 'karen@example.com',
114
+ role: 'Viewer',
115
+ status: 'Active',
116
+ department: 'Support',
117
+ lastLogin: '2026-03-19',
118
+ },
119
+ {
120
+ name: 'Leo King',
121
+ email: 'leo@example.com',
122
+ role: 'Editor',
123
+ status: 'Active',
124
+ department: 'Product',
125
+ lastLogin: '2026-03-28',
126
+ },
127
+ {
128
+ name: 'Mia Scott',
129
+ email: 'mia@example.com',
130
+ role: 'Admin',
131
+ status: 'Active',
132
+ department: 'HR',
133
+ lastLogin: '2026-03-30',
134
+ },
135
+ {
136
+ name: 'Nick Evans',
137
+ email: 'nick@example.com',
138
+ role: 'Viewer',
139
+ status: 'Inactive',
140
+ department: 'Finance',
141
+ lastLogin: '2026-02-05',
142
+ },
143
+ {
144
+ name: 'Olivia Foster',
145
+ email: 'olivia@example.com',
146
+ role: 'Editor',
147
+ status: 'Active',
148
+ department: 'Product',
149
+ lastLogin: '2026-03-27',
150
+ },
151
+ {
152
+ name: 'Paul Green',
153
+ email: 'paul@example.com',
154
+ role: 'Viewer',
155
+ status: 'Active',
156
+ department: 'Support',
157
+ lastLogin: '2026-03-18',
158
+ },
159
+ {
160
+ name: 'Quincy Adams',
161
+ email: 'quincy@example.com',
162
+ role: 'Editor',
163
+ status: 'Active',
164
+ department: 'Marketing',
165
+ lastLogin: '2026-03-25',
166
+ },
167
+ {
168
+ name: 'Rachel Lewis',
169
+ email: 'rachel@example.com',
170
+ role: 'Admin',
171
+ status: 'Active',
172
+ department: 'HR',
173
+ lastLogin: '2026-03-29',
174
+ },
175
+ {
176
+ name: 'Sam Nelson',
177
+ email: 'sam@example.com',
178
+ role: 'Viewer',
179
+ status: 'Inactive',
180
+ department: 'Finance',
181
+ lastLogin: '2026-01-15',
182
+ },
183
+ {
184
+ name: 'Tina Brooks',
185
+ email: 'tina@example.com',
186
+ role: 'Editor',
187
+ status: 'Active',
188
+ department: 'IT',
189
+ lastLogin: '2026-03-30',
190
+ },
191
+ ]
192
+
193
+ const sortRows = (rows: typeof allRows, key: string | undefined, direction: 'asc' | 'desc') => {
194
+ if (!key) return rows
195
+ return [...rows].sort((a, b) => {
196
+ const aVal = String(a[key as keyof typeof a] ?? '')
197
+ const bVal = String(b[key as keyof typeof b] ?? '')
198
+ const result = aVal.localeCompare(bVal, undefined, { numeric: true, sensitivity: 'base' })
199
+ return direction === 'asc' ? result : -result
200
+ })
201
+ }
202
+
203
+ export const Default: Story = {
204
+ args: { columns, rows: allRows },
205
+ render: (args) => ({
206
+ components: { Table },
207
+ setup() {
208
+ const sortKey = ref<string | undefined>(undefined)
209
+ const sortDirection = ref<'asc' | 'desc'>('asc')
210
+ const rows = computed(() =>
211
+ sortRows(args.rows as typeof allRows, sortKey.value, sortDirection.value),
212
+ )
213
+ const onSort = (key: string, direction: 'asc' | 'desc') => {
214
+ sortKey.value = key
215
+ sortDirection.value = direction
216
+ }
217
+ return { args, rows, sortKey, sortDirection, onSort }
218
+ },
219
+ template: `<Table :columns="args.columns" :rows="rows" :sort-key="sortKey" :sort-direction="sortDirection" @sort="onSort" />`,
220
+ }),
221
+ }
222
+
223
+ export const WithSelection: Story = {
224
+ args: { columns, rows: allRows, selectable: true },
225
+ render: (args) => ({
226
+ components: { Table },
227
+ setup() {
228
+ const sortKey = ref<string | undefined>(undefined)
229
+ const sortDirection = ref<'asc' | 'desc'>('asc')
230
+ const selected = ref<number[]>([])
231
+ const rows = computed(() =>
232
+ sortRows(args.rows as typeof allRows, sortKey.value, sortDirection.value),
233
+ )
234
+ const onSort = (key: string, direction: 'asc' | 'desc') => {
235
+ sortKey.value = key
236
+ sortDirection.value = direction
237
+ }
238
+ return { args, rows, sortKey, sortDirection, onSort, selected }
239
+ },
240
+ template: `<Table :columns="args.columns" :rows="rows" :sort-key="sortKey" :sort-direction="sortDirection" :selectable="args.selectable" v-model="selected" @sort="onSort" />`,
241
+ }),
242
+ }
243
+
244
+ export const WithSlots: Story = {
245
+ args: { columns, rows: allRows },
246
+ render: (args) => ({
247
+ components: { Table },
248
+ setup() {
249
+ const sortKey = ref<string | undefined>(undefined)
250
+ const sortDirection = ref<'asc' | 'desc'>('asc')
251
+ const rows = computed(() =>
252
+ sortRows(args.rows as typeof allRows, sortKey.value, sortDirection.value),
253
+ )
254
+ const onSort = (key: string, direction: 'asc' | 'desc') => {
255
+ sortKey.value = key
256
+ sortDirection.value = direction
257
+ }
258
+ return { args, rows, sortKey, sortDirection, onSort }
259
+ },
260
+ template: `
261
+ <Table :columns="args.columns" :rows="rows" :sort-key="sortKey" :sort-direction="sortDirection" @sort="onSort">
262
+ <template #cell-name="{ value, row }">
263
+ <div class="flex flex-col">
264
+ <span class="font-medium">{{ value }}</span>
265
+ <span class="text-xs text-slate-400">{{ row.email }}</span>
266
+ </div>
267
+ </template>
268
+ <template #cell-status="{ value }">
269
+ <span
270
+ class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium"
271
+ :class="value === 'Active' ? 'bg-green-100 text-green-700' : 'bg-slate-100 text-slate-500'"
272
+ >
273
+ {{ value }}
274
+ </span>
275
+ </template>
276
+ </Table>
277
+ `,
278
+ }),
279
+ }
280
+
281
+ export const Empty: Story = {
282
+ args: { columns, rows: [] },
283
+ }