@todovue/tv-footer 1.0.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 +67 -0
- package/README.md +47 -19
- package/dist/tv-footer.cjs.js +1 -1
- package/dist/tv-footer.css +1 -1
- package/dist/tv-footer.es.js +132 -81
- package/package.json +15 -10
- package/src/assets/scss/_variables.scss +12 -0
- package/src/assets/scss/style.scss +396 -0
- package/src/components/TvFooter.vue +101 -0
- package/src/composables/useFooter.js +45 -0
- package/src/demo/Demo.vue +21 -0
- package/src/demo/utils/demos/default.vue +68 -0
- package/src/demo/utils/demos/newsletter.vue +79 -0
- package/src/demo/utils/icons/facebook.png +0 -0
- package/src/demo/utils/icons/github-white.svg +1 -0
- package/src/demo/utils/icons/github.svg +1 -0
- package/src/demo/utils/icons/todovue.png +0 -0
- package/src/demo/utils/mocks.js +94 -0
- package/src/entry.ts +21 -0
- package/src/main.js +7 -0
- package/src/style.scss +1 -0
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/README.md
CHANGED
|
@@ -16,12 +16,11 @@ A simple, customizable, and responsive Vue 3 footer component with support for b
|
|
|
16
16
|
|
|
17
17
|
> Demo: https://ui.todovue.blog/footer
|
|
18
18
|
|
|
19
|
-
---
|
|
20
19
|
## Table of Contents
|
|
21
20
|
- [Features](#features)
|
|
22
21
|
- [Installation](#installation)
|
|
23
22
|
- [Quick Start (SPA)](#quick-start-spa)
|
|
24
|
-
- [Nuxt
|
|
23
|
+
- [Nuxt 4 / SSR Usage](#nuxt-4--ssr-usage)
|
|
25
24
|
- [Component Registration Options](#component-registration-options)
|
|
26
25
|
- [Props](#props)
|
|
27
26
|
- [Configuration Object](#configuration-object)
|
|
@@ -34,7 +33,6 @@ A simple, customizable, and responsive Vue 3 footer component with support for b
|
|
|
34
33
|
- [Contributing](#contributing)
|
|
35
34
|
- [License](#license)
|
|
36
35
|
|
|
37
|
-
---
|
|
38
36
|
## Features
|
|
39
37
|
- Fully responsive grid layout (1 column on mobile, 2 on tablet, 4 on desktop)
|
|
40
38
|
- Customizable brand section with logo and version display
|
|
@@ -49,7 +47,6 @@ A simple, customizable, and responsive Vue 3 footer component with support for b
|
|
|
49
47
|
- Lightweight and tree-shakeable
|
|
50
48
|
- TypeScript support
|
|
51
49
|
|
|
52
|
-
---
|
|
53
50
|
## Installation
|
|
54
51
|
Using npm:
|
|
55
52
|
```bash
|
|
@@ -93,7 +90,6 @@ export default defineNuxtConfig({
|
|
|
93
90
|
|
|
94
91
|
Then register the component in a plugin as shown in the [Nuxt 3 / SSR Usage](#nuxt-3--ssr-usage) section.
|
|
95
92
|
|
|
96
|
-
---
|
|
97
93
|
## Quick Start (SPA)
|
|
98
94
|
Global registration (main.js / main.ts):
|
|
99
95
|
```js
|
|
@@ -142,8 +138,7 @@ const footerConfig = {
|
|
|
142
138
|
```
|
|
143
139
|
**Note:** Don't forget to import the CSS in your main entry file as shown above.
|
|
144
140
|
|
|
145
|
-
|
|
146
|
-
## Nuxt 3 / SSR Usage
|
|
141
|
+
## Nuxt 4 / SSR Usage
|
|
147
142
|
First, add the module to your `nuxt.config.ts`:
|
|
148
143
|
```ts
|
|
149
144
|
// nuxt.config.ts
|
|
@@ -205,7 +200,6 @@ import { TvFooter } from '@todovue/tv-footer'
|
|
|
205
200
|
</script>
|
|
206
201
|
```
|
|
207
202
|
|
|
208
|
-
---
|
|
209
203
|
## Component Registration Options
|
|
210
204
|
| Approach | When to use |
|
|
211
205
|
|--------------------------------------------------------------------------|------------------------------------------------|
|
|
@@ -213,7 +207,6 @@ import { TvFooter } from '@todovue/tv-footer'
|
|
|
213
207
|
| Local named import `{ TvFooter }` | Isolated / code-split contexts |
|
|
214
208
|
| Direct default import `import TvFooter from '@todovue/tv-footer'` | Single usage or manual registration |
|
|
215
209
|
|
|
216
|
-
---
|
|
217
210
|
## Props
|
|
218
211
|
| Prop | Type | Default | Description |
|
|
219
212
|
|--------|--------|---------|----------------------------------------------------------------|
|
|
@@ -229,7 +222,32 @@ Example:
|
|
|
229
222
|
<TvFooter :config="myFooterConfig" />
|
|
230
223
|
```
|
|
231
224
|
|
|
232
|
-
|
|
225
|
+
## Events
|
|
226
|
+
|
|
227
|
+
| Event Name | Payload | Description |
|
|
228
|
+
|:------------|:--------|:--------------------------------------------------------------------------------|
|
|
229
|
+
| `subscribe` | `email` | Emitted when the user submits the newsletter form. Payload is the email string. |
|
|
230
|
+
|
|
231
|
+
## Slots
|
|
232
|
+
TvFooter provides slots to allow custom content injection for specific sections.
|
|
233
|
+
|
|
234
|
+
| Slot Name | Props (Scope) | Description |
|
|
235
|
+
|:-------------|:-------------------------------|:-------------------------------------------------|
|
|
236
|
+
| `brand` | `{ brand, version }` | Replace the brand/logo section |
|
|
237
|
+
| `newsletter` | `{ newsletter }` | Replace the newsletter subscription section |
|
|
238
|
+
| `bottom` | `{ copyright, legal }` | Replace the bottom section (copyright + legal) |
|
|
239
|
+
|
|
240
|
+
Example Usage:
|
|
241
|
+
```vue
|
|
242
|
+
<TvFooter :config="config">
|
|
243
|
+
<template #brand="{ brand }">
|
|
244
|
+
<div class="custom-brand">
|
|
245
|
+
<h1>{{ brand.name }}</h1>
|
|
246
|
+
</div>
|
|
247
|
+
</template>
|
|
248
|
+
</TvFooter>
|
|
249
|
+
```
|
|
250
|
+
|
|
233
251
|
## Configuration Object
|
|
234
252
|
The `config` prop accepts an object with the following structure:
|
|
235
253
|
|
|
@@ -301,6 +319,25 @@ The `config` prop accepts an object with the following structure:
|
|
|
301
319
|
}
|
|
302
320
|
```
|
|
303
321
|
|
|
322
|
+
### Newsletter Section
|
|
323
|
+
```ts
|
|
324
|
+
{
|
|
325
|
+
newsletter: {
|
|
326
|
+
title: string, // Newsletter Title (optional)
|
|
327
|
+
description: string, // Newsletter Description (optional)
|
|
328
|
+
placeholder: string, // Input placeholder (optional)
|
|
329
|
+
buttonText: string // Button text (optional)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Back To Top
|
|
335
|
+
```ts
|
|
336
|
+
{
|
|
337
|
+
backToTop: boolean // Show back to top button (default: false)
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
304
341
|
### Complete Configuration Example
|
|
305
342
|
```js
|
|
306
343
|
const footerConfig = {
|
|
@@ -357,7 +394,6 @@ const footerConfig = {
|
|
|
357
394
|
}
|
|
358
395
|
```
|
|
359
396
|
|
|
360
|
-
---
|
|
361
397
|
## Composable API
|
|
362
398
|
TvFooter includes a composable `useFooter` that you can use to process footer configuration and build custom footer implementations.
|
|
363
399
|
|
|
@@ -408,7 +444,6 @@ const { brand, copyright } = useFooter(config)
|
|
|
408
444
|
- Reactive computed properties
|
|
409
445
|
- Type-safe array validation
|
|
410
446
|
|
|
411
|
-
---
|
|
412
447
|
## Usage Examples
|
|
413
448
|
|
|
414
449
|
### Minimal Footer
|
|
@@ -626,7 +661,6 @@ const { brand, navigation, copyright } = useFooter(config)
|
|
|
626
661
|
</style>
|
|
627
662
|
```
|
|
628
663
|
|
|
629
|
-
---
|
|
630
664
|
## Styling
|
|
631
665
|
TvFooter comes with built-in responsive styles and light/dark mode support.
|
|
632
666
|
|
|
@@ -690,7 +724,6 @@ You can override the default styles by targeting the CSS classes:
|
|
|
690
724
|
- `.tv-footer__bottom` - Bottom section with copyright and legal
|
|
691
725
|
- `.tv-footer__legal` - Legal links container
|
|
692
726
|
|
|
693
|
-
---
|
|
694
727
|
## Accessibility
|
|
695
728
|
- **Semantic HTML**: Uses proper `<footer>`, `<nav>`, `<ul>`, and `<a>` elements
|
|
696
729
|
- **ARIA Labels**: Social links include accessible labels
|
|
@@ -700,7 +733,6 @@ You can override the default styles by targeting the CSS classes:
|
|
|
700
733
|
- **Screen Reader Friendly**: Proper heading hierarchy and semantic structure
|
|
701
734
|
- **Alternative Text**: Images include alt attributes
|
|
702
735
|
|
|
703
|
-
---
|
|
704
736
|
## SSR Notes
|
|
705
737
|
- **SSR-Safe**: No direct `window`/`document` access during module evaluation
|
|
706
738
|
- **Nuxt 3 Compatible**: Works seamlessly with Nuxt 3 out of the box
|
|
@@ -708,7 +740,6 @@ You can override the default styles by targeting the CSS classes:
|
|
|
708
740
|
- **Universal Rendering**: Works in both client and server contexts
|
|
709
741
|
- **Vue 3 Composition API**: Uses modern Vue 3 composables
|
|
710
742
|
|
|
711
|
-
---
|
|
712
743
|
## Development
|
|
713
744
|
```bash
|
|
714
745
|
git clone https://github.com/TODOvue/tv-footer.git
|
|
@@ -719,14 +750,11 @@ npm run build # build library
|
|
|
719
750
|
```
|
|
720
751
|
Local demo served from Vite using `index.html` and demo examples in `src/demo`.
|
|
721
752
|
|
|
722
|
-
---
|
|
723
753
|
## Contributing
|
|
724
754
|
PRs and issues welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
|
725
755
|
|
|
726
|
-
---
|
|
727
756
|
## License
|
|
728
757
|
MIT © TODOvue
|
|
729
758
|
|
|
730
|
-
---
|
|
731
759
|
### Attributions
|
|
732
760
|
Crafted for the TODOvue component ecosystem
|
package/dist/tv-footer.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue");function
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue");function E(t){const d=e.computed(()=>t?.brand||null),m=e.computed(()=>!t?.navigation||!Array.isArray(t.navigation)?[]:t.navigation),r=e.computed(()=>!t?.social||!Array.isArray(t.social)?[]:t.social),f=e.computed(()=>!t?.legal||!Array.isArray(t.legal)?[]:t.legal),c=e.computed(()=>t?.version||""),l=e.computed(()=>t?.copyright||""),a=e.computed(()=>t?.newsletter||null),i=new Date().getFullYear(),n=e.computed(()=>l.value.replace("{year}",i));return{brand:d,navigation:m,social:r,legal:f,version:c,copyright:n,newsletter:a}}const g={class:"tv-footer"},b={class:"tv-footer__container"},V={class:"tv-footer__left-column"},N={key:0,class:"tv-footer__brand"},S=["href"],w=["src","alt"],C={key:1},D={key:0,class:"tv-footer__version"},F={key:0,class:"tv-footer__newsletter"},T={key:0,class:"tv-footer__newsletter-title"},A={key:1,class:"tv-footer__newsletter-text"},x=["placeholder"],L={type:"submit",class:"tv-footer__newsletter-button"},M={class:"tv-footer__nav-definitions"},P={key:0,class:"tv-footer__section-title"},U={class:"tv-footer__links"},$=["href"],j={key:0,class:"tv-footer__social-section"},q={class:"tv-footer__social"},O=["href"],Y=["src","alt"],z={key:2},G={class:"tv-footer__bottom"},H={key:0},I={key:1,class:"tv-footer__legal"},J={class:"tv-footer__links",style:{"flex-direction":"row",gap:"1.5rem"}},K=["href"],Q={__name:"TvFooter",props:{config:{type:Object,default:()=>({})}},emits:["subscribe"],setup(t,{emit:d}){const m=t,{brand:r,navigation:f,social:c,legal:l,version:a,copyright:i,newsletter:n}=E(m.config),s=e.ref(""),h=d,y=()=>{s.value&&(h("subscribe",s.value),s.value="")};return(p,k)=>(e.openBlock(),e.createElementBlock("footer",g,[e.createElementVNode("div",b,[e.createElementVNode("div",V,[e.renderSlot(p.$slots,"brand",{brand:e.unref(r),version:e.unref(a)},()=>[e.unref(r)?(e.openBlock(),e.createElementBlock("div",N,[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,w)):e.createCommentVNode("",!0),e.unref(r).name?(e.openBlock(),e.createElementBlock("span",C,e.toDisplayString(e.unref(r).name),1)):e.createCommentVNode("",!0)],8,S),e.unref(a)?(e.openBlock(),e.createElementBlock("span",D,e.toDisplayString(e.unref(a)),1)):e.createCommentVNode("",!0)])):e.createCommentVNode("",!0)]),e.renderSlot(p.$slots,"newsletter",{newsletter:e.unref(n)},()=>[e.unref(n)?(e.openBlock(),e.createElementBlock("div",F,[e.unref(n).title?(e.openBlock(),e.createElementBlock("h3",T,e.toDisplayString(e.unref(n).title),1)):e.createCommentVNode("",!0),e.unref(n).description?(e.openBlock(),e.createElementBlock("p",A,e.toDisplayString(e.unref(n).description),1)):e.createCommentVNode("",!0),e.createElementVNode("form",{class:"tv-footer__newsletter-form",onSubmit:e.withModifiers(y,["prevent"])},[e.withDirectives(e.createElementVNode("input",{"onUpdate:modelValue":k[0]||(k[0]=o=>s.value=o),type:"email",placeholder:e.unref(n).placeholder||"Enter your email",class:"tv-footer__newsletter-input",required:""},null,8,x),[[e.vModelText,s.value]]),e.createElementVNode("button",L,e.toDisplayString(e.unref(n).buttonText||"Subscribe"),1)],32)])):e.createCommentVNode("",!0)])]),e.createElementVNode("div",M,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(e.unref(f),(o,u)=>(e.openBlock(),e.createElementBlock("div",{key:u,class:"tv-footer__section"},[o.title?(e.openBlock(),e.createElementBlock("h3",P,e.toDisplayString(o.title),1)):e.createCommentVNode("",!0),e.createElementVNode("ul",U,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(o.items,(v,B)=>(e.openBlock(),e.createElementBlock("li",{key:B},[e.createElementVNode("a",{href:v.url,class:"tv-footer__link"},e.toDisplayString(v.label),9,$)]))),128))])]))),128))]),e.unref(c)&&e.unref(c).length?(e.openBlock(),e.createElementBlock("div",j,[e.createElementVNode("div",q,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(e.unref(c),(o,u)=>(e.openBlock(),e.createElementBlock("a",{key:u,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,Y)):o.icon?(e.openBlock(),e.createElementBlock("i",{key:1,class:e.normalizeClass(o.icon)},null,2)):(e.openBlock(),e.createElementBlock("span",z,e.toDisplayString(o.label),1))],8,O))),128))])])):e.createCommentVNode("",!0)]),e.renderSlot(p.$slots,"bottom",{copyright:e.unref(i),legal:e.unref(l)},()=>[e.createElementVNode("div",G,[e.unref(i)?(e.openBlock(),e.createElementBlock("div",H,e.toDisplayString(e.unref(i)),1)):e.createCommentVNode("",!0),e.unref(l)&&e.unref(l).length?(e.openBlock(),e.createElementBlock("div",I,[e.createElementVNode("ul",J,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(e.unref(l),(o,u)=>(e.openBlock(),e.createElementBlock("li",{key:u},[e.createElementVNode("a",{href:o.url,class:"tv-footer__link"},e.toDisplayString(o.label),9,K)]))),128))])])):e.createCommentVNode("",!0)])])]))}},_=Q;_.install=t=>{t.component("TvFooter",_)};const R={install:_.install};exports.TvFooter=_;exports.TvFooterPlugin=R;exports.default=_;
|
package/dist/tv-footer.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@charset "UTF-8";.tv-footer{width:100%;padding:
|
|
1
|
+
@charset "UTF-8";.tv-footer{width:100%;padding:3rem 1.5rem;box-sizing:border-box;transition:background-color .4s ease,color .4s ease;position:relative;background-color:#0e131f;color:#cbd5e1;border-top:1px solid rgba(203,213,225,.05)}.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:#ffffff08;color:inherit;border:1px solid rgba(255,255,255,.05)}.tv-footer .tv-footer__social-link:hover,.dark-mode .tv-footer .tv-footer__social-link:hover{background-color:#ef233c;border-color:#ef233c;color:#fff;box-shadow:0 0 15px #ef233c66}.light-mode .tv-footer{background-color:#b9c4df;color:#1e293b;border-top-color:#1e293b0d}.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:#ffffff80;color:inherit;border:1px solid rgba(0,0,0,.05);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.light-mode .tv-footer .tv-footer__social-link:hover{background-color:#ef233c;border-color:#ef233c;color:#f1f9f9;box-shadow:0 4px 12px #ef233c4d}.light-mode .tv-footer .tv-footer__link:hover{color:#ef233c}.light-mode .tv-footer .tv-footer__newsletter-input{background-color:#fff9;border-color:#0000000d;color:#1e293b;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.light-mode .tv-footer .tv-footer__newsletter-input:focus{background-color:#ffffffe6;border-color:#ef233c;box-shadow:0 0 0 4px #ef233c1a}.tv-footer__container{display:flex;flex-wrap:wrap;gap:4rem;max-width:1200px;margin:0 auto}.tv-footer__left-column{flex:1 1 250px;max-width:400px;display:flex;flex-direction:column;gap:2rem}.tv-footer__brand{display:flex;flex-direction:column;gap:1rem}.tv-footer__logo{font-weight:800;font-size:1.5rem;text-decoration:none;display:inline-flex;align-items:center;gap:.75rem;letter-spacing:-.02em}.tv-footer__logo img{height:100px;width:auto}.tv-footer__version{font-size:.75rem;opacity:.6;font-weight:500;background:#7f7f7f1a;padding:.125rem .5rem;border-radius:12px;align-self:flex-start}.tv-footer__nav-definitions{display:flex;flex:999 1 400px;flex-wrap:wrap;gap:3rem}@media(max-width:768px){.tv-footer__nav-definitions{gap:2rem;flex-basis:100%}}.tv-footer__section{min-width:140px}.tv-footer__section-title{font-weight:700;margin-bottom:1.25rem;font-size:.9rem;letter-spacing:.05em;text-transform:uppercase;opacity:1;position:relative}.tv-footer__section-title:after{content:"";display:block;width:20px;height:2px;background-color:#ef233c;margin-top:.5rem;border-radius:2px}.tv-footer__links{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:.875rem}.tv-footer__link{color:inherit;text-decoration:none;font-size:.95rem;transition:all .2s ease;cursor:pointer;display:flex;align-items:center;gap:.5rem;opacity:.75;width:fit-content}.tv-footer__link:hover{color:#ef233c;opacity:1;transform:translate(4px)}.tv-footer__link:before{content:"";display:block;width:4px;height:4px;background-color:currentColor;border-radius:50%;opacity:0;transition:opacity .2s}.tv-footer__link:hover:before{opacity:1}.tv-footer__social-section{display:flex;flex-direction:column;justify-content:flex-start;padding-top:.5rem;flex:0 0 auto}.tv-footer__social{display:flex;gap:.75rem;flex-wrap:wrap}.tv-footer__social-link{font-size:1.1rem;text-decoration:none;display:flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;border-radius:50%;transition:all .3s cubic-bezier(.175,.885,.32,1.275)}.tv-footer__social-link:hover{transform:translateY(-4px) scale(1.1)}.tv-footer__social-link img{width:1.25rem;height:1.25rem;object-fit:contain;display:block}.tv-footer__bottom{border-top:1px solid rgba(127,127,127,.1);margin-top:3rem;padding-top:1.5rem;text-align:center;font-size:.85rem;display:flex;flex-direction:column;align-items:center;gap:1rem;max-width:1200px;margin-left:auto;margin-right:auto;opacity:.7}@media(min-width:640px){.tv-footer__bottom{flex-direction:row;justify-content:space-between}}.tv-footer__bottom .tv-footer__links{flex-direction:row;gap:1.5rem;flex-wrap:wrap;justify-content:center}.tv-footer__bottom .tv-footer__links li{display:inline-block}.tv-footer__bottom .tv-footer__links .tv-footer__link{font-size:.8rem}.tv-footer__bottom .tv-footer__links .tv-footer__link:hover{transform:none;text-decoration:underline}.tv-footer__bottom .tv-footer__links .tv-footer__link:before{display:none}.tv-footer__newsletter{display:flex;flex-direction:column;gap:1rem;margin-top:1rem;width:100%}.tv-footer__newsletter-title{font-weight:700;font-size:1rem;margin:0}.tv-footer__newsletter-text{font-size:.9rem;line-height:1.5;opacity:.8;margin:0}.tv-footer__newsletter-form{display:flex;gap:.5rem;flex-direction:column}@media(min-width:400px){.tv-footer__newsletter-form{flex-direction:row}}.tv-footer__newsletter-input{flex:1;min-width:0;padding:.75rem 1.25rem;border-radius:9999px;border:1px solid transparent;background-color:#ffffff0d;color:inherit;font-size:.9rem;outline:none;transition:all .3s ease}.tv-footer__newsletter-input:focus{background-color:#ffffff1a;border-color:#ef233c;box-shadow:0 0 0 4px #ef233c33}.tv-footer__newsletter-input::placeholder{opacity:.5}.tv-footer__newsletter-button{padding:.75rem 1.5rem;border-radius:9999px;border:none;background-color:#ef233c;color:#fff;font-weight:600;font-size:.9rem;cursor:pointer;transition:all .3s cubic-bezier(.175,.885,.32,1.275);display:inline-flex;align-items:center;justify-content:center;white-space:nowrap}.tv-footer__newsletter-button:hover{background-color:#e8112b;transform:translateY(-2px);box-shadow:0 4px 12px #ef233c66}.tv-footer__newsletter-button:active{transform:translateY(0)}.tv-footer__back-to-top{position:fixed;bottom:2.5rem;right:2.5rem;width:3.5rem;height:3.5rem;border-radius:50%;background-color:#ef233c;color:#fff;border:none;font-size:1.5rem;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 8px 16px #0003;transition:all .3s cubic-bezier(.175,.885,.32,1.275);z-index:1000}.tv-footer__back-to-top:hover{transform:translateY(-4px) scale(1.05);background-color:#e8112b;box-shadow:0 12px 20px #ef233c4d}.tv-footer__back-to-top:active{transform:translateY(-1px)}
|
package/dist/tv-footer.es.js
CHANGED
|
@@ -1,34 +1,47 @@
|
|
|
1
|
-
import { computed as
|
|
2
|
-
function
|
|
3
|
-
const
|
|
1
|
+
import { computed as _, ref as E, createElementBlock as e, openBlock as o, createElementVNode as l, renderSlot as F, createCommentVNode as n, unref as t, toDisplayString as c, withModifiers as U, withDirectives as V, vModelText as $, Fragment as p, renderList as m, normalizeClass as B } from "vue";
|
|
2
|
+
function M(s) {
|
|
3
|
+
const b = _(() => s?.brand || null), k = _(() => !s?.navigation || !Array.isArray(s.navigation) ? [] : s.navigation), i = _(() => !s?.social || !Array.isArray(s.social) ? [] : s.social), g = _(() => !s?.legal || !Array.isArray(s.legal) ? [] : s.legal), v = _(() => s?.version || ""), u = _(() => s?.copyright || ""), h = _(() => s?.newsletter || null), f = (/* @__PURE__ */ new Date()).getFullYear(), a = _(() => u.value.replace("{year}", f));
|
|
4
4
|
return {
|
|
5
|
-
brand:
|
|
6
|
-
navigation:
|
|
7
|
-
social:
|
|
8
|
-
legal:
|
|
9
|
-
version:
|
|
10
|
-
copyright:
|
|
5
|
+
brand: b,
|
|
6
|
+
navigation: k,
|
|
7
|
+
social: i,
|
|
8
|
+
legal: g,
|
|
9
|
+
version: v,
|
|
10
|
+
copyright: a,
|
|
11
|
+
newsletter: h
|
|
11
12
|
};
|
|
12
13
|
}
|
|
13
|
-
const
|
|
14
|
+
const N = { class: "tv-footer" }, Y = { class: "tv-footer__container" }, j = { class: "tv-footer__left-column" }, q = {
|
|
14
15
|
key: 0,
|
|
15
16
|
class: "tv-footer__brand"
|
|
16
|
-
},
|
|
17
|
+
}, z = ["href"], L = ["src", "alt"], O = { key: 1 }, P = {
|
|
17
18
|
key: 0,
|
|
18
19
|
class: "tv-footer__version"
|
|
19
|
-
},
|
|
20
|
+
}, G = {
|
|
20
21
|
key: 0,
|
|
21
|
-
class: "tv-
|
|
22
|
-
},
|
|
22
|
+
class: "tv-footer__newsletter"
|
|
23
|
+
}, H = {
|
|
24
|
+
key: 0,
|
|
25
|
+
class: "tv-footer__newsletter-title"
|
|
26
|
+
}, I = {
|
|
23
27
|
key: 1,
|
|
24
|
-
class: "tv-
|
|
25
|
-
},
|
|
28
|
+
class: "tv-footer__newsletter-text"
|
|
29
|
+
}, J = ["placeholder"], K = {
|
|
30
|
+
type: "submit",
|
|
31
|
+
class: "tv-footer__newsletter-button"
|
|
32
|
+
}, Q = { class: "tv-footer__nav-definitions" }, R = {
|
|
33
|
+
key: 0,
|
|
34
|
+
class: "tv-footer__section-title"
|
|
35
|
+
}, W = { class: "tv-footer__links" }, X = ["href"], Z = {
|
|
36
|
+
key: 0,
|
|
37
|
+
class: "tv-footer__social-section"
|
|
38
|
+
}, tt = { class: "tv-footer__social" }, et = ["href"], ot = ["src", "alt"], st = { key: 2 }, rt = { class: "tv-footer__bottom" }, lt = { key: 0 }, nt = {
|
|
26
39
|
key: 1,
|
|
27
40
|
class: "tv-footer__legal"
|
|
28
|
-
},
|
|
41
|
+
}, it = {
|
|
29
42
|
class: "tv-footer__links",
|
|
30
43
|
style: { "flex-direction": "row", gap: "1.5rem" }
|
|
31
|
-
},
|
|
44
|
+
}, at = ["href"], ct = {
|
|
32
45
|
__name: "TvFooter",
|
|
33
46
|
props: {
|
|
34
47
|
config: {
|
|
@@ -36,84 +49,122 @@ const C = { class: "tv-footer" }, w = { class: "tv-footer__container" }, B = {
|
|
|
36
49
|
default: () => ({})
|
|
37
50
|
}
|
|
38
51
|
},
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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 }, [
|
|
52
|
+
emits: ["subscribe"],
|
|
53
|
+
setup(s, { emit: b }) {
|
|
54
|
+
const k = s, { brand: i, navigation: g, social: v, legal: u, version: h, copyright: f, newsletter: a } = M(k.config), d = E(""), S = b, C = () => {
|
|
55
|
+
d.value && (S("subscribe", d.value), d.value = "");
|
|
56
|
+
};
|
|
57
|
+
return (w, T) => (o(), e("footer", N, [
|
|
58
|
+
l("div", Y, [
|
|
59
|
+
l("div", j, [
|
|
60
|
+
F(w.$slots, "brand", {
|
|
61
|
+
brand: t(i),
|
|
62
|
+
version: t(h)
|
|
63
|
+
}, () => [
|
|
64
|
+
t(i) ? (o(), e("div", q, [
|
|
64
65
|
l("a", {
|
|
65
|
-
href:
|
|
66
|
-
class: "tv-
|
|
67
|
-
},
|
|
68
|
-
|
|
66
|
+
href: t(i).url || "/",
|
|
67
|
+
class: "tv-footer__logo"
|
|
68
|
+
}, [
|
|
69
|
+
t(i).logo ? (o(), e("img", {
|
|
70
|
+
key: 0,
|
|
71
|
+
src: t(i).logo,
|
|
72
|
+
alt: t(i).name
|
|
73
|
+
}, null, 8, L)) : n("", !0),
|
|
74
|
+
t(i).name ? (o(), e("span", O, c(t(i).name), 1)) : n("", !0)
|
|
75
|
+
], 8, z),
|
|
76
|
+
t(h) ? (o(), e("span", P, c(t(h)), 1)) : n("", !0)
|
|
77
|
+
])) : n("", !0)
|
|
78
|
+
]),
|
|
79
|
+
F(w.$slots, "newsletter", { newsletter: t(a) }, () => [
|
|
80
|
+
t(a) ? (o(), e("div", G, [
|
|
81
|
+
t(a).title ? (o(), e("h3", H, c(t(a).title), 1)) : n("", !0),
|
|
82
|
+
t(a).description ? (o(), e("p", I, c(t(a).description), 1)) : n("", !0),
|
|
83
|
+
l("form", {
|
|
84
|
+
class: "tv-footer__newsletter-form",
|
|
85
|
+
onSubmit: U(C, ["prevent"])
|
|
86
|
+
}, [
|
|
87
|
+
V(l("input", {
|
|
88
|
+
"onUpdate:modelValue": T[0] || (T[0] = (r) => d.value = r),
|
|
89
|
+
type: "email",
|
|
90
|
+
placeholder: t(a).placeholder || "Enter your email",
|
|
91
|
+
class: "tv-footer__newsletter-input",
|
|
92
|
+
required: ""
|
|
93
|
+
}, null, 8, J), [
|
|
94
|
+
[$, d.value]
|
|
95
|
+
]),
|
|
96
|
+
l("button", K, c(t(a).buttonText || "Subscribe"), 1)
|
|
97
|
+
], 32)
|
|
98
|
+
])) : n("", !0)
|
|
69
99
|
])
|
|
70
|
-
])
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
100
|
+
]),
|
|
101
|
+
l("div", Q, [
|
|
102
|
+
(o(!0), e(p, null, m(t(g), (r, y) => (o(), e("div", {
|
|
103
|
+
key: y,
|
|
104
|
+
class: "tv-footer__section"
|
|
105
|
+
}, [
|
|
106
|
+
r.title ? (o(), e("h3", R, c(r.title), 1)) : n("", !0),
|
|
107
|
+
l("ul", W, [
|
|
108
|
+
(o(!0), e(p, null, m(r.items, (x, D) => (o(), e("li", { key: D }, [
|
|
109
|
+
l("a", {
|
|
110
|
+
href: x.url,
|
|
111
|
+
class: "tv-footer__link"
|
|
112
|
+
}, c(x.label), 9, X)
|
|
113
|
+
]))), 128))
|
|
114
|
+
])
|
|
115
|
+
]))), 128))
|
|
116
|
+
]),
|
|
117
|
+
t(v) && t(v).length ? (o(), e("div", Z, [
|
|
118
|
+
l("div", tt, [
|
|
119
|
+
(o(!0), e(p, null, m(t(v), (r, y) => (o(), e("a", {
|
|
120
|
+
key: y,
|
|
121
|
+
href: r.url,
|
|
76
122
|
class: "tv-footer__social-link",
|
|
77
123
|
target: "_blank",
|
|
78
124
|
rel: "noopener noreferer"
|
|
79
125
|
}, [
|
|
80
|
-
|
|
126
|
+
r.iconUrl ? (o(), e("img", {
|
|
81
127
|
key: 0,
|
|
82
|
-
src:
|
|
83
|
-
alt:
|
|
128
|
+
src: r.iconUrl,
|
|
129
|
+
alt: r.label,
|
|
84
130
|
class: "tv-footer__social-icon-img"
|
|
85
|
-
}, null, 8,
|
|
131
|
+
}, null, 8, ot)) : r.icon ? (o(), e("i", {
|
|
86
132
|
key: 1,
|
|
87
|
-
class:
|
|
88
|
-
}, null, 2)) : (
|
|
89
|
-
], 8,
|
|
133
|
+
class: B(r.icon)
|
|
134
|
+
}, null, 2)) : (o(), e("span", st, c(r.label), 1))
|
|
135
|
+
], 8, et))), 128))
|
|
90
136
|
])
|
|
91
|
-
])) :
|
|
137
|
+
])) : n("", !0)
|
|
92
138
|
]),
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
139
|
+
F(w.$slots, "bottom", {
|
|
140
|
+
copyright: t(f),
|
|
141
|
+
legal: t(u)
|
|
142
|
+
}, () => [
|
|
143
|
+
l("div", rt, [
|
|
144
|
+
t(f) ? (o(), e("div", lt, c(t(f)), 1)) : n("", !0),
|
|
145
|
+
t(u) && t(u).length ? (o(), e("div", nt, [
|
|
146
|
+
l("ul", it, [
|
|
147
|
+
(o(!0), e(p, null, m(t(u), (r, y) => (o(), e("li", { key: y }, [
|
|
148
|
+
l("a", {
|
|
149
|
+
href: r.url,
|
|
150
|
+
class: "tv-footer__link"
|
|
151
|
+
}, c(r.label), 9, at)
|
|
152
|
+
]))), 128))
|
|
153
|
+
])
|
|
154
|
+
])) : n("", !0)
|
|
155
|
+
])
|
|
105
156
|
])
|
|
106
157
|
]));
|
|
107
158
|
}
|
|
108
|
-
},
|
|
109
|
-
|
|
110
|
-
|
|
159
|
+
}, A = ct;
|
|
160
|
+
A.install = (s) => {
|
|
161
|
+
s.component("TvFooter", A);
|
|
111
162
|
};
|
|
112
|
-
const
|
|
113
|
-
install:
|
|
163
|
+
const ut = {
|
|
164
|
+
install: A.install
|
|
114
165
|
};
|
|
115
166
|
export {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
167
|
+
A as TvFooter,
|
|
168
|
+
ut as TvFooterPlugin,
|
|
169
|
+
A as default
|
|
119
170
|
};
|
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.
|
|
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
|
-
"
|
|
45
|
+
"CHANGELOG.md",
|
|
42
46
|
"LICENSE",
|
|
43
47
|
"README.md",
|
|
44
|
-
"
|
|
48
|
+
"dist",
|
|
49
|
+
"nuxt.js",
|
|
50
|
+
"src"
|
|
45
51
|
],
|
|
46
52
|
"engines": {
|
|
47
53
|
"node": ">=20.19.0"
|
|
@@ -53,17 +59,16 @@
|
|
|
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.
|
|
65
|
+
"vue": "^3.5.27"
|
|
61
66
|
},
|
|
62
67
|
"devDependencies": {
|
|
63
|
-
"@todovue/tv-demo": "^1.
|
|
68
|
+
"@todovue/tv-demo": "^1.4.11",
|
|
64
69
|
"@vitejs/plugin-vue": "^6.0.3",
|
|
65
|
-
"sass": "^1.97.
|
|
66
|
-
"vite": "^7.3.
|
|
70
|
+
"sass": "^1.97.3",
|
|
71
|
+
"vite": "^7.3.1",
|
|
67
72
|
"vite-plugin-dts": "^4.5.4"
|
|
68
73
|
}
|
|
69
74
|
}
|
|
@@ -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;
|