@todovue/tv-footer 1.0.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/LICENSE +21 -0
- package/README.md +732 -0
- package/dist/entry.d.ts +11 -0
- package/dist/favicon.ico +0 -0
- package/dist/tv-footer.cjs.js +1 -0
- package/dist/tv-footer.css +1 -0
- package/dist/tv-footer.d.ts +6 -0
- package/dist/tv-footer.es.js +119 -0
- package/nuxt.js +15 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cristhian Daza
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
<p align="center"><img width="150" src="https://res.cloudinary.com/dcdfhi8qz/image/upload/v1763663056/uqqtkgp1lg3xdplutpga.png" alt="TODOvue logo">
|
|
2
|
+
</p>
|
|
3
|
+
|
|
4
|
+
# TODOvue Footer (TvFooter)
|
|
5
|
+
A simple, customizable, and responsive Vue 3 footer component with support for branding, navigation sections, social links, legal links, and SSR compatibility. Perfect for building professional footers in Single Page Apps and Server-Side Rendered environments like Nuxt 3.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@todovue/tv-footer)
|
|
8
|
+
[](https://www.npmjs.com/package/@todovue/tv-footer)
|
|
9
|
+
[](https://www.npmjs.com/package/@todovue/tv-footer)
|
|
10
|
+

|
|
11
|
+

|
|
12
|
+

|
|
13
|
+

|
|
14
|
+

|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
> Demo: https://ui.todovue.blog/footer
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
## Table of Contents
|
|
21
|
+
- [Features](#features)
|
|
22
|
+
- [Installation](#installation)
|
|
23
|
+
- [Quick Start (SPA)](#quick-start-spa)
|
|
24
|
+
- [Nuxt 3 / SSR Usage](#nuxt-3--ssr-usage)
|
|
25
|
+
- [Component Registration Options](#component-registration-options)
|
|
26
|
+
- [Props](#props)
|
|
27
|
+
- [Configuration Object](#configuration-object)
|
|
28
|
+
- [Composable API](#composable-api)
|
|
29
|
+
- [Usage Examples](#usage-examples)
|
|
30
|
+
- [Styling](#styling)
|
|
31
|
+
- [Accessibility](#accessibility)
|
|
32
|
+
- [SSR Notes](#ssr-notes)
|
|
33
|
+
- [Development](#development)
|
|
34
|
+
- [Contributing](#contributing)
|
|
35
|
+
- [License](#license)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
## Features
|
|
39
|
+
- Fully responsive grid layout (1 column on mobile, 2 on tablet, 4 on desktop)
|
|
40
|
+
- Customizable brand section with logo and version display
|
|
41
|
+
- Multiple navigation sections with titles and links
|
|
42
|
+
- Social media links with icon support
|
|
43
|
+
- Legal links section (Privacy, Terms, etc.)
|
|
44
|
+
- Dynamic copyright with automatic year replacement
|
|
45
|
+
- Light/Dark mode support built-in
|
|
46
|
+
- SSR-safe (works with Nuxt 3 and other SSR frameworks)
|
|
47
|
+
- Composable API (`useFooter`) for custom implementations
|
|
48
|
+
- Accessible and semantic HTML
|
|
49
|
+
- Lightweight and tree-shakeable
|
|
50
|
+
- TypeScript support
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
## Installation
|
|
54
|
+
Using npm:
|
|
55
|
+
```bash
|
|
56
|
+
npm install @todovue/tv-footer
|
|
57
|
+
```
|
|
58
|
+
Using yarn:
|
|
59
|
+
```bash
|
|
60
|
+
yarn add @todovue/tv-footer
|
|
61
|
+
```
|
|
62
|
+
Using pnpm:
|
|
63
|
+
```bash
|
|
64
|
+
pnpm add @todovue/tv-footer
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Importing Styles
|
|
68
|
+
**Important:** You must explicitly import the stylesheet in your application.
|
|
69
|
+
|
|
70
|
+
#### For Vue/Vite SPA:
|
|
71
|
+
```ts
|
|
72
|
+
// main.ts
|
|
73
|
+
import { createApp } from 'vue'
|
|
74
|
+
import App from './App.vue'
|
|
75
|
+
|
|
76
|
+
import '@todovue/tv-footer/style.css'
|
|
77
|
+
import { TvFooter } from '@todovue/tv-footer'
|
|
78
|
+
|
|
79
|
+
const app = createApp(App)
|
|
80
|
+
app.component('TvFooter', TvFooter)
|
|
81
|
+
app.mount('#app')
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### For Nuxt 3/4:
|
|
85
|
+
```ts
|
|
86
|
+
// nuxt.config.ts
|
|
87
|
+
export default defineNuxtConfig({
|
|
88
|
+
modules: [
|
|
89
|
+
'@todovue/tv-footer/nuxt'
|
|
90
|
+
]
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Then register the component in a plugin as shown in the [Nuxt 3 / SSR Usage](#nuxt-3--ssr-usage) section.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
## Quick Start (SPA)
|
|
98
|
+
Global registration (main.js / main.ts):
|
|
99
|
+
```js
|
|
100
|
+
import { createApp } from 'vue'
|
|
101
|
+
import App from './App.vue'
|
|
102
|
+
import '@todovue/tv-footer/style.css'
|
|
103
|
+
import TvFooter from '@todovue/tv-footer'
|
|
104
|
+
|
|
105
|
+
createApp(App)
|
|
106
|
+
.use(TvFooter) // enables <TvFooter /> globally
|
|
107
|
+
.mount('#app')
|
|
108
|
+
```
|
|
109
|
+
Local import inside a component:
|
|
110
|
+
```vue
|
|
111
|
+
<script setup>
|
|
112
|
+
import { TvFooter } from '@todovue/tv-footer'
|
|
113
|
+
import '@todovue/tv-footer/style.css'
|
|
114
|
+
|
|
115
|
+
const footerConfig = {
|
|
116
|
+
brand: {
|
|
117
|
+
logo: 'https://example.com/logo.png',
|
|
118
|
+
name: 'My Company',
|
|
119
|
+
url: '/'
|
|
120
|
+
},
|
|
121
|
+
navigation: [
|
|
122
|
+
{
|
|
123
|
+
title: 'Product',
|
|
124
|
+
items: [
|
|
125
|
+
{ label: 'Features', url: '/features' },
|
|
126
|
+
{ label: 'Pricing', url: '/pricing' }
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
],
|
|
130
|
+
copyright: '© {year} My Company. All rights reserved.'
|
|
131
|
+
}
|
|
132
|
+
</script>
|
|
133
|
+
|
|
134
|
+
<template>
|
|
135
|
+
<div>
|
|
136
|
+
<!-- Your page content -->
|
|
137
|
+
|
|
138
|
+
<!-- Footer at the bottom -->
|
|
139
|
+
<TvFooter :config="footerConfig" />
|
|
140
|
+
</div>
|
|
141
|
+
</template>
|
|
142
|
+
```
|
|
143
|
+
**Note:** Don't forget to import the CSS in your main entry file as shown above.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
## Nuxt 3 / SSR Usage
|
|
147
|
+
First, add the module to your `nuxt.config.ts`:
|
|
148
|
+
```ts
|
|
149
|
+
// nuxt.config.ts
|
|
150
|
+
export default defineNuxtConfig({
|
|
151
|
+
modules: ['@todovue/tv-footer/nuxt']
|
|
152
|
+
})
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Alternatively, you can manually add the CSS:
|
|
156
|
+
```ts
|
|
157
|
+
// nuxt.config.ts
|
|
158
|
+
export default defineNuxtConfig({
|
|
159
|
+
css: ['@todovue/tv-footer/style.css'],
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Then create a plugin file: `plugins/tv-footer.client.ts`:
|
|
164
|
+
```ts
|
|
165
|
+
import { defineNuxtPlugin } from '#app'
|
|
166
|
+
import TvFooter from '@todovue/tv-footer'
|
|
167
|
+
|
|
168
|
+
export default defineNuxtPlugin(nuxtApp => {
|
|
169
|
+
nuxtApp.vueApp.use(TvFooter)
|
|
170
|
+
})
|
|
171
|
+
```
|
|
172
|
+
Use anywhere in your Nuxt app:
|
|
173
|
+
```vue
|
|
174
|
+
<template>
|
|
175
|
+
<div>
|
|
176
|
+
<NuxtPage />
|
|
177
|
+
|
|
178
|
+
<!-- Footer -->
|
|
179
|
+
<TvFooter :config="footerConfig" />
|
|
180
|
+
</div>
|
|
181
|
+
</template>
|
|
182
|
+
|
|
183
|
+
<script setup>
|
|
184
|
+
const footerConfig = {
|
|
185
|
+
brand: {
|
|
186
|
+
logo: 'https://example.com/logo.png',
|
|
187
|
+
url: '/'
|
|
188
|
+
},
|
|
189
|
+
navigation: [
|
|
190
|
+
{
|
|
191
|
+
title: 'Product',
|
|
192
|
+
items: [
|
|
193
|
+
{ label: 'Features', url: '/features' }
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
copyright: '© {year} My Company. All rights reserved.'
|
|
198
|
+
}
|
|
199
|
+
</script>
|
|
200
|
+
```
|
|
201
|
+
Optional direct import (no plugin):
|
|
202
|
+
```vue
|
|
203
|
+
<script setup>
|
|
204
|
+
import { TvFooter } from '@todovue/tv-footer'
|
|
205
|
+
</script>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
## Component Registration Options
|
|
210
|
+
| Approach | When to use |
|
|
211
|
+
|--------------------------------------------------------------------------|------------------------------------------------|
|
|
212
|
+
| Global via `app.use(TvFooter)` | Many usages across app / design system install |
|
|
213
|
+
| Local named import `{ TvFooter }` | Isolated / code-split contexts |
|
|
214
|
+
| Direct default import `import TvFooter from '@todovue/tv-footer'` | Single usage or manual registration |
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
## Props
|
|
218
|
+
| Prop | Type | Default | Description |
|
|
219
|
+
|--------|--------|---------|----------------------------------------------------------------|
|
|
220
|
+
| config | Object | `{}` | Configuration object containing all footer data and settings. |
|
|
221
|
+
|
|
222
|
+
### Prop Details
|
|
223
|
+
|
|
224
|
+
#### `config`
|
|
225
|
+
The main configuration object that defines all footer content including brand, navigation, social links, legal links, and copyright information. See [Configuration Object](#configuration-object) for detailed structure.
|
|
226
|
+
|
|
227
|
+
Example:
|
|
228
|
+
```vue
|
|
229
|
+
<TvFooter :config="myFooterConfig" />
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
## Configuration Object
|
|
234
|
+
The `config` prop accepts an object with the following structure:
|
|
235
|
+
|
|
236
|
+
### Brand Section
|
|
237
|
+
```ts
|
|
238
|
+
{
|
|
239
|
+
brand: {
|
|
240
|
+
logo: string, // URL to logo image (optional)
|
|
241
|
+
name: string, // Brand name (optional, shown if no logo or alongside logo)
|
|
242
|
+
url: string // URL for brand link (default: '/')
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Version Display
|
|
248
|
+
```ts
|
|
249
|
+
{
|
|
250
|
+
version: string // Version string to display (e.g., 'v2.4.0')
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Navigation Sections
|
|
255
|
+
```ts
|
|
256
|
+
{
|
|
257
|
+
navigation: [
|
|
258
|
+
{
|
|
259
|
+
title: string, // Section title
|
|
260
|
+
items: [
|
|
261
|
+
{
|
|
262
|
+
label: string, // Link text
|
|
263
|
+
url: string // Link URL
|
|
264
|
+
}
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Social Links
|
|
272
|
+
```ts
|
|
273
|
+
{
|
|
274
|
+
social: [
|
|
275
|
+
{
|
|
276
|
+
label: string, // Accessible label for the link
|
|
277
|
+
url: string, // Social media URL
|
|
278
|
+
iconUrl: string, // URL to icon image (optional)
|
|
279
|
+
icon: string // CSS class for icon (e.g., 'fab fa-github') (optional)
|
|
280
|
+
}
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Legal Links
|
|
286
|
+
```ts
|
|
287
|
+
{
|
|
288
|
+
legal: [
|
|
289
|
+
{
|
|
290
|
+
label: string, // Link text (e.g., 'Privacy Policy')
|
|
291
|
+
url: string // Link URL
|
|
292
|
+
}
|
|
293
|
+
]
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Copyright
|
|
298
|
+
```ts
|
|
299
|
+
{
|
|
300
|
+
copyright: string // Copyright text. Use {year} for automatic year replacement
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Complete Configuration Example
|
|
305
|
+
```js
|
|
306
|
+
const footerConfig = {
|
|
307
|
+
brand: {
|
|
308
|
+
logo: 'https://example.com/logo.png',
|
|
309
|
+
name: 'MyApp',
|
|
310
|
+
url: '/'
|
|
311
|
+
},
|
|
312
|
+
version: 'v2.4.0',
|
|
313
|
+
navigation: [
|
|
314
|
+
{
|
|
315
|
+
title: 'Product',
|
|
316
|
+
items: [
|
|
317
|
+
{ label: 'Features', url: '/features' },
|
|
318
|
+
{ label: 'Pricing', url: '/pricing' },
|
|
319
|
+
{ label: 'Showcase', url: '/showcase' }
|
|
320
|
+
]
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
title: 'Resources',
|
|
324
|
+
items: [
|
|
325
|
+
{ label: 'Documentation', url: '/docs' },
|
|
326
|
+
{ label: 'API Reference', url: '/api' },
|
|
327
|
+
{ label: 'Community', url: '/community' }
|
|
328
|
+
]
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
title: 'Company',
|
|
332
|
+
items: [
|
|
333
|
+
{ label: 'About Us', url: '/about' },
|
|
334
|
+
{ label: 'Blog', url: '/blog' },
|
|
335
|
+
{ label: 'Careers', url: '/careers' }
|
|
336
|
+
]
|
|
337
|
+
}
|
|
338
|
+
],
|
|
339
|
+
social: [
|
|
340
|
+
{
|
|
341
|
+
label: 'GitHub',
|
|
342
|
+
url: 'https://github.com/mycompany',
|
|
343
|
+
iconUrl: '/icons/github.svg'
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
label: 'Twitter',
|
|
347
|
+
url: 'https://twitter.com/mycompany',
|
|
348
|
+
icon: 'fab fa-twitter'
|
|
349
|
+
}
|
|
350
|
+
],
|
|
351
|
+
legal: [
|
|
352
|
+
{ label: 'Privacy Policy', url: '/privacy' },
|
|
353
|
+
{ label: 'Terms of Service', url: '/terms' },
|
|
354
|
+
{ label: 'Cookie Policy', url: '/cookies' }
|
|
355
|
+
],
|
|
356
|
+
copyright: '© {year} MyApp. All rights reserved.'
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
## Composable API
|
|
362
|
+
TvFooter includes a composable `useFooter` that you can use to process footer configuration and build custom footer implementations.
|
|
363
|
+
|
|
364
|
+
### `useFooter(config)`
|
|
365
|
+
```js
|
|
366
|
+
import { useFooter } from '@todovue/tv-footer'
|
|
367
|
+
|
|
368
|
+
const { brand, navigation, social, legal, version, copyright } = useFooter(config)
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
**Parameters:**
|
|
372
|
+
- `config` (Object): Footer configuration object
|
|
373
|
+
|
|
374
|
+
**Returns:**
|
|
375
|
+
- `brand` (ComputedRef): Processed brand configuration
|
|
376
|
+
- `navigation` (ComputedRef): Array of navigation sections
|
|
377
|
+
- `social` (ComputedRef): Array of social links
|
|
378
|
+
- `legal` (ComputedRef): Array of legal links
|
|
379
|
+
- `version` (ComputedRef): Version string
|
|
380
|
+
- `copyright` (ComputedRef): Copyright text with {year} replaced by current year
|
|
381
|
+
|
|
382
|
+
**Example:**
|
|
383
|
+
```vue
|
|
384
|
+
<script setup>
|
|
385
|
+
import { useFooter } from '@todovue/tv-footer'
|
|
386
|
+
|
|
387
|
+
const config = {
|
|
388
|
+
brand: { name: 'MyApp', url: '/' },
|
|
389
|
+
copyright: '© {year} MyApp. All rights reserved.'
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const { brand, copyright } = useFooter(config)
|
|
393
|
+
</script>
|
|
394
|
+
|
|
395
|
+
<template>
|
|
396
|
+
<footer>
|
|
397
|
+
<div>
|
|
398
|
+
<a :href="brand.url">{{ brand.name }}</a>
|
|
399
|
+
</div>
|
|
400
|
+
<div>{{ copyright }}</div>
|
|
401
|
+
</footer>
|
|
402
|
+
</template>
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**Features:**
|
|
406
|
+
- Automatic current year replacement in copyright text
|
|
407
|
+
- Safe defaults for missing configuration
|
|
408
|
+
- Reactive computed properties
|
|
409
|
+
- Type-safe array validation
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
## Usage Examples
|
|
413
|
+
|
|
414
|
+
### Minimal Footer
|
|
415
|
+
```vue
|
|
416
|
+
<script setup>
|
|
417
|
+
import { TvFooter } from '@todovue/tv-footer'
|
|
418
|
+
import '@todovue/tv-footer/style.css'
|
|
419
|
+
|
|
420
|
+
const config = {
|
|
421
|
+
copyright: '© {year} My Company. All rights reserved.'
|
|
422
|
+
}
|
|
423
|
+
</script>
|
|
424
|
+
|
|
425
|
+
<template>
|
|
426
|
+
<TvFooter :config="config" />
|
|
427
|
+
</template>
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Footer with Brand and Navigation
|
|
431
|
+
```vue
|
|
432
|
+
<script setup>
|
|
433
|
+
import { TvFooter } from '@todovue/tv-footer'
|
|
434
|
+
import '@todovue/tv-footer/style.css'
|
|
435
|
+
|
|
436
|
+
const config = {
|
|
437
|
+
brand: {
|
|
438
|
+
logo: 'https://example.com/logo.png',
|
|
439
|
+
name: 'MyApp',
|
|
440
|
+
url: '/'
|
|
441
|
+
},
|
|
442
|
+
version: 'v1.0.0',
|
|
443
|
+
navigation: [
|
|
444
|
+
{
|
|
445
|
+
title: 'Product',
|
|
446
|
+
items: [
|
|
447
|
+
{ label: 'Features', url: '/features' },
|
|
448
|
+
{ label: 'Pricing', url: '/pricing' }
|
|
449
|
+
]
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
title: 'Support',
|
|
453
|
+
items: [
|
|
454
|
+
{ label: 'Help Center', url: '/help' },
|
|
455
|
+
{ label: 'Contact', url: '/contact' }
|
|
456
|
+
]
|
|
457
|
+
}
|
|
458
|
+
],
|
|
459
|
+
copyright: '© {year} MyApp. All rights reserved.'
|
|
460
|
+
}
|
|
461
|
+
</script>
|
|
462
|
+
|
|
463
|
+
<template>
|
|
464
|
+
<TvFooter :config="config" />
|
|
465
|
+
</template>
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Complete Footer with Social & Legal Links
|
|
469
|
+
```vue
|
|
470
|
+
<script setup>
|
|
471
|
+
import { TvFooter } from '@todovue/tv-footer'
|
|
472
|
+
import '@todovue/tv-footer/style.css'
|
|
473
|
+
import GitHubIcon from './assets/github.svg'
|
|
474
|
+
import TwitterIcon from './assets/twitter.svg'
|
|
475
|
+
|
|
476
|
+
const config = {
|
|
477
|
+
brand: {
|
|
478
|
+
logo: 'https://example.com/logo.png',
|
|
479
|
+
name: 'MyApp',
|
|
480
|
+
url: '/'
|
|
481
|
+
},
|
|
482
|
+
version: 'v2.4.0',
|
|
483
|
+
navigation: [
|
|
484
|
+
{
|
|
485
|
+
title: 'Product',
|
|
486
|
+
items: [
|
|
487
|
+
{ label: 'Features', url: '/features' },
|
|
488
|
+
{ label: 'Pricing', url: '/pricing' },
|
|
489
|
+
{ label: 'Showcase', url: '/showcase' }
|
|
490
|
+
]
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
title: 'Resources',
|
|
494
|
+
items: [
|
|
495
|
+
{ label: 'Documentation', url: '/docs' },
|
|
496
|
+
{ label: 'API Reference', url: '/api' },
|
|
497
|
+
{ label: 'Community', url: '/community' }
|
|
498
|
+
]
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
title: 'Company',
|
|
502
|
+
items: [
|
|
503
|
+
{ label: 'About Us', url: '/about' },
|
|
504
|
+
{ label: 'Blog', url: '/blog' },
|
|
505
|
+
{ label: 'Careers', url: '/careers' }
|
|
506
|
+
]
|
|
507
|
+
}
|
|
508
|
+
],
|
|
509
|
+
social: [
|
|
510
|
+
{
|
|
511
|
+
label: 'GitHub',
|
|
512
|
+
url: 'https://github.com/mycompany',
|
|
513
|
+
iconUrl: GitHubIcon
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
label: 'Twitter',
|
|
517
|
+
url: 'https://twitter.com/mycompany',
|
|
518
|
+
iconUrl: TwitterIcon
|
|
519
|
+
}
|
|
520
|
+
],
|
|
521
|
+
legal: [
|
|
522
|
+
{ label: 'Privacy Policy', url: '/privacy' },
|
|
523
|
+
{ label: 'Terms of Service', url: '/terms' },
|
|
524
|
+
{ label: 'Cookie Policy', url: '/cookies' }
|
|
525
|
+
],
|
|
526
|
+
copyright: '© {year} MyApp. All rights reserved.'
|
|
527
|
+
}
|
|
528
|
+
</script>
|
|
529
|
+
|
|
530
|
+
<template>
|
|
531
|
+
<div>
|
|
532
|
+
<!-- Your page content -->
|
|
533
|
+
<main>
|
|
534
|
+
<!-- ... -->
|
|
535
|
+
</main>
|
|
536
|
+
|
|
537
|
+
<!-- Footer -->
|
|
538
|
+
<TvFooter :config="config" />
|
|
539
|
+
</div>
|
|
540
|
+
</template>
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Using Icon Libraries (Font Awesome, etc.)
|
|
544
|
+
```vue
|
|
545
|
+
<script setup>
|
|
546
|
+
import { TvFooter } from '@todovue/tv-footer'
|
|
547
|
+
import '@todovue/tv-footer/style.css'
|
|
548
|
+
|
|
549
|
+
const config = {
|
|
550
|
+
social: [
|
|
551
|
+
{
|
|
552
|
+
label: 'GitHub',
|
|
553
|
+
url: 'https://github.com/mycompany',
|
|
554
|
+
icon: 'fab fa-github' // Font Awesome class
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
label: 'Twitter',
|
|
558
|
+
url: 'https://twitter.com/mycompany',
|
|
559
|
+
icon: 'fab fa-twitter' // Font Awesome class
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
label: 'LinkedIn',
|
|
563
|
+
url: 'https://linkedin.com/company/mycompany',
|
|
564
|
+
icon: 'fab fa-linkedin' // Font Awesome class
|
|
565
|
+
}
|
|
566
|
+
],
|
|
567
|
+
copyright: '© {year} MyApp. All rights reserved.'
|
|
568
|
+
}
|
|
569
|
+
</script>
|
|
570
|
+
|
|
571
|
+
<template>
|
|
572
|
+
<TvFooter :config="config" />
|
|
573
|
+
</template>
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Custom Implementation with Composable
|
|
577
|
+
```vue
|
|
578
|
+
<script setup>
|
|
579
|
+
import { useFooter } from '@todovue/tv-footer'
|
|
580
|
+
|
|
581
|
+
const config = {
|
|
582
|
+
brand: {
|
|
583
|
+
name: 'MyApp',
|
|
584
|
+
url: '/'
|
|
585
|
+
},
|
|
586
|
+
navigation: [
|
|
587
|
+
{
|
|
588
|
+
title: 'Links',
|
|
589
|
+
items: [
|
|
590
|
+
{ label: 'Home', url: '/' },
|
|
591
|
+
{ label: 'About', url: '/about' }
|
|
592
|
+
]
|
|
593
|
+
}
|
|
594
|
+
],
|
|
595
|
+
copyright: '© {year} MyApp. All rights reserved.'
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const { brand, navigation, copyright } = useFooter(config)
|
|
599
|
+
</script>
|
|
600
|
+
|
|
601
|
+
<template>
|
|
602
|
+
<footer class="custom-footer">
|
|
603
|
+
<div class="custom-footer__brand">
|
|
604
|
+
<a :href="brand.url">{{ brand.name }}</a>
|
|
605
|
+
</div>
|
|
606
|
+
|
|
607
|
+
<nav v-for="(section, index) in navigation" :key="index">
|
|
608
|
+
<h4>{{ section.title }}</h4>
|
|
609
|
+
<ul>
|
|
610
|
+
<li v-for="(link, i) in section.items" :key="i">
|
|
611
|
+
<a :href="link.url">{{ link.label }}</a>
|
|
612
|
+
</li>
|
|
613
|
+
</ul>
|
|
614
|
+
</nav>
|
|
615
|
+
|
|
616
|
+
<div class="custom-footer__copyright">
|
|
617
|
+
{{ copyright }}
|
|
618
|
+
</div>
|
|
619
|
+
</footer>
|
|
620
|
+
</template>
|
|
621
|
+
|
|
622
|
+
<style scoped>
|
|
623
|
+
.custom-footer {
|
|
624
|
+
/* Your custom styles */
|
|
625
|
+
}
|
|
626
|
+
</style>
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
## Styling
|
|
631
|
+
TvFooter comes with built-in responsive styles and light/dark mode support.
|
|
632
|
+
|
|
633
|
+
### Built-in Features
|
|
634
|
+
- **Responsive Grid Layout**:
|
|
635
|
+
- Mobile (< 640px): 1 column
|
|
636
|
+
- Tablet (≥ 640px): 2 columns
|
|
637
|
+
- Desktop (≥ 1024px): 4 columns
|
|
638
|
+
- **Dark Mode Support**: Automatically adapts to `.dark-mode` class on parent
|
|
639
|
+
- **Light Mode Support**: Adapts to `.light-mode` class on parent
|
|
640
|
+
- **Hover Effects**: Smooth transitions on links and social icons
|
|
641
|
+
- **Backdrop Blur**: Modern glassmorphism effect on social icons
|
|
642
|
+
|
|
643
|
+
### Customization
|
|
644
|
+
You can override the default styles by targeting the CSS classes:
|
|
645
|
+
|
|
646
|
+
```css
|
|
647
|
+
/* Override brand logo size */
|
|
648
|
+
.tv-footer__logo img {
|
|
649
|
+
height: 80px;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/* Change link colors */
|
|
653
|
+
.tv-footer__link {
|
|
654
|
+
color: #your-color;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
.tv-footer__link:hover {
|
|
658
|
+
color: #your-hover-color;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/* Customize social icons */
|
|
662
|
+
.tv-footer__social-link {
|
|
663
|
+
background-color: #your-background;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/* Adjust spacing */
|
|
667
|
+
.tv-footer {
|
|
668
|
+
padding: 3rem 1rem;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/* Customize container max-width */
|
|
672
|
+
.tv-footer__container {
|
|
673
|
+
max-width: 1400px;
|
|
674
|
+
}
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### Available CSS Classes
|
|
678
|
+
- `.tv-footer` - Main footer container
|
|
679
|
+
- `.tv-footer__container` - Inner grid container
|
|
680
|
+
- `.tv-footer__brand` - Brand section
|
|
681
|
+
- `.tv-footer__logo` - Brand logo/name link
|
|
682
|
+
- `.tv-footer__version` - Version display
|
|
683
|
+
- `.tv-footer__section` - Navigation or social section
|
|
684
|
+
- `.tv-footer__section-title` - Section title
|
|
685
|
+
- `.tv-footer__links` - List of links
|
|
686
|
+
- `.tv-footer__link` - Individual link
|
|
687
|
+
- `.tv-footer__social` - Social links container
|
|
688
|
+
- `.tv-footer__social-link` - Individual social link
|
|
689
|
+
- `.tv-footer__social-icon-img` - Social icon image
|
|
690
|
+
- `.tv-footer__bottom` - Bottom section with copyright and legal
|
|
691
|
+
- `.tv-footer__legal` - Legal links container
|
|
692
|
+
|
|
693
|
+
---
|
|
694
|
+
## Accessibility
|
|
695
|
+
- **Semantic HTML**: Uses proper `<footer>`, `<nav>`, `<ul>`, and `<a>` elements
|
|
696
|
+
- **ARIA Labels**: Social links include accessible labels
|
|
697
|
+
- **External Link Safety**: Social links include `rel="noopener noreferrer"` for security
|
|
698
|
+
- **Keyboard Navigation**: All links are keyboard accessible
|
|
699
|
+
- **Focus States**: Clear focus indicators for keyboard navigation
|
|
700
|
+
- **Screen Reader Friendly**: Proper heading hierarchy and semantic structure
|
|
701
|
+
- **Alternative Text**: Images include alt attributes
|
|
702
|
+
|
|
703
|
+
---
|
|
704
|
+
## SSR Notes
|
|
705
|
+
- **SSR-Safe**: No direct `window`/`document` access during module evaluation
|
|
706
|
+
- **Nuxt 3 Compatible**: Works seamlessly with Nuxt 3 out of the box
|
|
707
|
+
- **Hydration Safe**: No hydration mismatches
|
|
708
|
+
- **Universal Rendering**: Works in both client and server contexts
|
|
709
|
+
- **Vue 3 Composition API**: Uses modern Vue 3 composables
|
|
710
|
+
|
|
711
|
+
---
|
|
712
|
+
## Development
|
|
713
|
+
```bash
|
|
714
|
+
git clone https://github.com/TODOvue/tv-footer.git
|
|
715
|
+
cd tv-footer
|
|
716
|
+
npm install
|
|
717
|
+
npm run dev # run demo playground
|
|
718
|
+
npm run build # build library
|
|
719
|
+
```
|
|
720
|
+
Local demo served from Vite using `index.html` and demo examples in `src/demo`.
|
|
721
|
+
|
|
722
|
+
---
|
|
723
|
+
## Contributing
|
|
724
|
+
PRs and issues welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
|
725
|
+
|
|
726
|
+
---
|
|
727
|
+
## License
|
|
728
|
+
MIT © TODOvue
|
|
729
|
+
|
|
730
|
+
---
|
|
731
|
+
### Attributions
|
|
732
|
+
Crafted for the TODOvue component ecosystem
|
package/dist/entry.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Plugin } from 'vue';
|
|
2
|
+
import { default as _TvFooter } from './components/TvFooter.vue';
|
|
3
|
+
declare const TvFooter: typeof _TvFooter & Plugin;
|
|
4
|
+
export { TvFooter };
|
|
5
|
+
export declare const TvFooterPlugin: Plugin;
|
|
6
|
+
export default TvFooter;
|
|
7
|
+
declare module 'vue' {
|
|
8
|
+
interface GlobalComponents {
|
|
9
|
+
TvFooter: typeof TvFooter;
|
|
10
|
+
}
|
|
11
|
+
}
|
package/dist/favicon.ico
ADDED
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue");function f(t){const u=e.computed(()=>t?.brand||null),r=e.computed(()=>!t?.navigation||!Array.isArray(t.navigation)?[]:t.navigation),_=e.computed(()=>!t?.social||!Array.isArray(t.social)?[]:t.social),n=e.computed(()=>!t?.legal||!Array.isArray(t.legal)?[]:t.legal),l=e.computed(()=>t?.version||""),a=e.computed(()=>t?.copyright||""),i=new Date().getFullYear(),d=e.computed(()=>a.value.replace("{year}",i));return{brand:u,navigation:r,social:_,legal:n,version:l,copyright:d}}const v={class:"tv-footer"},B={class:"tv-footer__container"},h={key:0,class:"tv-footer__brand"},y=["href"],g=["src","alt"],E={key:1},N={key:0,class:"tv-footer__version"},V={key:0,class:"tv-footer__section-title"},b={class:"tv-footer__links"},F=["href"],C={key:1,class:"tv-footer__section"},S={class:"tv-footer__social"},D=["href"],T=["src","alt"],A={key:2},x={class:"tv-footer__bottom"},L={key:0},P={key:1,class:"tv-footer__legal"},j={class:"tv-footer__links",style:{"flex-direction":"row",gap:"1.5rem"}},w=["href"],M={__name:"TvFooter",props:{config:{type:Object,default:()=>({})}},setup(t){const u=t,{brand:r,navigation:_,social:n,legal:l,version:a,copyright:i}=f(u.config);return(d,k)=>(e.openBlock(),e.createElementBlock("footer",v,[e.createElementVNode("div",B,[e.unref(r)?(e.openBlock(),e.createElementBlock("div",h,[e.createElementVNode("a",{href:e.unref(r).url||"/",class:"tv-footer__logo"},[e.unref(r).logo?(e.openBlock(),e.createElementBlock("img",{key:0,src:e.unref(r).logo,alt:e.unref(r).name},null,8,g)):e.createCommentVNode("",!0),e.unref(r).name?(e.openBlock(),e.createElementBlock("span",E,e.toDisplayString(e.unref(r).name),1)):e.createCommentVNode("",!0)],8,y),e.unref(a)?(e.openBlock(),e.createElementBlock("span",N,e.toDisplayString(e.unref(a)),1)):e.createCommentVNode("",!0)])):e.createCommentVNode("",!0),(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(e.unref(_),(o,c)=>(e.openBlock(),e.createElementBlock("div",{key:c,class:"tv-footer__section"},[o.title?(e.openBlock(),e.createElementBlock("h3",V,e.toDisplayString(o.title),1)):e.createCommentVNode("",!0),e.createElementVNode("ul",b,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(o.items,(m,p)=>(e.openBlock(),e.createElementBlock("li",{key:p},[e.createElementVNode("a",{href:m.url,class:"tv-footer__link"},e.toDisplayString(m.label),9,F)]))),128))])]))),128)),e.unref(n)&&e.unref(n).length?(e.openBlock(),e.createElementBlock("div",C,[e.createElementVNode("div",S,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(e.unref(n),(o,c)=>(e.openBlock(),e.createElementBlock("a",{key:c,href:o.url,class:"tv-footer__social-link",target:"_blank",rel:"noopener noreferer"},[o.iconUrl?(e.openBlock(),e.createElementBlock("img",{key:0,src:o.iconUrl,alt:o.label,class:"tv-footer__social-icon-img"},null,8,T)):o.icon?(e.openBlock(),e.createElementBlock("i",{key:1,class:e.normalizeClass(o.icon)},null,2)):(e.openBlock(),e.createElementBlock("span",A,e.toDisplayString(o.label),1))],8,D))),128))])])):e.createCommentVNode("",!0)]),e.createElementVNode("div",x,[e.unref(i)?(e.openBlock(),e.createElementBlock("div",L,e.toDisplayString(e.unref(i)),1)):e.createCommentVNode("",!0),e.unref(l)&&e.unref(l).length?(e.openBlock(),e.createElementBlock("div",P,[e.createElementVNode("ul",j,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(e.unref(l),(o,c)=>(e.openBlock(),e.createElementBlock("li",{key:c},[e.createElementVNode("a",{href:o.url,class:"tv-footer__link"},e.toDisplayString(o.label),9,w)]))),128))])])):e.createCommentVNode("",!0)])]))}},s=M;s.install=t=>{t.component("TvFooter",s)};const O={install:s.install};exports.TvFooter=s;exports.TvFooterPlugin=O;exports.default=s;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@charset "UTF-8";.tv-footer{width:100%;padding:4rem 1.5rem;font-family:system-ui,-apple-system,sans-serif;box-sizing:border-box;transition:background-color .3s ease,color .3s ease;background-color:#0e131f;color:#cbd5e1;border-top:1px solid rgba(203,213,225,.1)}.tv-footer .tv-footer__logo,.tv-footer .tv-footer__section-title,.dark-mode .tv-footer .tv-footer__logo,.dark-mode .tv-footer .tv-footer__section-title{color:#fff}.tv-footer .tv-footer__social-link,.dark-mode .tv-footer .tv-footer__social-link{background-color:#ffffff0d;color:inherit}.tv-footer .tv-footer__social-link:hover,.dark-mode .tv-footer .tv-footer__social-link:hover{background-color:#ef233c;color:#fff}.light-mode .tv-footer{background-color:#b9c4df;color:#1e293b;border-top-color:#1e293b1a}.light-mode .tv-footer .tv-footer__logo,.light-mode .tv-footer .tv-footer__section-title{color:#1e293b}.light-mode .tv-footer .tv-footer__social-link{background-color:#0000000d;color:inherit}.light-mode .tv-footer .tv-footer__social-link:hover{background-color:#ef233c;color:#f1f9f9}.light-mode .tv-footer .tv-footer__link:hover{color:#d00f27}.tv-footer__container{display:grid;grid-template-columns:1fr;gap:2.5rem;max-width:1200px;margin:0 auto}@media(min-width:640px){.tv-footer__container{grid-template-columns:repeat(2,1fr)}}@media(min-width:1024px){.tv-footer__container{grid-template-columns:repeat(4,1fr)}}.tv-footer__brand{display:flex;flex-direction:column;gap:1rem}.tv-footer__logo{font-weight:700;font-size:1.5rem;text-decoration:none;display:inline-flex;align-items:center;gap:.5rem}.tv-footer__logo img{height:100px;width:auto}.tv-footer__version{font-size:.875rem;opacity:.7}.tv-footer__section-title{font-weight:600;margin-bottom:1.25rem;font-size:1rem}.tv-footer__links{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:.75rem}.tv-footer__link{color:inherit;text-decoration:none;font-size:.95rem;transition:color .2s;cursor:pointer}.tv-footer__link:hover{color:#ef233c}.tv-footer__social{display:flex;gap:1rem;flex-wrap:wrap}.tv-footer__social-link{font-size:1.25rem;text-decoration:none;display:flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;border-radius:50%;transition:all .2s;transform:translateY(0)}.tv-footer__social-link:hover{transform:translateY(-2px)}.tv-footer__social-link img{width:1.25rem;height:1.25rem;object-fit:contain;display:block}.tv-footer__bottom{border-top-style:solid;border-top-width:1px;margin-top:4rem;padding-top:2rem;text-align:center;font-size:.875rem;display:flex;flex-direction:column;align-items:center;gap:1rem;max-width:1200px;margin-left:auto;margin-right:auto}@media(min-width:640px){.tv-footer__bottom{flex-direction:row;justify-content:space-between}}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { computed as c, createElementBlock as t, openBlock as e, createElementVNode as l, createCommentVNode as a, unref as o, toDisplayString as i, Fragment as y, renderList as f, normalizeClass as x } from "vue";
|
|
2
|
+
function T(r) {
|
|
3
|
+
const k = c(() => r?.brand || null), n = c(() => !r?.navigation || !Array.isArray(r.navigation) ? [] : r.navigation), p = c(() => !r?.social || !Array.isArray(r.social) ? [] : r.social), _ = c(() => !r?.legal || !Array.isArray(r.legal) ? [] : r.legal), u = c(() => r?.version || ""), v = c(() => r?.copyright || ""), h = (/* @__PURE__ */ new Date()).getFullYear(), m = c(() => v.value.replace("{year}", h));
|
|
4
|
+
return {
|
|
5
|
+
brand: k,
|
|
6
|
+
navigation: n,
|
|
7
|
+
social: p,
|
|
8
|
+
legal: _,
|
|
9
|
+
version: u,
|
|
10
|
+
copyright: m
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const C = { class: "tv-footer" }, w = { class: "tv-footer__container" }, B = {
|
|
14
|
+
key: 0,
|
|
15
|
+
class: "tv-footer__brand"
|
|
16
|
+
}, D = ["href"], E = ["src", "alt"], N = { key: 1 }, U = {
|
|
17
|
+
key: 0,
|
|
18
|
+
class: "tv-footer__version"
|
|
19
|
+
}, V = {
|
|
20
|
+
key: 0,
|
|
21
|
+
class: "tv-footer__section-title"
|
|
22
|
+
}, Y = { class: "tv-footer__links" }, j = ["href"], z = {
|
|
23
|
+
key: 1,
|
|
24
|
+
class: "tv-footer__section"
|
|
25
|
+
}, L = { class: "tv-footer__social" }, O = ["href"], P = ["src", "alt"], S = { key: 2 }, q = { class: "tv-footer__bottom" }, G = { key: 0 }, H = {
|
|
26
|
+
key: 1,
|
|
27
|
+
class: "tv-footer__legal"
|
|
28
|
+
}, I = {
|
|
29
|
+
class: "tv-footer__links",
|
|
30
|
+
style: { "flex-direction": "row", gap: "1.5rem" }
|
|
31
|
+
}, J = ["href"], K = {
|
|
32
|
+
__name: "TvFooter",
|
|
33
|
+
props: {
|
|
34
|
+
config: {
|
|
35
|
+
type: Object,
|
|
36
|
+
default: () => ({})
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
setup(r) {
|
|
40
|
+
const k = r, { brand: n, navigation: p, social: _, legal: u, version: v, copyright: h } = T(k.config);
|
|
41
|
+
return (m, F) => (e(), t("footer", C, [
|
|
42
|
+
l("div", w, [
|
|
43
|
+
o(n) ? (e(), t("div", B, [
|
|
44
|
+
l("a", {
|
|
45
|
+
href: o(n).url || "/",
|
|
46
|
+
class: "tv-footer__logo"
|
|
47
|
+
}, [
|
|
48
|
+
o(n).logo ? (e(), t("img", {
|
|
49
|
+
key: 0,
|
|
50
|
+
src: o(n).logo,
|
|
51
|
+
alt: o(n).name
|
|
52
|
+
}, null, 8, E)) : a("", !0),
|
|
53
|
+
o(n).name ? (e(), t("span", N, i(o(n).name), 1)) : a("", !0)
|
|
54
|
+
], 8, D),
|
|
55
|
+
o(v) ? (e(), t("span", U, i(o(v)), 1)) : a("", !0)
|
|
56
|
+
])) : a("", !0),
|
|
57
|
+
(e(!0), t(y, null, f(o(p), (s, d) => (e(), t("div", {
|
|
58
|
+
key: d,
|
|
59
|
+
class: "tv-footer__section"
|
|
60
|
+
}, [
|
|
61
|
+
s.title ? (e(), t("h3", V, i(s.title), 1)) : a("", !0),
|
|
62
|
+
l("ul", Y, [
|
|
63
|
+
(e(!0), t(y, null, f(s.items, (b, A) => (e(), t("li", { key: A }, [
|
|
64
|
+
l("a", {
|
|
65
|
+
href: b.url,
|
|
66
|
+
class: "tv-footer__link"
|
|
67
|
+
}, i(b.label), 9, j)
|
|
68
|
+
]))), 128))
|
|
69
|
+
])
|
|
70
|
+
]))), 128)),
|
|
71
|
+
o(_) && o(_).length ? (e(), t("div", z, [
|
|
72
|
+
l("div", L, [
|
|
73
|
+
(e(!0), t(y, null, f(o(_), (s, d) => (e(), t("a", {
|
|
74
|
+
key: d,
|
|
75
|
+
href: s.url,
|
|
76
|
+
class: "tv-footer__social-link",
|
|
77
|
+
target: "_blank",
|
|
78
|
+
rel: "noopener noreferer"
|
|
79
|
+
}, [
|
|
80
|
+
s.iconUrl ? (e(), t("img", {
|
|
81
|
+
key: 0,
|
|
82
|
+
src: s.iconUrl,
|
|
83
|
+
alt: s.label,
|
|
84
|
+
class: "tv-footer__social-icon-img"
|
|
85
|
+
}, null, 8, P)) : s.icon ? (e(), t("i", {
|
|
86
|
+
key: 1,
|
|
87
|
+
class: x(s.icon)
|
|
88
|
+
}, null, 2)) : (e(), t("span", S, i(s.label), 1))
|
|
89
|
+
], 8, O))), 128))
|
|
90
|
+
])
|
|
91
|
+
])) : a("", !0)
|
|
92
|
+
]),
|
|
93
|
+
l("div", q, [
|
|
94
|
+
o(h) ? (e(), t("div", G, i(o(h)), 1)) : a("", !0),
|
|
95
|
+
o(u) && o(u).length ? (e(), t("div", H, [
|
|
96
|
+
l("ul", I, [
|
|
97
|
+
(e(!0), t(y, null, f(o(u), (s, d) => (e(), t("li", { key: d }, [
|
|
98
|
+
l("a", {
|
|
99
|
+
href: s.url,
|
|
100
|
+
class: "tv-footer__link"
|
|
101
|
+
}, i(s.label), 9, J)
|
|
102
|
+
]))), 128))
|
|
103
|
+
])
|
|
104
|
+
])) : a("", !0)
|
|
105
|
+
])
|
|
106
|
+
]));
|
|
107
|
+
}
|
|
108
|
+
}, g = K;
|
|
109
|
+
g.install = (r) => {
|
|
110
|
+
r.component("TvFooter", g);
|
|
111
|
+
};
|
|
112
|
+
const Q = {
|
|
113
|
+
install: g.install
|
|
114
|
+
};
|
|
115
|
+
export {
|
|
116
|
+
g as TvFooter,
|
|
117
|
+
Q as TvFooterPlugin,
|
|
118
|
+
g as default
|
|
119
|
+
};
|
package/nuxt.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
import { defineNuxtModule } from '@nuxt/kit'
|
|
3
|
+
|
|
4
|
+
export default defineNuxtModule({
|
|
5
|
+
meta: {
|
|
6
|
+
name: '@todovue/tv-footer',
|
|
7
|
+
configKey: 'tvFooter'
|
|
8
|
+
},
|
|
9
|
+
setup(_options, nuxt) {
|
|
10
|
+
const cssPath = '@todovue/tv-footer/style.css';
|
|
11
|
+
if (!nuxt.options.css.includes(cssPath)) {
|
|
12
|
+
nuxt.options.css.push(cssPath);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@todovue/tv-footer",
|
|
3
|
+
"private": false,
|
|
4
|
+
"author": "Cristhian Daza",
|
|
5
|
+
"description": "A simple and customizable footer component for Vue.js applications, perfect for enhancing your web projects with ease.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"version": "1.0.0",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"homepage": "https://ui.todovue.blog/footer",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/TODOvue/tv-footer.git"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/TODOvue/tv-footer/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"todovue",
|
|
19
|
+
"front-end",
|
|
20
|
+
"web",
|
|
21
|
+
"vue",
|
|
22
|
+
"vuejs",
|
|
23
|
+
"vue-js",
|
|
24
|
+
"footer",
|
|
25
|
+
"show-footer",
|
|
26
|
+
"vue-footer",
|
|
27
|
+
"vue-footer-component"
|
|
28
|
+
],
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"import": "./dist/tv-footer.es.js",
|
|
32
|
+
"require": "./dist/tv-footer.cjs.js"
|
|
33
|
+
},
|
|
34
|
+
"./style.css": "./dist/tv-footer.css",
|
|
35
|
+
"./nuxt": "./nuxt.js"
|
|
36
|
+
},
|
|
37
|
+
"main": "dist/tv-footer.cjs.js",
|
|
38
|
+
"module": "dist/tv-footer.es.js",
|
|
39
|
+
"types": "dist/tv-footer.d.ts",
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"LICENSE",
|
|
43
|
+
"README.md",
|
|
44
|
+
"nuxt.js"
|
|
45
|
+
],
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=20.19.0"
|
|
48
|
+
},
|
|
49
|
+
"sideEffects": [
|
|
50
|
+
"*.css",
|
|
51
|
+
"*.scss",
|
|
52
|
+
"dist/*.css"
|
|
53
|
+
],
|
|
54
|
+
"scripts": {
|
|
55
|
+
"dev": "vite",
|
|
56
|
+
"build": "vite build",
|
|
57
|
+
"build:demo": "cp README.md public/ && cp CHANGELOG.md public/ && VITE_BUILD_TARGET=demo vite build"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"vue": "^3.5.26"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@todovue/tv-demo": "^1.2.7",
|
|
64
|
+
"@vitejs/plugin-vue": "^6.0.3",
|
|
65
|
+
"sass": "^1.97.1",
|
|
66
|
+
"vite": "^7.3.0",
|
|
67
|
+
"vite-plugin-dts": "^4.5.4"
|
|
68
|
+
}
|
|
69
|
+
}
|