@katlux/block-ecommerce 0.1.0-beta.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.
@@ -0,0 +1,4 @@
1
+ export default {
2
+ failOnWarn: false,
3
+ externals: ['#app', '@katlux/toolkit', '@katlux/providers']
4
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@katlux/block-ecommerce",
3
+ "version": "0.1.0-beta.0",
4
+ "description": "Pre-built eCommerce block components for Katlux toolkit",
5
+ "author": "Katlux",
6
+ "license": "MIT",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "main": "./dist/module.mjs",
11
+ "scripts": {
12
+ "build": "nuxt-module-build build",
13
+ "dev": "nuxt-module-build build --stub"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/types.d.ts",
18
+ "import": "./dist/module.mjs",
19
+ "require": "./dist/module.cjs"
20
+ },
21
+ "./package.json": "./package.json",
22
+ "./components/*": "./dist/runtime/components/*"
23
+ },
24
+ "dependencies": {
25
+ "@katlux/providers": "*",
26
+ "@katlux/toolkit": "*",
27
+ "@nuxt/kit": "^3.20.1",
28
+ "pug": "^3.0.0",
29
+ "sass": "^1.80.0"
30
+ }
31
+ }
package/src/module.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { defineNuxtModule, createResolver, addComponentsDir } from '@nuxt/kit'
2
+
3
+ export interface ModuleOptions { }
4
+
5
+ export default defineNuxtModule<ModuleOptions>({
6
+ meta: {
7
+ name: '@katlux/block-ecommerce',
8
+ configKey: 'katluxEcommerceBlocks',
9
+ compatibility: {
10
+ nuxt: '^3.0.0'
11
+ }
12
+ },
13
+ defaults: {},
14
+ async setup(options, nuxt) {
15
+ const resolver = createResolver(import.meta.url)
16
+
17
+ // Add components directory
18
+ addComponentsDir({
19
+ path: resolver.resolve('./runtime/components'),
20
+ pathPrefix: false,
21
+ prefix: '',
22
+ global: true
23
+ })
24
+ }
25
+ })
@@ -0,0 +1,64 @@
1
+ import type { ADataProvider, KProductItemData, KProductRowAction } from '@katlux/providers'
2
+ import { computed, type PropType } from 'vue'
3
+
4
+ export interface KProductListProps {
5
+ dataProvider: ADataProvider
6
+ layout?: 'grid' | 'list'
7
+ rowActions?: KProductRowAction[]
8
+ gridColumns?: number | string
9
+ gap?: string
10
+ }
11
+
12
+ export const KProductListDefaultProps = {
13
+ dataProvider: {
14
+ type: Object as PropType<ADataProvider>,
15
+ required: true,
16
+ },
17
+ layout: {
18
+ type: String as PropType<'grid' | 'list'>,
19
+ default: 'grid',
20
+ },
21
+ rowActions: {
22
+ type: Array as PropType<KProductRowAction[]>,
23
+ default: () => [],
24
+ },
25
+ gridColumns: {
26
+ type: [Number, String],
27
+ default: 4,
28
+ },
29
+ gap: {
30
+ type: String,
31
+ default: '1rem',
32
+ }
33
+ }
34
+
35
+ export function useKProductListLogic(props: KProductListProps) {
36
+ const isGrid = computed(() => props.layout !== 'list')
37
+
38
+ const gridStyles = computed(() => {
39
+ if (!isGrid.value) return {}
40
+
41
+ const cols = props.gridColumns
42
+ return {
43
+ display: 'grid',
44
+ gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
45
+ gap: props.gap
46
+ }
47
+ })
48
+
49
+ const listStyles = computed(() => {
50
+ if (isGrid.value) return {}
51
+
52
+ return {
53
+ display: 'flex',
54
+ flexDirection: 'column' as const,
55
+ gap: props.gap
56
+ }
57
+ })
58
+
59
+ return {
60
+ isGrid,
61
+ gridStyles,
62
+ listStyles
63
+ }
64
+ }
@@ -0,0 +1,70 @@
1
+ <template>
2
+ <div class="k-product-list" :class="{ 'is-grid': isGrid, 'is-list': !isGrid }">
3
+ <!-- If there's a custom template provided via default slot -->
4
+ <template v-if="$slots.default">
5
+ <div class="list-container" :style="isGrid ? gridStyles : listStyles">
6
+ <slot v-for="(item, index) in props.dataProvider.pageData.value" :item="item" :index="index"></slot>
7
+ </div>
8
+ </template>
9
+
10
+ <!-- Otherwise, use the default KProductListItem -->
11
+ <template v-else>
12
+ <div class="list-container" :style="isGrid ? gridStyles : listStyles">
13
+ <KProductListItem
14
+ v-for="(item, index) in props.dataProvider.pageData.value"
15
+ :key="item.id || index"
16
+ :item="item"
17
+ :row-actions="rowActions"
18
+ >
19
+ <template v-for="(_, slot) in $slots" #[slot]="scope">
20
+ <slot :name="slot" v-bind="scope"></slot>
21
+ </template>
22
+ </KProductListItem>
23
+ </div>
24
+ </template>
25
+
26
+ <div class="footer">
27
+ <KPagination class="pagination" :dataProvider="props.dataProvider" />
28
+ </div>
29
+ </div>
30
+ </template>
31
+
32
+ <script lang="ts" setup>
33
+ import type { KProductItemData, KProductRowAction } from '@katlux/providers'
34
+ import { ADataProvider } from '@katlux/providers'
35
+ import { useKProductListLogic } from './KProductList.logic'
36
+
37
+ const props = withDefaults(defineProps<{
38
+ dataProvider: ADataProvider
39
+ layout?: 'grid' | 'list'
40
+ rowActions?: KProductRowAction[]
41
+ gridColumns?: number | string
42
+ gap?: string
43
+ }>(), {
44
+ layout: 'grid',
45
+ rowActions: () => [],
46
+ gridColumns: 4,
47
+ gap: '1rem'
48
+ })
49
+
50
+ const { isGrid, gridStyles, listStyles } = useKProductListLogic(props as any)
51
+ </script>
52
+
53
+ <style lang="scss" scoped>
54
+ .k-product-list {
55
+ width: 100%;
56
+
57
+ .list-container {
58
+ width: 100%;
59
+ }
60
+
61
+ .footer {
62
+ display: flex;
63
+ justify-content: flex-end;
64
+ margin-top: var(--gap-lg, 24px);
65
+ .pagination {
66
+ flex: 1;
67
+ }
68
+ }
69
+ }
70
+ </style>
@@ -0,0 +1,36 @@
1
+ import { computed, type PropType } from 'vue'
2
+ import type { KProductItemData, KProductRowAction } from '@katlux/providers'
3
+
4
+
5
+ export interface KProductListItemProps {
6
+ item: KProductItemData
7
+ rowActions?: KProductRowAction[]
8
+ }
9
+
10
+ export const KProductListItemDefaultProps = {
11
+ item: {
12
+ type: Object as PropType<KProductItemData>,
13
+ required: true as const,
14
+ },
15
+ rowActions: {
16
+ type: Array as PropType<KProductRowAction[]>,
17
+ default: () => [],
18
+ },
19
+ }
20
+
21
+ export function useKProductListItemLogic(props: KProductListItemProps) {
22
+ const hasActions = computed(() => props.rowActions && props.rowActions.length > 0)
23
+
24
+ const formattedPrice = computed(() => {
25
+ if (props.item.price === undefined) return ''
26
+ return new Intl.NumberFormat('en-US', {
27
+ style: 'currency',
28
+ currency: props.item.currency || 'USD'
29
+ }).format(props.item.price)
30
+ })
31
+
32
+ return {
33
+ hasActions,
34
+ formattedPrice
35
+ }
36
+ }
@@ -0,0 +1,129 @@
1
+ <template lang="pug">
2
+ .k-product-list-item
3
+ .item-image(v-if="item.image")
4
+ img(:src="item.image" :alt="item.title")
5
+ .item-image-placeholder(v-else)
6
+ KIcon(name="tabler:photo" class="placeholder-icon")
7
+ .item-details
8
+ h3.item-title {{ item.title }}
9
+ p.item-description(v-if="item.description") {{ item.description }}
10
+ .item-price(v-if="formattedPrice") {{ formattedPrice }}
11
+ .item-actions(v-if="hasActions")
12
+ KButton(
13
+ v-for="(action, index) in rowActions"
14
+ :key="index"
15
+ :class="action.color || 'primary'"
16
+ @click="action.action(item)"
17
+ )
18
+ template(v-if="action.icon" #icon)
19
+ KIcon(:name="action.icon")
20
+ | {{ action.label }}
21
+ </template>
22
+
23
+ <script lang="ts" setup>
24
+ import type { KProductItemData, KProductRowAction } from '@katlux/providers'
25
+
26
+ const props = defineProps<{
27
+ item: KProductItemData
28
+ rowActions?: KProductRowAction[]
29
+ }>()
30
+
31
+ import { useKProductListItemLogic } from './KProductListItem.logic'
32
+ const { hasActions, formattedPrice } = useKProductListItemLogic(props)
33
+ </script>
34
+
35
+ <style lang="scss" scoped>
36
+ .k-product-list-item {
37
+ display: flex;
38
+ flex-direction: column;
39
+ border: 1px solid var(--border-color-light, #e9ecef);
40
+ border-radius: var(--border-radius-md, 8px);
41
+ overflow: hidden;
42
+ background: var(--bg-color-surface, #ffffff);
43
+ transition: box-shadow 0.2s ease, transform 0.2s ease;
44
+ height: 100%;
45
+
46
+ &:hover {
47
+ box-shadow: var(--shadow-md, 0 4px 12px rgba(0,0,0,0.08));
48
+ transform: translateY(-2px);
49
+ }
50
+
51
+ .item-image, .item-image-placeholder {
52
+ width: 100%;
53
+ aspect-ratio: 1;
54
+ overflow: hidden;
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ background: var(--bg-color-muted, #f8f9fa);
59
+ border-bottom: 1px solid var(--border-color-light, #e9ecef);
60
+ }
61
+
62
+ .item-image img {
63
+ width: 100%;
64
+ height: 100%;
65
+ object-fit: cover;
66
+ transition: transform 0.3s ease;
67
+ }
68
+
69
+ &:hover .item-image img {
70
+ transform: scale(1.05);
71
+ }
72
+
73
+ .item-image-placeholder {
74
+ .placeholder-icon {
75
+ font-size: 3rem;
76
+ color: var(--text-color-muted, #adb5bd);
77
+ opacity: 0.5;
78
+ }
79
+ }
80
+
81
+ .item-details {
82
+ padding: var(--gap-md, 16px);
83
+ flex-grow: 1;
84
+ display: flex;
85
+ flex-direction: column;
86
+ }
87
+
88
+ .item-title {
89
+ margin: 0 0 var(--gap-xs, 4px) 0;
90
+ font-size: var(--font-size-lg, 1.1rem);
91
+ font-weight: 600;
92
+ color: var(--text-color-primary, #212529);
93
+ line-height: 1.3;
94
+ }
95
+
96
+ .item-description {
97
+ margin: 0 0 var(--gap-sm, 8px) 0;
98
+ font-size: var(--font-size-sm, 0.9rem);
99
+ color: var(--text-color-secondary, #6c757d);
100
+ line-height: 1.4;
101
+ flex-grow: 1;
102
+ display: -webkit-box;
103
+ -webkit-line-clamp: 2;
104
+ -webkit-box-orient: vertical;
105
+ overflow: hidden;
106
+ }
107
+
108
+ .item-price {
109
+ font-size: var(--font-size-xl, 1.25rem);
110
+ font-weight: 700;
111
+ color: var(--color-primary, #0d6efd);
112
+ margin-top: auto;
113
+ padding-top: var(--gap-sm, 8px);
114
+ }
115
+
116
+ .item-actions {
117
+ padding: var(--gap-md, 16px);
118
+ padding-top: 0;
119
+ display: flex;
120
+ gap: var(--gap-sm, 8px);
121
+ flex-wrap: wrap;
122
+
123
+ :deep(.KButton) {
124
+ flex: 1;
125
+ min-width: 100px;
126
+ }
127
+ }
128
+ }
129
+ </style>