@todovue/tv-footer 1.1.0 → 1.1.1

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 ADDED
@@ -0,0 +1,67 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@todovue/tv-footer` will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.1.1] - 2026-01-27
9
+
10
+ ### Changed
11
+ - Simplified the file list in `package.json` to include only essential assets.
12
+ - Simplified the build configuration by removing demo-specific logic.
13
+ - Enhanced GitHub Actions workflows to automate npm package publishing and GitHub release creation.
14
+ - Moved the `@todovue/tv-demo` component import from main.js to `Demo.vue` to localize its usage.
15
+ - Updated build commands to include `README.md` and `CHANGELOG.md` files in the public directory during the build process.
16
+
17
+ ### Added
18
+ - Included the `src` directory in the `package.json` files list to ensure component source files are bundled in the package distribution.
19
+
20
+ ### Removed
21
+ - Eliminated the global import of the `@todovue/tv-demo` component from `main.js`.
22
+
23
+ ### Dependencies
24
+ - Updated `@todovue/tv-demo` to `^1.4.11`.
25
+ - Updated `vue` to `^3.5.27`.
26
+ - Updated `sass` to `^1.97.3`.
27
+
28
+ ## [1.1.0] - 2026-01-21
29
+
30
+ ### Added
31
+ - Added `newsletter` properties to `useFooter` composable return.
32
+ - Added new styles for newsletter form and back-to-top button.
33
+
34
+ ### Dependencies
35
+ - Updated `@todovue/tv-demo` to `^1.4.4`.
36
+ - Updated `sass` to `^1.97.2`.
37
+ - Updated `vite` to `^7.3.1`.
38
+
39
+ ### Changed
40
+ - Enhanced footer styles to improve visual consistency across the application.
41
+ - Improved responsiveness of the footer component for better mobile and tablet compatibility.
42
+
43
+ ## [1.0.0] - 2026-01-07
44
+
45
+ ### Added
46
+ - Initial release of TvFooter component
47
+ - Responsive grid layout (1 column mobile, 2 tablet, 4 desktop)
48
+ - Brand section with logo and name display support
49
+ - Version display capability
50
+ - Multiple navigation sections with titles and links
51
+ - Social media links with icon support (iconUrl and icon class)
52
+ - Legal links section (Privacy, Terms, etc.)
53
+ - Dynamic copyright with automatic year replacement (`{year}` placeholder)
54
+ - Light/Dark mode support built-in
55
+ - SSR-safe implementation (Nuxt 3 compatible)
56
+ - `useFooter` composable for custom implementations
57
+ - TypeScript support with type definitions
58
+ - Semantic HTML structure for accessibility
59
+ - ARIA labels and proper link attributes
60
+ - External link safety (`rel="noopener noreferrer"`)
61
+ - Smooth hover effects and transitions
62
+ - Backdrop blur effect on social icons
63
+ - Comprehensive documentation and examples
64
+
65
+ [1.1.1]: https://github.com/TODOvue/tv-footer/pull/3/files
66
+ [1.1.0]: https://github.com/TODOvue/tv-footer/pull/2/files
67
+ [1.0.0]: https://github.com/TODOvue/tv-footer/pull/1/files
package/package.json CHANGED
@@ -4,10 +4,11 @@
4
4
  "author": "Cristhian Daza",
5
5
  "description": "A simple and customizable footer component for Vue.js applications, perfect for enhancing your web projects with ease.",
6
6
  "license": "MIT",
7
- "version": "1.1.0",
7
+ "version": "1.1.1",
8
8
  "type": "module",
9
9
  "homepage": "https://ui.todovue.blog/footer",
10
10
  "repository": {
11
+ "name": "@todovue/tv-footer",
11
12
  "type": "git",
12
13
  "url": "git+https://github.com/TODOvue/tv-footer.git"
13
14
  },
@@ -32,16 +33,21 @@
32
33
  "require": "./dist/tv-footer.cjs.js"
33
34
  },
34
35
  "./style.css": "./dist/tv-footer.css",
35
- "./nuxt": "./nuxt.js"
36
+ "./nuxt": "./nuxt.js",
37
+ "./demo": {
38
+ "import": "./src/demo/Demo.vue"
39
+ }
36
40
  },
37
41
  "main": "dist/tv-footer.cjs.js",
38
42
  "module": "dist/tv-footer.es.js",
39
43
  "types": "dist/tv-footer.d.ts",
40
44
  "files": [
41
- "dist",
45
+ "CHANGELOG.md",
42
46
  "LICENSE",
43
47
  "README.md",
44
- "nuxt.js"
48
+ "dist",
49
+ "nuxt.js",
50
+ "src"
45
51
  ],
46
52
  "engines": {
47
53
  "node": ">=20.19.0"
@@ -53,16 +59,15 @@
53
59
  ],
54
60
  "scripts": {
55
61
  "dev": "vite",
56
- "build": "vite build",
57
- "build:demo": "cp README.md public/ && cp CHANGELOG.md public/ && VITE_BUILD_TARGET=demo vite build"
62
+ "build": "vite build"
58
63
  },
59
64
  "peerDependencies": {
60
- "vue": "^3.5.26"
65
+ "vue": "^3.5.27"
61
66
  },
62
67
  "devDependencies": {
63
- "@todovue/tv-demo": "^1.4.4",
68
+ "@todovue/tv-demo": "^1.4.11",
64
69
  "@vitejs/plugin-vue": "^6.0.3",
65
- "sass": "^1.97.2",
70
+ "sass": "^1.97.3",
66
71
  "vite": "^7.3.1",
67
72
  "vite-plugin-dts": "^4.5.4"
68
73
  }
@@ -0,0 +1,12 @@
1
+ /* 🌙 Dark Theme */
2
+ $dark-body-bg: #161E31;
3
+ $dark-card-bg: #0E131F;
4
+ $dark-text: #CBD5E1;
5
+
6
+ /* ☀️ Light Theme */
7
+ $light-body-bg: #f8FAFC;
8
+ $light-card-bg: #B9C4DF;
9
+ $light-button-text: #F1F9F9;
10
+ $light-text: #1E293B;
11
+
12
+ $primary-color: #ef233c;
@@ -0,0 +1,396 @@
1
+ @use "variables" as *;
2
+ @use "sass:color";
3
+
4
+ .tv-footer {
5
+ width: 100%;
6
+ padding: 3rem 1.5rem;
7
+ box-sizing: border-box;
8
+ transition: background-color 0.4s ease, color 0.4s ease;
9
+ position: relative;
10
+
11
+ background-color: $dark-card-bg;
12
+ color: $dark-text;
13
+ border-top: 1px solid rgba($dark-text, 0.05);
14
+
15
+ &,
16
+ .dark-mode & {
17
+
18
+ .tv-footer__logo,
19
+ .tv-footer__section-title {
20
+ color: #fff;
21
+ }
22
+
23
+ .tv-footer__social-link {
24
+ background-color: rgba(255, 255, 255, 0.03);
25
+ color: inherit;
26
+ border: 1px solid rgba(255, 255, 255, 0.05);
27
+
28
+ &:hover {
29
+ background-color: $primary-color;
30
+ border-color: $primary-color;
31
+ color: white;
32
+ box-shadow: 0 0 15px rgba($primary-color, 0.4);
33
+ }
34
+ }
35
+ }
36
+
37
+ .light-mode & {
38
+ background-color: $light-card-bg;
39
+ color: $light-text;
40
+ border-top-color: rgba($light-text, 0.05);
41
+
42
+ .tv-footer__logo,
43
+ .tv-footer__section-title {
44
+ color: $light-text;
45
+ }
46
+
47
+ .tv-footer__social-link {
48
+ background-color: rgba(255, 255, 255, 0.5);
49
+ color: inherit;
50
+ border: 1px solid rgba(0, 0, 0, 0.05);
51
+ backdrop-filter: blur(4px);
52
+
53
+ &:hover {
54
+ background-color: $primary-color;
55
+ border-color: $primary-color;
56
+ color: $light-button-text;
57
+ box-shadow: 0 4px 12px rgba($primary-color, 0.3);
58
+ }
59
+ }
60
+
61
+ .tv-footer__link:hover {
62
+ color: $primary-color;
63
+ }
64
+
65
+ .tv-footer__newsletter-input {
66
+ background-color: rgba(255, 255, 255, 0.6);
67
+ border-color: rgba(0, 0, 0, 0.05);
68
+ color: $light-text;
69
+ backdrop-filter: blur(8px);
70
+
71
+ &:focus {
72
+ background-color: rgba(255, 255, 255, 0.9);
73
+ border-color: $primary-color;
74
+ box-shadow: 0 0 0 4px rgba($primary-color, 0.1);
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ .tv-footer__container {
81
+ display: flex;
82
+ flex-wrap: wrap;
83
+ gap: 4rem;
84
+ max-width: 1200px;
85
+ margin: 0 auto;
86
+ }
87
+
88
+ .tv-footer__left-column {
89
+ flex: 1 1 250px;
90
+ max-width: 400px;
91
+ display: flex;
92
+ flex-direction: column;
93
+ gap: 2rem;
94
+ }
95
+
96
+ .tv-footer__brand {
97
+ display: flex;
98
+ flex-direction: column;
99
+ gap: 1rem;
100
+ }
101
+
102
+ .tv-footer__logo {
103
+ font-weight: 800;
104
+ font-size: 1.5rem;
105
+ text-decoration: none;
106
+ display: inline-flex;
107
+ align-items: center;
108
+ gap: 0.75rem;
109
+ letter-spacing: -0.02em;
110
+
111
+ img {
112
+ height: 100px;
113
+ width: auto;
114
+ }
115
+ }
116
+
117
+ .tv-footer__version {
118
+ font-size: 0.75rem;
119
+ opacity: 0.6;
120
+ font-weight: 500;
121
+ background: rgba(127, 127, 127, 0.1);
122
+ padding: 0.125rem 0.5rem;
123
+ border-radius: 12px;
124
+ align-self: flex-start;
125
+ }
126
+
127
+ .tv-footer__nav-definitions {
128
+ display: flex;
129
+ flex: 999 1 400px;
130
+ flex-wrap: wrap;
131
+ gap: 3rem;
132
+
133
+ @media (max-width: 768px) {
134
+ gap: 2rem;
135
+ flex-basis: 100%;
136
+ }
137
+ }
138
+
139
+ .tv-footer__section {
140
+ min-width: 140px;
141
+ }
142
+
143
+ .tv-footer__section-title {
144
+ font-weight: 700;
145
+ margin-bottom: 1.25rem;
146
+ font-size: 0.9rem;
147
+ letter-spacing: 0.05em;
148
+ text-transform: uppercase;
149
+ opacity: 1;
150
+ position: relative;
151
+
152
+ &::after {
153
+ content: '';
154
+ display: block;
155
+ width: 20px;
156
+ height: 2px;
157
+ background-color: $primary-color;
158
+ margin-top: 0.5rem;
159
+ border-radius: 2px;
160
+ }
161
+ }
162
+
163
+ .tv-footer__links {
164
+ list-style: none;
165
+ padding: 0;
166
+ margin: 0;
167
+ display: flex;
168
+ flex-direction: column;
169
+ gap: 0.875rem;
170
+ }
171
+
172
+ .tv-footer__link {
173
+ color: inherit;
174
+ text-decoration: none;
175
+ font-size: 0.95rem;
176
+ transition: all 0.2s ease;
177
+ cursor: pointer;
178
+ display: flex;
179
+ align-items: center;
180
+ gap: 0.5rem;
181
+ opacity: 0.75;
182
+ width: fit-content;
183
+
184
+ &:hover {
185
+ color: $primary-color;
186
+ opacity: 1;
187
+ transform: translateX(4px);
188
+ }
189
+
190
+ &::before {
191
+ content: '';
192
+ display: block;
193
+ width: 4px;
194
+ height: 4px;
195
+ background-color: currentColor;
196
+ border-radius: 50%;
197
+ opacity: 0;
198
+ transition: opacity 0.2s;
199
+ }
200
+
201
+ &:hover::before {
202
+ opacity: 1;
203
+ }
204
+ }
205
+
206
+ .tv-footer__social-section {
207
+ display: flex;
208
+ flex-direction: column;
209
+ justify-content: flex-start;
210
+ padding-top: 0.5rem;
211
+ flex: 0 0 auto;
212
+ }
213
+
214
+ .tv-footer__social {
215
+ display: flex;
216
+ gap: 0.75rem;
217
+ flex-wrap: wrap;
218
+ }
219
+
220
+ .tv-footer__social-link {
221
+ font-size: 1.1rem;
222
+ text-decoration: none;
223
+ display: flex;
224
+ align-items: center;
225
+ justify-content: center;
226
+ width: 2.5rem;
227
+ height: 2.5rem;
228
+ border-radius: 50%;
229
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
230
+
231
+ &:hover {
232
+ transform: translateY(-4px) scale(1.1);
233
+ }
234
+
235
+ img {
236
+ width: 1.25rem;
237
+ height: 1.25rem;
238
+ object-fit: contain;
239
+ display: block;
240
+ }
241
+ }
242
+
243
+ .tv-footer__bottom {
244
+ border-top: 1px solid rgba(127, 127, 127, 0.1);
245
+ margin-top: 3rem;
246
+ padding-top: 1.5rem;
247
+ text-align: center;
248
+ font-size: 0.85rem;
249
+ display: flex;
250
+ flex-direction: column;
251
+ align-items: center;
252
+ gap: 1rem;
253
+ max-width: 1200px;
254
+ margin-left: auto;
255
+ margin-right: auto;
256
+ opacity: 0.7;
257
+
258
+ @media (min-width: 640px) {
259
+ flex-direction: row;
260
+ justify-content: space-between;
261
+ }
262
+
263
+ .tv-footer__links {
264
+ flex-direction: row;
265
+ gap: 1.5rem;
266
+ flex-wrap: wrap;
267
+ justify-content: center;
268
+
269
+ li {
270
+ display: inline-block;
271
+ }
272
+
273
+ .tv-footer__link {
274
+ font-size: 0.8rem;
275
+
276
+ &:hover {
277
+ transform: none;
278
+ text-decoration: underline;
279
+ }
280
+
281
+ &::before {
282
+ display: none;
283
+ }
284
+ }
285
+ }
286
+ }
287
+
288
+ .tv-footer__newsletter {
289
+ display: flex;
290
+ flex-direction: column;
291
+ gap: 1rem;
292
+ margin-top: 1rem;
293
+ width: 100%;
294
+ }
295
+
296
+ .tv-footer__newsletter-title {
297
+ font-weight: 700;
298
+ font-size: 1rem;
299
+ margin: 0;
300
+ }
301
+
302
+ .tv-footer__newsletter-text {
303
+ font-size: 0.9rem;
304
+ line-height: 1.5;
305
+ opacity: 0.8;
306
+ margin: 0;
307
+ }
308
+
309
+ .tv-footer__newsletter-form {
310
+ display: flex;
311
+ gap: 0.5rem;
312
+ flex-direction: column;
313
+
314
+ @media(min-width: 400px) {
315
+ flex-direction: row;
316
+ }
317
+ }
318
+
319
+ .tv-footer__newsletter-input {
320
+ flex: 1;
321
+ min-width: 0;
322
+ padding: 0.75rem 1.25rem;
323
+ border-radius: 9999px;
324
+ border: 1px solid transparent;
325
+ background-color: rgba(255, 255, 255, 0.05);
326
+ color: inherit;
327
+ font-size: 0.9rem;
328
+ outline: none;
329
+ transition: all 0.3s ease;
330
+
331
+ &:focus {
332
+ background-color: rgba(255, 255, 255, 0.1);
333
+ border-color: $primary-color;
334
+ box-shadow: 0 0 0 4px rgba($primary-color, 0.2);
335
+ }
336
+
337
+ &::placeholder {
338
+ opacity: 0.5;
339
+ }
340
+ }
341
+
342
+ .tv-footer__newsletter-button {
343
+ padding: 0.75rem 1.5rem;
344
+ border-radius: 9999px;
345
+ border: none;
346
+ background-color: $primary-color;
347
+ color: #fff;
348
+ font-weight: 600;
349
+ font-size: 0.9rem;
350
+ cursor: pointer;
351
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
352
+ display: inline-flex;
353
+ align-items: center;
354
+ justify-content: center;
355
+ white-space: nowrap;
356
+
357
+ &:hover {
358
+ background-color: color.adjust($primary-color, $lightness: -5%);
359
+ transform: translateY(-2px);
360
+ box-shadow: 0 4px 12px rgba($primary-color, 0.4);
361
+ }
362
+
363
+ &:active {
364
+ transform: translateY(0);
365
+ }
366
+ }
367
+
368
+ .tv-footer__back-to-top {
369
+ position: fixed;
370
+ bottom: 2.5rem;
371
+ right: 2.5rem;
372
+ width: 3.5rem;
373
+ height: 3.5rem;
374
+ border-radius: 50%;
375
+ background-color: $primary-color;
376
+ color: #fff;
377
+ border: none;
378
+ font-size: 1.5rem;
379
+ display: flex;
380
+ align-items: center;
381
+ justify-content: center;
382
+ cursor: pointer;
383
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
384
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
385
+ z-index: 1000;
386
+
387
+ &:hover {
388
+ transform: translateY(-4px) scale(1.05);
389
+ background-color: color.adjust($primary-color, $lightness: -5%);
390
+ box-shadow: 0 12px 20px rgba($primary-color, 0.3);
391
+ }
392
+
393
+ &:active {
394
+ transform: translateY(-1px);
395
+ }
396
+ }
@@ -0,0 +1,101 @@
1
+ <script setup>
2
+ import { useFooter } from '../composables/useFooter.js'
3
+ import { ref } from 'vue'
4
+
5
+ const props = defineProps({
6
+ config: {
7
+ type: Object,
8
+ default: () => ({})
9
+ }
10
+ })
11
+
12
+ const { brand, navigation, social, legal, version, copyright, newsletter } = useFooter(props.config)
13
+ const email = ref('')
14
+ const emit = defineEmits(['subscribe'])
15
+
16
+ const handleSubscribe = () => {
17
+ if (email.value) {
18
+ emit('subscribe', email.value)
19
+ email.value = ''
20
+ }
21
+ }
22
+ </script>
23
+
24
+ <template>
25
+ <footer class="tv-footer">
26
+ <div class="tv-footer__container">
27
+ <div class="tv-footer__left-column">
28
+ <slot name="brand" :brand="brand" :version="version">
29
+ <div v-if="brand" class="tv-footer__brand">
30
+ <a :href="brand.url || '/'" class="tv-footer__logo">
31
+ <img v-if="brand.logo" :src="brand.logo" :alt="brand.name" />
32
+ <span v-if="brand.name">{{ brand.name }}</span>
33
+ </a>
34
+ <span v-if="version" class="tv-footer__version">{{ version }}</span>
35
+ </div>
36
+ </slot>
37
+
38
+ <slot name="newsletter" :newsletter="newsletter">
39
+ <div v-if="newsletter" class="tv-footer__newsletter">
40
+ <h3 v-if="newsletter.title" class="tv-footer__newsletter-title">{{ newsletter.title }}</h3>
41
+ <p v-if="newsletter.description" class="tv-footer__newsletter-text">{{ newsletter.description }}</p>
42
+ <form class="tv-footer__newsletter-form" @submit.prevent="handleSubscribe">
43
+ <input
44
+ v-model="email"
45
+ type="email"
46
+ :placeholder="newsletter.placeholder || 'Enter your email'"
47
+ class="tv-footer__newsletter-input"
48
+ required
49
+ />
50
+ <button type="submit" class="tv-footer__newsletter-button">
51
+ {{ newsletter.buttonText || 'Subscribe' }}
52
+ </button>
53
+ </form>
54
+ </div>
55
+ </slot>
56
+ </div>
57
+
58
+ <div class="tv-footer__nav-definitions">
59
+ <div v-for="(group, index) in navigation" :key="index" class="tv-footer__section">
60
+ <h3 v-if="group.title" class="tv-footer__section-title">{{ group.title }}</h3>
61
+ <ul class="tv-footer__links">
62
+ <li v-for="(link, i) in group.items" :key="i">
63
+ <a :href="link.url" class="tv-footer__link">
64
+ {{ link.label }}
65
+ </a>
66
+ </li>
67
+ </ul>
68
+ </div>
69
+ </div>
70
+
71
+ <div v-if="social && social.length" class="tv-footer__social-section">
72
+ <div class="tv-footer__social">
73
+ <a v-for="(item, index) in social" :key="index" :href="item.url" class="tv-footer__social-link" target="_blank" rel="noopener noreferer">
74
+ <img v-if="item.iconUrl" :src="item.iconUrl" :alt="item.label" class="tv-footer__social-icon-img" />
75
+ <i v-else-if="item.icon" :class="item.icon"></i>
76
+ <span v-else>{{ item.label }}</span>
77
+ </a>
78
+ </div>
79
+ </div>
80
+ </div>
81
+
82
+ <slot name="bottom" :copyright="copyright" :legal="legal">
83
+ <div class="tv-footer__bottom">
84
+ <div v-if="copyright">{{ copyright }}</div>
85
+ <div v-if="legal && legal.length" class="tv-footer__legal">
86
+ <ul class="tv-footer__links" style="flex-direction: row; gap: 1.5rem;">
87
+ <li v-for="(link, index) in legal" :key="index">
88
+ <a :href="link.url" class="tv-footer__link">
89
+ {{ link.label }}
90
+ </a>
91
+ </li>
92
+ </ul>
93
+ </div>
94
+ </div>
95
+ </slot>
96
+ </footer>
97
+ </template>
98
+
99
+ <style scoped>
100
+
101
+ </style>
@@ -0,0 +1,45 @@
1
+ import { computed } from 'vue'
2
+
3
+ export function useFooter(config) {
4
+ const brand = computed(() => config?.brand || null)
5
+
6
+ const navigation = computed(() => {
7
+ if (!config?.navigation || !Array.isArray(config.navigation)) return []
8
+ return config.navigation
9
+ })
10
+
11
+ const social = computed(() => {
12
+ if (!config?.social || !Array.isArray(config.social)) return []
13
+ return config.social
14
+ })
15
+
16
+ const legal = computed(() => {
17
+ if (!config?.legal || !Array.isArray(config.legal)) return []
18
+ return config.legal
19
+ })
20
+
21
+ const version = computed(() => config?.version || '')
22
+
23
+ const copyright = computed(() => config?.copyright || '')
24
+
25
+ const newsletter = computed(() => {
26
+ return config?.newsletter || null
27
+ })
28
+
29
+ const currentYear = new Date().getFullYear()
30
+
31
+ const formattedCopyright = computed(() => {
32
+ const text = copyright.value
33
+ return text.replace('{year}', currentYear)
34
+ })
35
+
36
+ return {
37
+ brand,
38
+ navigation,
39
+ social,
40
+ legal,
41
+ version,
42
+ copyright: formattedCopyright,
43
+ newsletter
44
+ }
45
+ }
@@ -0,0 +1,21 @@
1
+ <script setup>
2
+ import { defineAsyncComponent } from 'vue'
3
+ import { demos } from './utils/mocks.js'
4
+ import { TvDemo } from '@todovue/tv-demo'
5
+
6
+ const TvFooter = defineAsyncComponent(/* webpackChunkName: "tvFooter" */() => import('../components/TvFooter.vue'))
7
+ </script>
8
+
9
+ <template>
10
+ <TvDemo
11
+ hide-background
12
+ :component="TvFooter"
13
+ :variants="demos"
14
+ :manual-emits="['subscribe']"
15
+ component-name="TvFooter"
16
+ npm-install="@todovue/tv-footer"
17
+ source-link="https://github.com/TODOvue/tv-footer"
18
+ url-clone="https://github.com/TODOvue/tv-footer.git"
19
+ version="1.1.1"
20
+ />
21
+ </template>
@@ -0,0 +1,68 @@
1
+ <template>
2
+ <TvFooter :config="conf" />
3
+ </template>
4
+
5
+ <script setup>
6
+ import { TvFooter } from '@todovue/tv-footer'
7
+ import '@todovue/tv-footer/style.css'
8
+ import FacebookIcon from './utils/icons/facebook.png'
9
+ import GitHubWithIcon from "../icons/github-white.svg";
10
+ import TODOvue from "../icons/todovue.png";
11
+
12
+ const conf = {
13
+ brand: {
14
+ logo: 'https://res.cloudinary.com/denj4fg7f/image/upload/v1766199952/logo_ohpadg.png', // Optional
15
+ url: '/'
16
+ },
17
+ version: 'v2.4.0',
18
+ navigation: [
19
+ {
20
+ title: 'Product',
21
+ items: [
22
+ { label: 'Features', url: '/#' },
23
+ { label: 'Pricing', url: '/#' },
24
+ { label: 'Showcase', url: '/#' }
25
+ ]
26
+ },
27
+ {
28
+ title: 'Resources',
29
+ items: [
30
+ { label: 'Documentation', url: '/#' },
31
+ { label: 'API Reference', url: '/#' },
32
+ { label: 'Community', url: '/#' }
33
+ ]
34
+ },
35
+ {
36
+ title: 'Company',
37
+ items: [
38
+ { label: 'About Us', url: '/#' },
39
+ { label: 'Blog', url: '/#' },
40
+ { label: 'Careers', url: '/#' }
41
+ ]
42
+ }
43
+ ],
44
+ social: [
45
+ {
46
+ label: 'GitHub',
47
+ url: 'https://github.com/TODOvue',
48
+ iconUrl: GitHubWithIcon // Icon library class (e.g. FontAwesome, UnoCSS) use /icon
49
+ },
50
+ {
51
+ label: 'Facebook',
52
+ url: 'https://facebook.com',
53
+ iconUrl: FacebookIcon
54
+ },
55
+ {
56
+ label: 'TODOvue',
57
+ url: 'https://todovue.blog',
58
+ iconUrl: TODOvue
59
+ }
60
+ ],
61
+ legal: [
62
+ { label: 'Privacy', url: '/#' },
63
+ { label: 'Terms', url: '/#' },
64
+ { label: 'Cookies', url: '/#' }
65
+ ],
66
+ copyright: '© {year} TvFooter. All rights reserved.'
67
+ }
68
+ </script>
@@ -0,0 +1,79 @@
1
+ <template>
2
+ <TvFooter :config="conf" @subscribe="handleSubscribe" />
3
+ </template>
4
+
5
+ <script setup>
6
+ import { TvFooter } from '@todovue/tv-footer'
7
+ import '@todovue/tv-footer/style.css'
8
+ import FacebookIcon from './utils/icons/facebook.png'
9
+ import GitHubWithIcon from "../icons/github-white.svg";
10
+ import TODOvue from "../icons/todovue.png";
11
+
12
+ const handleSubscribe = (email) => {
13
+ console.log('Subscribe event received:', email);
14
+ alert(`Subscribed with: ${email}`);
15
+ }
16
+
17
+ const conf = {
18
+ newsletter: {
19
+ title: 'Subscribe to our newsletter',
20
+ description: 'Get the latest news and updates right to your inbox.',
21
+ placeholder: 'Your email address',
22
+ buttonText: 'Subscribe'
23
+ },
24
+ brand: {
25
+ logo: 'https://res.cloudinary.com/denj4fg7f/image/upload/v1766199952/logo_ohpadg.png', // Optional
26
+ url: '/'
27
+ },
28
+ version: 'v2.4.0',
29
+ navigation: [
30
+ {
31
+ title: 'Product',
32
+ items: [
33
+ { label: 'Features', url: '/#' },
34
+ { label: 'Pricing', url: '/#' },
35
+ { label: 'Showcase', url: '/#' }
36
+ ]
37
+ },
38
+ {
39
+ title: 'Resources',
40
+ items: [
41
+ { label: 'Documentation', url: '/#' },
42
+ { label: 'API Reference', url: '/#' },
43
+ { label: 'Community', url: '/#' }
44
+ ]
45
+ },
46
+ {
47
+ title: 'Company',
48
+ items: [
49
+ { label: 'About Us', url: '/#' },
50
+ { label: 'Blog', url: '/#' },
51
+ { label: 'Careers', url: '/#' }
52
+ ]
53
+ }
54
+ ],
55
+ social: [
56
+ {
57
+ label: 'GitHub',
58
+ url: 'https://github.com/TODOvue',
59
+ iconUrl: GitHubWithIcon // Icon library class (e.g. FontAwesome, UnoCSS) use /icon
60
+ },
61
+ {
62
+ label: 'Facebook',
63
+ url: 'https://facebook.com',
64
+ iconUrl: FacebookIcon
65
+ },
66
+ {
67
+ label: 'TODOvue',
68
+ url: 'https://todovue.blog',
69
+ iconUrl: TODOvue
70
+ }
71
+ ],
72
+ legal: [
73
+ { label: 'Privacy', url: '/#' },
74
+ { label: 'Terms', url: '/#' },
75
+ { label: 'Cookies', url: '/#' }
76
+ ],
77
+ copyright: '© {year} TvFooter. All rights reserved.'
78
+ }
79
+ </script>
@@ -0,0 +1 @@
1
+ <svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/></svg>
@@ -0,0 +1 @@
1
+ <svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#24292f"/></svg>
Binary file
@@ -0,0 +1,94 @@
1
+ import GitHubWithIcon from './icons/github-white.svg';
2
+ import FacebookIcon from './icons/facebook.png';
3
+ import TODOvue from './icons/todovue.png';
4
+
5
+ import Default from './demos/default.vue?raw';
6
+ import Newsletter from './demos/newsletter.vue?raw';
7
+
8
+ const config = {
9
+ brand: {
10
+ logo: 'https://res.cloudinary.com/denj4fg7f/image/upload/v1766199952/logo_ohpadg.png', // Optional
11
+ url: '/'
12
+ },
13
+ version: 'v2.4.0',
14
+ navigation: [
15
+ {
16
+ title: 'Product',
17
+ items: [
18
+ { label: 'Features', url: '/#' },
19
+ { label: 'Pricing', url: '/#' },
20
+ { label: 'Showcase', url: '/#' }
21
+ ]
22
+ },
23
+ {
24
+ title: 'Resources',
25
+ items: [
26
+ { label: 'Documentation', url: '/#' },
27
+ { label: 'API Reference', url: '/#' },
28
+ { label: 'Community', url: '/#' }
29
+ ]
30
+ },
31
+ {
32
+ title: 'Company',
33
+ items: [
34
+ { label: 'About Us', url: '/#' },
35
+ { label: 'Blog', url: '/#' },
36
+ { label: 'Careers', url: '/#' }
37
+ ]
38
+ }
39
+ ],
40
+ social: [
41
+ {
42
+ label: 'GitHub',
43
+ url: 'https://github.com/TODOvue',
44
+ iconUrl: GitHubWithIcon // Icon library class (e.g. FontAwesome, UnoCSS)
45
+ },
46
+ {
47
+ label: 'Facebook',
48
+ url: 'https://facebook.com',
49
+ iconUrl: FacebookIcon
50
+ },
51
+ {
52
+ label: 'TODOvue',
53
+ url: 'https://todovue.blog',
54
+ iconUrl: TODOvue
55
+ }
56
+ ],
57
+ legal: [
58
+ { label: 'Privacy', url: '/#' },
59
+ { label: 'Terms', url: '/#' },
60
+ { label: 'Cookies', url: '/#' }
61
+ ],
62
+ copyright: '© {year} TvFooter. All rights reserved.'
63
+ }
64
+
65
+ const configEnhanced = {
66
+ ...config,
67
+ newsletter: {
68
+ title: 'Subscribe to our newsletter',
69
+ description: 'Get the latest news and updates right to your inbox.',
70
+ placeholder: 'Your email address',
71
+ buttonText: 'Subscribe'
72
+ }
73
+ }
74
+
75
+ export const demos = [
76
+ {
77
+ id: 1,
78
+ title: "TvFooter Default",
79
+ description: "Default TvFooter component",
80
+ propsData: {
81
+ config,
82
+ },
83
+ html: Default,
84
+ },
85
+ {
86
+ id: 2,
87
+ title: "TvFooter Enhanced",
88
+ description: "TvFooter with Newsletter",
89
+ propsData: {
90
+ config: configEnhanced,
91
+ },
92
+ html: Newsletter,
93
+ },
94
+ ];
package/src/entry.ts ADDED
@@ -0,0 +1,21 @@
1
+ import type { App, Plugin } from 'vue'
2
+ import _TvFooter from './components/TvFooter.vue'
3
+ import './style.scss'
4
+
5
+ const TvFooter = _TvFooter as typeof _TvFooter & Plugin;
6
+ TvFooter.install = (app: App) => {
7
+ app.component('TvFooter', TvFooter)
8
+ };
9
+
10
+ export { TvFooter }
11
+
12
+ export const TvFooterPlugin: Plugin = {
13
+ install: TvFooter.install
14
+ };
15
+ export default TvFooter;
16
+
17
+ declare module 'vue' {
18
+ export interface GlobalComponents {
19
+ TvFooter: typeof TvFooter;
20
+ }
21
+ }
package/src/main.js ADDED
@@ -0,0 +1,7 @@
1
+ import { createApp } from 'vue'
2
+ import './style.scss'
3
+ import '@todovue/tv-demo/style.css'
4
+ import TvFooter from './demo/Demo.vue'
5
+
6
+ const app = createApp(TvFooter)
7
+ app.mount('#tv-footer')
package/src/style.scss ADDED
@@ -0,0 +1 @@
1
+ @use "./assets/scss/style";