@nexxtmove/ui 0.1.29 → 0.1.30

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,27 @@
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 = {
1
+ import { defineNuxtModule as c, addComponent as s, addTemplate as i } from "@nuxt/kit";
2
+ const a = "components/AnimatedNumber/AnimatedNumber.vue", m = "components/Button/Button.vue", x = "components/Calendar/Calendar.vue", u = "components/Checkbox/Checkbox.vue", p = "components/Chip/Chip.vue", l = "components/CustomerJourneyTile/CustomerJourneyTile.vue", r = "components/DatePicker/DatePicker.vue", d = "components/DropdownButton/DropdownButton.vue", v = "components/Header/Header.vue", N = "components/Icon/Icon.vue", T = "components/InfoBlock/InfoBlock.vue", C = "components/ProgressBar/ProgressBar.vue", S = "components/SocialIcons/SocialIcons.vue", b = "components/SocialMediaCustomTemplate/SocialMediaCustomTemplate.vue", f = "components/SocialMediaTemplate/SocialMediaTemplate.vue", h = "components/SocialMediaType/SocialMediaType.vue", k = "components/StepperHeader/StepperHeader.vue", B = "components/Table/Table.vue", M = "components/TimelineEvent/TimelineEvent.vue", P = "components/TimelinePhaseblock/TimelinePhaseblock.vue", I = "components/Tooltip/Tooltip.vue", y = {
3
3
  NexxtAnimatedNumber: a,
4
4
  NexxtButton: m,
5
- NexxtCalendar: u,
5
+ NexxtCalendar: x,
6
+ NexxtCheckbox: u,
6
7
  NexxtChip: p,
7
- NexxtCustomerJourneyTile: x,
8
- NexxtDatePicker: l,
9
- NexxtDropdownButton: r,
10
- NexxtHeader: d,
11
- NexxtIcon: v,
12
- NexxtInfoBlock: N,
13
- NexxtProgressBar: T,
8
+ NexxtCustomerJourneyTile: l,
9
+ NexxtDatePicker: r,
10
+ NexxtDropdownButton: d,
11
+ NexxtHeader: v,
12
+ NexxtIcon: N,
13
+ NexxtInfoBlock: T,
14
+ NexxtProgressBar: C,
14
15
  NexxtSocialIcons: S,
15
- NexxtSocialMediaCustomTemplate: C,
16
+ NexxtSocialMediaCustomTemplate: b,
16
17
  NexxtSocialMediaTemplate: f,
17
- NexxtSocialMediaType: B,
18
- NexxtStepperHeader: h,
18
+ NexxtSocialMediaType: h,
19
+ NexxtStepperHeader: k,
20
+ NexxtTable: B,
19
21
  NexxtTimelineEvent: M,
20
22
  NexxtTimelinePhaseblock: P,
21
- NexxtTooltip: b
22
- }, y = s({
23
+ NexxtTooltip: I
24
+ }, H = c({
23
25
  meta: {
24
26
  name: "@nexxtmove/ui",
25
27
  configKey: "nexxtmoveUi",
@@ -31,8 +33,8 @@ const a = "components/AnimatedNumber/AnimatedNumber.vue", m = "components/Button
31
33
  addCSS: !0
32
34
  },
33
35
  setup(e, o) {
34
- for (const [t, n] of Object.entries(k))
35
- c({
36
+ for (const [t, n] of Object.entries(y))
37
+ s({
36
38
  name: t,
37
39
  export: "default",
38
40
  filePath: `@nexxtmove/ui/${n}`
@@ -50,5 +52,5 @@ const a = "components/AnimatedNumber/AnimatedNumber.vue", m = "components/Button
50
52
  }
51
53
  });
52
54
  export {
53
- y as default
55
+ H as default
54
56
  };
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.30",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
@@ -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
+ }