@todovue/tv-ui 0.1.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 +207 -0
- package/dist/.htaccess +8 -0
- package/dist/demos/tv-alert/CHANGELOG.md +127 -0
- package/dist/demos/tv-alert/README.md +334 -0
- package/dist/demos/tv-article/CHANGELOG.md +221 -0
- package/dist/demos/tv-article/README.md +258 -0
- package/dist/demos/tv-breadcrumbs/CHANGELOG.md +135 -0
- package/dist/demos/tv-breadcrumbs/README.md +364 -0
- package/dist/demos/tv-button/CHANGELOG.md +158 -0
- package/dist/demos/tv-button/README.md +255 -0
- package/dist/demos/tv-card/CHANGELOG.md +158 -0
- package/dist/demos/tv-card/README.md +332 -0
- package/dist/demos/tv-demo/CHANGELOG.md +352 -0
- package/dist/demos/tv-demo/README.md +229 -0
- package/dist/demos/tv-footer/CHANGELOG.md +67 -0
- package/dist/demos/tv-footer/README.md +760 -0
- package/dist/demos/tv-hero/CHANGELOG.md +137 -0
- package/dist/demos/tv-hero/README.md +410 -0
- package/dist/demos/tv-label/CHANGELOG.md +138 -0
- package/dist/demos/tv-label/README.md +357 -0
- package/dist/demos/tv-menu/CHANGELOG.md +145 -0
- package/dist/demos/tv-menu/README.md +389 -0
- package/dist/demos/tv-modal/CHANGELOG.md +127 -0
- package/dist/demos/tv-modal/README.md +466 -0
- package/dist/demos/tv-pagination/CHANGELOG.md +125 -0
- package/dist/demos/tv-pagination/README.md +275 -0
- package/dist/demos/tv-progress-bar/CHANGELOG.md +84 -0
- package/dist/demos/tv-progress-bar/README.md +894 -0
- package/dist/demos/tv-relative-time/CHANGELOG.md +122 -0
- package/dist/demos/tv-relative-time/README.md +405 -0
- package/dist/demos/tv-scroll-top/CHANGELOG.md +69 -0
- package/dist/demos/tv-scroll-top/README.md +445 -0
- package/dist/demos/tv-search/CHANGELOG.md +155 -0
- package/dist/demos/tv-search/README.md +407 -0
- package/dist/demos/tv-settings/CHANGELOG.md +94 -0
- package/dist/demos/tv-settings/README.md +314 -0
- package/dist/demos/tv-sidebar/CHANGELOG.md +229 -0
- package/dist/demos/tv-sidebar/README.md +592 -0
- package/dist/demos/tv-theme-button/CHANGELOG.md +136 -0
- package/dist/demos/tv-theme-button/README.md +392 -0
- package/dist/demos/tv-toc/CHANGELOG.md +80 -0
- package/dist/demos/tv-toc/README.md +288 -0
- package/dist/entry.d.ts +48 -0
- package/dist/favicon.ico +0 -0
- package/dist/tv-ui.cjs.js +1 -0
- package/dist/tv-ui.css +1 -0
- package/dist/tv-ui.d.ts +6 -0
- package/dist/tv-ui.es.js +92 -0
- package/nuxt.js +58 -0
- package/package.json +92 -0
|
@@ -0,0 +1,894 @@
|
|
|
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 Progress Bar (TvProgressBar)
|
|
5
|
+
A lightweight, customizable Vue 3 reading progress bar component that tracks scroll position through content. Features smooth animations, flexible target selection, configurable offsets, and SSR support. Works seamlessly in Single Page Apps and Server-Side Rendered (SSR) environments like Nuxt 3.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@todovue/tv-progress-bar)
|
|
8
|
+
[](https://www.npmjs.com/package/@todovue/tv-progress-bar)
|
|
9
|
+
[](https://www.npmjs.com/package/@todovue/tv-progress-bar)
|
|
10
|
+

|
|
11
|
+

|
|
12
|
+

|
|
13
|
+

|
|
14
|
+

|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
> Demo: https://ui.todovue.blog/progressbar
|
|
18
|
+
|
|
19
|
+
## Table of Contents
|
|
20
|
+
- [Features](#features)
|
|
21
|
+
- [Installation](#installation)
|
|
22
|
+
- [Quick Start (SPA)](#quick-start-spa)
|
|
23
|
+
- [Nuxt 4 / SSR Usage](#nuxt-4--ssr-usage)
|
|
24
|
+
- [Component Registration Options](#component-registration-options)
|
|
25
|
+
- [Props](#props)
|
|
26
|
+
- [Composable API](#composable-api)
|
|
27
|
+
- [Usage Examples](#usage-examples)
|
|
28
|
+
- [Accessibility](#accessibility)
|
|
29
|
+
- [SSR Notes](#ssr-notes)
|
|
30
|
+
- [Development](#development)
|
|
31
|
+
- [Contributing](#contributing)
|
|
32
|
+
- [License](#license)
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
- Real-time reading progress tracking based on scroll position
|
|
36
|
+
- Flexible target selection (CSS selector, element reference, or DOM element)
|
|
37
|
+
- Configurable height and color
|
|
38
|
+
- Support for Gradients: Pass multiple colors for a modern look
|
|
39
|
+
- Glow Effect: Optional neon glow that follows the progress bar
|
|
40
|
+
- Customizable Transitions: Configure duration and easing functions
|
|
41
|
+
- **Vertical Orientation**: Support for side progress bars (left/right)
|
|
42
|
+
- **Reading Checkpoints**: Display indicators at specific progress points (e.g., 25%, 50%, 75%)
|
|
43
|
+
- **Progress Labels**: Show percentage inside the bar or as a floating bubble
|
|
44
|
+
- **Flexible Positioning**: Fix the bar at the top, bottom, left, right, or use sticky behavior
|
|
45
|
+
- Top and bottom offset support for fixed headers/footers
|
|
46
|
+
- Smooth linear transitions with reduced motion support
|
|
47
|
+
- SSR-safe (works with Nuxt 3 and other SSR frameworks)
|
|
48
|
+
- Composable API (`useProgressBar`) for custom implementations
|
|
49
|
+
- ResizeObserver support for responsive content
|
|
50
|
+
- RequestAnimationFrame optimization for smooth performance
|
|
51
|
+
- Keyboard accessible with ARIA labels
|
|
52
|
+
- Lightweight and tree-shakeable
|
|
53
|
+
- TypeScript support
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
Using npm:
|
|
57
|
+
```bash
|
|
58
|
+
npm install @todovue/tv-progress-bar
|
|
59
|
+
```
|
|
60
|
+
Using yarn:
|
|
61
|
+
```bash
|
|
62
|
+
yarn add @todovue/tv-progress-bar
|
|
63
|
+
```
|
|
64
|
+
Using pnpm:
|
|
65
|
+
```bash
|
|
66
|
+
pnpm add @todovue/tv-progress-bar
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Importing Styles
|
|
70
|
+
**Important:** You must explicitly import the stylesheet in your application.
|
|
71
|
+
|
|
72
|
+
#### For Vue/Vite SPA:
|
|
73
|
+
```ts
|
|
74
|
+
// main.ts
|
|
75
|
+
import { createApp } from 'vue'
|
|
76
|
+
import App from './App.vue'
|
|
77
|
+
|
|
78
|
+
import '@todovue/tv-progress-bar/style.css'
|
|
79
|
+
import { TvProgressBar } from '@todovue/tv-progress-bar'
|
|
80
|
+
|
|
81
|
+
const app = createApp(App)
|
|
82
|
+
app.component('TvProgressBar', TvProgressBar)
|
|
83
|
+
app.mount('#app')
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### For Nuxt 3/4:
|
|
87
|
+
```ts
|
|
88
|
+
// nuxt.config.ts
|
|
89
|
+
export default defineNuxtConfig({
|
|
90
|
+
modules: [
|
|
91
|
+
'@todovue/tv-progress-bar/nuxt'
|
|
92
|
+
]
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Then register the component in a plugin as shown in the [Nuxt 3 / SSR Usage](#nuxt-3--ssr-usage) section.
|
|
97
|
+
|
|
98
|
+
## Quick Start (SPA)
|
|
99
|
+
Global registration (main.js / main.ts):
|
|
100
|
+
```js
|
|
101
|
+
import { createApp } from 'vue'
|
|
102
|
+
import App from './App.vue'
|
|
103
|
+
import '@todovue/tv-progress-bar/style.css'
|
|
104
|
+
import TvProgressBar from '@todovue/tv-progress-bar'
|
|
105
|
+
|
|
106
|
+
createApp(App)
|
|
107
|
+
.use(TvProgressBar) // enables <TvProgressBar /> globally
|
|
108
|
+
.mount('#app')
|
|
109
|
+
```
|
|
110
|
+
Local import inside a component:
|
|
111
|
+
```vue
|
|
112
|
+
<script setup>
|
|
113
|
+
import { ref } from 'vue'
|
|
114
|
+
import { TvProgressBar } from '@todovue/tv-progress-bar'
|
|
115
|
+
import '@todovue/tv-progress-bar/style.css'
|
|
116
|
+
|
|
117
|
+
const articleContainer = ref(null)
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
<template>
|
|
121
|
+
<div>
|
|
122
|
+
<!-- Progress bar tracks the article container -->
|
|
123
|
+
<TvProgressBar :target="articleContainer" />
|
|
124
|
+
|
|
125
|
+
<!-- Your article content -->
|
|
126
|
+
<article ref="articleContainer">
|
|
127
|
+
<h1>My Article</h1>
|
|
128
|
+
<p>Lorem ipsum dolor sit amet...</p>
|
|
129
|
+
<!-- Long content here -->
|
|
130
|
+
</article>
|
|
131
|
+
</div>
|
|
132
|
+
</template>
|
|
133
|
+
```
|
|
134
|
+
**Note:** Don't forget to import the CSS in your main entry file as shown above.
|
|
135
|
+
|
|
136
|
+
## Nuxt 4 / SSR Usage
|
|
137
|
+
First, add the module to your `nuxt.config.ts`:
|
|
138
|
+
```ts
|
|
139
|
+
// nuxt.config.ts
|
|
140
|
+
export default defineNuxtConfig({
|
|
141
|
+
modules: ['@todovue/tv-progress-bar/nuxt']
|
|
142
|
+
})
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Alternatively, you can manually add the CSS:
|
|
146
|
+
```ts
|
|
147
|
+
// nuxt.config.ts
|
|
148
|
+
export default defineNuxtConfig({
|
|
149
|
+
css: ['@todovue/tv-progress-bar/style.css'],
|
|
150
|
+
})
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Then create a plugin file: `plugins/tv-progress-bar.client.ts`:
|
|
154
|
+
```ts
|
|
155
|
+
import { defineNuxtPlugin } from '#app'
|
|
156
|
+
import TvProgressBar from '@todovue/tv-progress-bar'
|
|
157
|
+
|
|
158
|
+
export default defineNuxtPlugin(nuxtApp => {
|
|
159
|
+
nuxtApp.vueApp.use(TvProgressBar)
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
Use anywhere in your Nuxt app:
|
|
163
|
+
```vue
|
|
164
|
+
<script setup>
|
|
165
|
+
import { ref } from 'vue'
|
|
166
|
+
|
|
167
|
+
const mainContent = ref(null)
|
|
168
|
+
</script>
|
|
169
|
+
|
|
170
|
+
<template>
|
|
171
|
+
<div>
|
|
172
|
+
<TvProgressBar :target="mainContent" :offset-top="60" />
|
|
173
|
+
|
|
174
|
+
<main ref="mainContent">
|
|
175
|
+
<NuxtPage />
|
|
176
|
+
</main>
|
|
177
|
+
</div>
|
|
178
|
+
</template>
|
|
179
|
+
```
|
|
180
|
+
Optional direct import (no plugin):
|
|
181
|
+
```vue
|
|
182
|
+
<script setup>
|
|
183
|
+
import { TvProgressBar } from '@todovue/tv-progress-bar'
|
|
184
|
+
</script>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Component Registration Options
|
|
188
|
+
| Approach | When to use |
|
|
189
|
+
|------------------------------------------------------------------------------|------------------------------------------------|
|
|
190
|
+
| Global via `app.use(TvProgressBar)` | Many usages across app / design system install |
|
|
191
|
+
| Local named import `{ TvProgressBar }` | Isolated / code-split contexts |
|
|
192
|
+
| Direct default import `import TvProgressBar from '@todovue/tv-progress-bar'` | Single usage or manual registration |
|
|
193
|
+
|
|
194
|
+
## Props
|
|
195
|
+
| Prop | Type | Default | Description |
|
|
196
|
+
|---------------|------------------|--------------------------------------------------|-----------------------------------------------------------------|
|
|
197
|
+
| target | String \| Object | '.container-blog' | CSS selector or element reference to track scroll progress. |
|
|
198
|
+
| offsetTop | Number | 0 | Top offset in pixels (useful for fixed headers). |
|
|
199
|
+
| offsetBottom | Number | 0 | Bottom offset in pixels (useful for fixed footers). |
|
|
200
|
+
| height | String | '4px' | Height of horizontal progress bar (CSS value). |
|
|
201
|
+
| width | String | '4px' | Width of vertical progress bar (CSS value). |
|
|
202
|
+
| zIndex | Number | 1200 | Z-index for the progress bar positioning. |
|
|
203
|
+
| disabled | Boolean | false | Whether the progress bar is enabled and visible. |
|
|
204
|
+
| color | String | '' | Custom background color for the progress bar (CSS color value). |
|
|
205
|
+
| gradient | Array | [] | Array of colors for a linear gradient background. |
|
|
206
|
+
| glow | Boolean | false | Whether to enable the glow effect. |
|
|
207
|
+
| glowColor | String | '' | Custom color for the glow effect. |
|
|
208
|
+
| duration | String | '120ms' | Transition duration (e.g., '300ms', '0.5s'). |
|
|
209
|
+
| easing | String | 'linear' | Transition easing function (e.g., 'ease-in-out'). |
|
|
210
|
+
| orientation | String | 'horizontal' | Bar orientation: 'horizontal' or 'vertical'. |
|
|
211
|
+
| position | String | 'top' (horiz) / 'left' (vert) | Positioning: 'top', 'bottom', 'left', 'right', or 'sticky'. |
|
|
212
|
+
| showLabel | Boolean | false | Whether to show the percentage label. |
|
|
213
|
+
| labelPosition | String | 'inside' | Label position: 'inside' or 'floating'. |
|
|
214
|
+
| checkpoints | Array | [] | Array of numbers (0-100) to show indicators on the bar. |
|
|
215
|
+
|
|
216
|
+
### Prop Details
|
|
217
|
+
|
|
218
|
+
#### `target`
|
|
219
|
+
The element to track for reading progress. Can be:
|
|
220
|
+
- A CSS selector string (e.g., `'.article'`, `'#content'`)
|
|
221
|
+
- A template ref (e.g., `ref(null)`)
|
|
222
|
+
- An HTMLElement reference
|
|
223
|
+
|
|
224
|
+
Example:
|
|
225
|
+
```vue
|
|
226
|
+
<script setup>
|
|
227
|
+
import { ref } from 'vue'
|
|
228
|
+
|
|
229
|
+
const articleRef = ref(null)
|
|
230
|
+
</script>
|
|
231
|
+
|
|
232
|
+
<template>
|
|
233
|
+
<!-- Using template ref -->
|
|
234
|
+
<TvProgressBar :target="articleRef" />
|
|
235
|
+
<article ref="articleRef">...</article>
|
|
236
|
+
|
|
237
|
+
<!-- Using CSS selector -->
|
|
238
|
+
<TvProgressBar target="#my-article" />
|
|
239
|
+
<article id="my-article">...</article>
|
|
240
|
+
</template>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
#### `offsetTop`
|
|
244
|
+
Accounts for fixed headers or navigation bars at the top of the page. The progress calculation will consider this offset.
|
|
245
|
+
|
|
246
|
+
Example:
|
|
247
|
+
```vue
|
|
248
|
+
<!-- 60px offset for fixed header -->
|
|
249
|
+
<TvProgressBar :target="articleRef" :offset-top="60" />
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
#### `offsetBottom`
|
|
253
|
+
Accounts for fixed footers or bottom bars. The progress calculation will consider this offset.
|
|
254
|
+
|
|
255
|
+
Example:
|
|
256
|
+
```vue
|
|
257
|
+
<!-- 80px offset for fixed footer -->
|
|
258
|
+
<TvProgressBar :target="articleRef" :offset-bottom="80" />
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### `height`
|
|
262
|
+
Controls the thickness of the progress bar. Accepts any CSS height value.
|
|
263
|
+
|
|
264
|
+
Example:
|
|
265
|
+
```vue
|
|
266
|
+
<TvProgressBar :target="articleRef" height="8px" />
|
|
267
|
+
<TvProgressBar :target="articleRef" height="0.5rem" />
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
#### `zIndex`
|
|
271
|
+
Controls the stacking order of the progress bar. Default is 1200 to ensure it appears above most content.
|
|
272
|
+
|
|
273
|
+
Example:
|
|
274
|
+
```vue
|
|
275
|
+
<TvProgressBar :target="articleRef" :z-index="9999" />
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
#### `disabled`
|
|
279
|
+
Allows you to conditionally enable/disable the progress bar. When disabled, the bar won't be rendered.
|
|
280
|
+
|
|
281
|
+
Example:
|
|
282
|
+
```vue
|
|
283
|
+
<script setup>
|
|
284
|
+
import { ref } from 'vue'
|
|
285
|
+
|
|
286
|
+
const showProgress = ref(true)
|
|
287
|
+
</script>
|
|
288
|
+
|
|
289
|
+
<template>
|
|
290
|
+
<TvProgressBar :target="articleRef" :disabled="showProgress" />
|
|
291
|
+
</template>
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### `color`
|
|
295
|
+
Custom color for the progress bar. Accepts any CSS color value. If not provided, uses the default theme color.
|
|
296
|
+
|
|
297
|
+
Example:
|
|
298
|
+
```vue
|
|
299
|
+
<TvProgressBar :target="articleRef" color="#42b983" />
|
|
300
|
+
<TvProgressBar :target="articleRef" color="rgb(66, 185, 131)" />
|
|
301
|
+
<TvProgressBar :target="articleRef" color="var(--primary-color)" />
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
#### `gradient`
|
|
305
|
+
Array of colors to create a linear gradient background. When provided, it overrides the `color` prop.
|
|
306
|
+
|
|
307
|
+
Example:
|
|
308
|
+
```vue
|
|
309
|
+
<TvProgressBar :target="articleRef" :gradient="['#f093fb', '#f5576c']" />
|
|
310
|
+
<TvProgressBar :target="articleRef" :gradient="['#84fab0', '#8fd3f4']" />
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### `glow`
|
|
314
|
+
Enables a shadow effect that follows the progress bar, giving it a depth or "neon" look.
|
|
315
|
+
|
|
316
|
+
Example:
|
|
317
|
+
```vue
|
|
318
|
+
<TvProgressBar :target="articleRef" glow />
|
|
319
|
+
<TvProgressBar :target="articleRef" color="#00f2fe" glow />
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
#### `glowColor`
|
|
323
|
+
Customizes the color of the glow effect. If not provided, it defaults to the `color` prop or the last color in the `gradient`.
|
|
324
|
+
|
|
325
|
+
Example:
|
|
326
|
+
```vue
|
|
327
|
+
<TvProgressBar :target="articleRef" glow glow-color="#ff00ff" />
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
#### `duration`
|
|
331
|
+
Sets the duration of the progress bar transition.
|
|
332
|
+
|
|
333
|
+
Example:
|
|
334
|
+
```vue
|
|
335
|
+
<TvProgressBar :target="articleRef" duration="300ms" />
|
|
336
|
+
<TvProgressBar :target="articleRef" duration="0.5s" />
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
#### `easing`
|
|
340
|
+
Sets the easing function for the progress bar transition.
|
|
341
|
+
|
|
342
|
+
Example:
|
|
343
|
+
```vue
|
|
344
|
+
<TvProgressBar :target="articleRef" easing="ease-in-out" />
|
|
345
|
+
<TvProgressBar :target="articleRef" easing="cubic-bezier(0.4, 0, 0.2, 1)" />
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Composable API
|
|
349
|
+
TvProgressBar includes a composable `useProgressBar` that you can use to build custom progress tracking functionality.
|
|
350
|
+
|
|
351
|
+
### `useProgressBar(targetEl, options)`
|
|
352
|
+
```js
|
|
353
|
+
import { useProgressBar } from '@todovue/tv-progress-bar'
|
|
354
|
+
|
|
355
|
+
const targetEl = ref(null)
|
|
356
|
+
const { progress, progressPercent, recalculate } = useProgressBar(targetEl, {
|
|
357
|
+
offsetTop: computed(() => 60),
|
|
358
|
+
offsetBottom: computed(() => 0)
|
|
359
|
+
})
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Parameters:**
|
|
363
|
+
- `targetEl` (Ref<HTMLElement>): Reactive reference to the element to track
|
|
364
|
+
- `options` (Object): Configuration options
|
|
365
|
+
- `offsetTop` (Number|Ref|ComputedRef): Top offset in pixels (default: 0)
|
|
366
|
+
- `offsetBottom` (Number|Ref|ComputedRef): Bottom offset in pixels (default: 0)
|
|
367
|
+
|
|
368
|
+
**Returns:**
|
|
369
|
+
- `progress` (Ref<Number>): Reactive number between 0 and 1 representing scroll progress
|
|
370
|
+
- `progressPercent` (Ref<Number>): Reactive number between 0 and 100 (rounded)
|
|
371
|
+
- `recalculate` (Function): Function to manually trigger a progress recalculation
|
|
372
|
+
|
|
373
|
+
**Example:**
|
|
374
|
+
```vue
|
|
375
|
+
<script setup>
|
|
376
|
+
import { ref, computed } from 'vue'
|
|
377
|
+
import { useProgressBar } from '@todovue/tv-progress-bar'
|
|
378
|
+
|
|
379
|
+
const articleRef = ref(null)
|
|
380
|
+
const { progress, progressPercent, recalculate } = useProgressBar(articleRef, {
|
|
381
|
+
offsetTop: computed(() => 60),
|
|
382
|
+
offsetBottom: computed(() => 0)
|
|
383
|
+
})
|
|
384
|
+
</script>
|
|
385
|
+
|
|
386
|
+
<template>
|
|
387
|
+
<div>
|
|
388
|
+
<!-- Custom progress display -->
|
|
389
|
+
<div class="custom-progress">
|
|
390
|
+
Reading progress: {{ progressPercent }}%
|
|
391
|
+
</div>
|
|
392
|
+
|
|
393
|
+
<!-- Custom progress bar -->
|
|
394
|
+
<div class="custom-bar">
|
|
395
|
+
<div
|
|
396
|
+
class="custom-fill"
|
|
397
|
+
:style="{ width: `${progress * 100}%` }"
|
|
398
|
+
/>
|
|
399
|
+
</div>
|
|
400
|
+
|
|
401
|
+
<article ref="articleRef">
|
|
402
|
+
<!-- Your content -->
|
|
403
|
+
</article>
|
|
404
|
+
|
|
405
|
+
<button @click="recalculate">Recalculate Progress</button>
|
|
406
|
+
</div>
|
|
407
|
+
</template>
|
|
408
|
+
|
|
409
|
+
<style scoped>
|
|
410
|
+
.custom-progress {
|
|
411
|
+
position: fixed;
|
|
412
|
+
top: 10px;
|
|
413
|
+
right: 10px;
|
|
414
|
+
background: rgba(0, 0, 0, 0.7);
|
|
415
|
+
color: white;
|
|
416
|
+
padding: 8px 12px;
|
|
417
|
+
border-radius: 4px;
|
|
418
|
+
font-size: 14px;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.custom-bar {
|
|
422
|
+
position: fixed;
|
|
423
|
+
top: 0;
|
|
424
|
+
left: 0;
|
|
425
|
+
right: 0;
|
|
426
|
+
height: 4px;
|
|
427
|
+
background: rgba(0, 0, 0, 0.1);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.custom-fill {
|
|
431
|
+
height: 100%;
|
|
432
|
+
background: #42b983;
|
|
433
|
+
transition: width 120ms linear;
|
|
434
|
+
}
|
|
435
|
+
</style>
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## Usage Examples
|
|
439
|
+
|
|
440
|
+
### Default (CSS Selector)
|
|
441
|
+
```vue
|
|
442
|
+
<script setup>
|
|
443
|
+
import { TvProgressBar } from '@todovue/tv-progress-bar'
|
|
444
|
+
import '@todovue/tv-progress-bar/style.css'
|
|
445
|
+
</script>
|
|
446
|
+
|
|
447
|
+
<template>
|
|
448
|
+
<div>
|
|
449
|
+
<TvProgressBar target=".article-content" />
|
|
450
|
+
|
|
451
|
+
<article class="article-content">
|
|
452
|
+
<h1>My Article</h1>
|
|
453
|
+
<p>Lorem ipsum dolor sit amet...</p>
|
|
454
|
+
<!-- Your long content -->
|
|
455
|
+
</article>
|
|
456
|
+
</div>
|
|
457
|
+
</template>
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Using Template Ref
|
|
461
|
+
```vue
|
|
462
|
+
<script setup>
|
|
463
|
+
import { ref } from 'vue'
|
|
464
|
+
import { TvProgressBar } from '@todovue/tv-progress-bar'
|
|
465
|
+
import '@todovue/tv-progress-bar/style.css'
|
|
466
|
+
|
|
467
|
+
const articleContainer = ref(null)
|
|
468
|
+
</script>
|
|
469
|
+
|
|
470
|
+
<template>
|
|
471
|
+
<div>
|
|
472
|
+
<TvProgressBar :target="articleContainer" />
|
|
473
|
+
|
|
474
|
+
<article ref="articleContainer">
|
|
475
|
+
<h1>My Article</h1>
|
|
476
|
+
<p>Lorem ipsum dolor sit amet...</p>
|
|
477
|
+
<!-- Your long content -->
|
|
478
|
+
</article>
|
|
479
|
+
</div>
|
|
480
|
+
</template>
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Custom Color
|
|
484
|
+
```vue
|
|
485
|
+
<script setup>
|
|
486
|
+
import { ref } from 'vue'
|
|
487
|
+
import { TvProgressBar } from '@todovue/tv-progress-bar'
|
|
488
|
+
import '@todovue/tv-progress-bar/style.css'
|
|
489
|
+
|
|
490
|
+
const articleContainer = ref(null)
|
|
491
|
+
</script>
|
|
492
|
+
|
|
493
|
+
<template>
|
|
494
|
+
<div>
|
|
495
|
+
<TvProgressBar
|
|
496
|
+
:target="articleContainer"
|
|
497
|
+
color="#42b983"
|
|
498
|
+
/>
|
|
499
|
+
|
|
500
|
+
<article ref="articleContainer">
|
|
501
|
+
<h1>My Article</h1>
|
|
502
|
+
<p>Lorem ipsum dolor sit amet...</p>
|
|
503
|
+
</article>
|
|
504
|
+
</div>
|
|
505
|
+
</template>
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Custom Height
|
|
509
|
+
```vue
|
|
510
|
+
<script setup>
|
|
511
|
+
import { ref } from 'vue'
|
|
512
|
+
import { TvProgressBar } from '@todovue/tv-progress-bar'
|
|
513
|
+
import '@todovue/tv-progress-bar/style.css'
|
|
514
|
+
|
|
515
|
+
const articleContainer = ref(null)
|
|
516
|
+
</script>
|
|
517
|
+
|
|
518
|
+
<template>
|
|
519
|
+
<div>
|
|
520
|
+
<TvProgressBar
|
|
521
|
+
:target="articleContainer"
|
|
522
|
+
height="8px"
|
|
523
|
+
/>
|
|
524
|
+
|
|
525
|
+
<article ref="articleContainer">
|
|
526
|
+
<h1>My Article</h1>
|
|
527
|
+
<p>Lorem ipsum dolor sit amet...</p>
|
|
528
|
+
</article>
|
|
529
|
+
</div>
|
|
530
|
+
</template>
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### Gradient Support
|
|
534
|
+
```vue
|
|
535
|
+
<script setup>
|
|
536
|
+
import { ref } from 'vue'
|
|
537
|
+
import { TvProgressBar } from '@todovue/tv-progress-bar'
|
|
538
|
+
import '@todovue/tv-progress-bar/style.css'
|
|
539
|
+
|
|
540
|
+
const articleContainer = ref(null)
|
|
541
|
+
</script>
|
|
542
|
+
|
|
543
|
+
<template>
|
|
544
|
+
<div>
|
|
545
|
+
<TvProgressBar
|
|
546
|
+
:target="articleContainer"
|
|
547
|
+
:gradient="['#f093fb', '#f5576c']"
|
|
548
|
+
height="6px"
|
|
549
|
+
/>
|
|
550
|
+
|
|
551
|
+
<article ref="articleContainer">
|
|
552
|
+
<h1>My Article with Gradient</h1>
|
|
553
|
+
<p>Scroll to see the gradient progress bar...</p>
|
|
554
|
+
</article>
|
|
555
|
+
</div>
|
|
556
|
+
</template>
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Glow Effect
|
|
560
|
+
```vue
|
|
561
|
+
<script setup>
|
|
562
|
+
import { ref } from 'vue'
|
|
563
|
+
import { TvProgressBar } from '@todovue/tv-progress-bar'
|
|
564
|
+
import '@todovue/tv-progress-bar/style.css'
|
|
565
|
+
|
|
566
|
+
const articleContainer = ref(null)
|
|
567
|
+
</script>
|
|
568
|
+
|
|
569
|
+
<template>
|
|
570
|
+
<div>
|
|
571
|
+
<TvProgressBar
|
|
572
|
+
:target="articleContainer"
|
|
573
|
+
color="#00f2fe"
|
|
574
|
+
glow
|
|
575
|
+
height="4px"
|
|
576
|
+
/>
|
|
577
|
+
|
|
578
|
+
<article ref="articleContainer">
|
|
579
|
+
<h1>Neon Glow Progress</h1>
|
|
580
|
+
<p>Notice the subtle glow under the bar...</p>
|
|
581
|
+
</article>
|
|
582
|
+
</div>
|
|
583
|
+
</template>
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### Custom Transitions
|
|
587
|
+
```vue
|
|
588
|
+
<script setup>
|
|
589
|
+
import { ref } from 'vue'
|
|
590
|
+
import { TvProgressBar } from '@todovue/tv-progress-bar'
|
|
591
|
+
import '@todovue/tv-progress-bar/style.css'
|
|
592
|
+
|
|
593
|
+
const articleContainer = ref(null)
|
|
594
|
+
</script>
|
|
595
|
+
|
|
596
|
+
<template>
|
|
597
|
+
<div>
|
|
598
|
+
<TvProgressBar
|
|
599
|
+
:target="articleContainer"
|
|
600
|
+
duration="800ms"
|
|
601
|
+
easing="cubic-bezier(0.68, -0.55, 0.265, 1.55)"
|
|
602
|
+
color="#3f51b5"
|
|
603
|
+
/>
|
|
604
|
+
|
|
605
|
+
<article ref="articleContainer">
|
|
606
|
+
<h1>Bouncy Progress Bar</h1>
|
|
607
|
+
<p>Scroll fast to see the custom easing effect...</p>
|
|
608
|
+
</article>
|
|
609
|
+
</div>
|
|
610
|
+
</template>
|
|
611
|
+
|
|
612
|
+
### Vertical Orientation
|
|
613
|
+
```vue
|
|
614
|
+
<template>
|
|
615
|
+
<TvProgressBar
|
|
616
|
+
target=".content"
|
|
617
|
+
orientation="vertical"
|
|
618
|
+
position="left"
|
|
619
|
+
width="6px"
|
|
620
|
+
color="#4f46e5"
|
|
621
|
+
/>
|
|
622
|
+
</template>
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
### Reading Checkpoints
|
|
626
|
+
```vue
|
|
627
|
+
<template>
|
|
628
|
+
<TvProgressBar
|
|
629
|
+
target=".content"
|
|
630
|
+
:checkpoints="[25, 50, 75]"
|
|
631
|
+
color="#f59e0b"
|
|
632
|
+
/>
|
|
633
|
+
</template>
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
### Floating Percentage Label
|
|
637
|
+
```vue
|
|
638
|
+
<template>
|
|
639
|
+
<TvProgressBar
|
|
640
|
+
target=".content"
|
|
641
|
+
show-label
|
|
642
|
+
label-position="floating"
|
|
643
|
+
color="#10b981"
|
|
644
|
+
glow
|
|
645
|
+
/>
|
|
646
|
+
</template>
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
### Sticky Position (Inside Container)
|
|
650
|
+
```vue
|
|
651
|
+
<template>
|
|
652
|
+
<div class="relative-container">
|
|
653
|
+
<TvProgressBar
|
|
654
|
+
target=".content"
|
|
655
|
+
position="sticky"
|
|
656
|
+
color="#ec4899"
|
|
657
|
+
/>
|
|
658
|
+
<div class="content">...</div>
|
|
659
|
+
</div>
|
|
660
|
+
</template>
|
|
661
|
+
```
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
### With Fixed Header (Offset Top)
|
|
665
|
+
```vue
|
|
666
|
+
<script setup>
|
|
667
|
+
import { ref } from 'vue'
|
|
668
|
+
import { TvProgressBar } from '@todovue/tv-progress-bar'
|
|
669
|
+
import '@todovue/tv-progress-bar/style.css'
|
|
670
|
+
|
|
671
|
+
const articleContainer = ref(null)
|
|
672
|
+
</script>
|
|
673
|
+
|
|
674
|
+
<template>
|
|
675
|
+
<div>
|
|
676
|
+
<header style="position: fixed; top: 0; height: 60px;">
|
|
677
|
+
<!-- Fixed header content -->
|
|
678
|
+
</header>
|
|
679
|
+
|
|
680
|
+
<TvProgressBar
|
|
681
|
+
:target="articleContainer"
|
|
682
|
+
:offset-top="60"
|
|
683
|
+
/>
|
|
684
|
+
|
|
685
|
+
<article ref="articleContainer" style="margin-top: 60px;">
|
|
686
|
+
<h1>My Article</h1>
|
|
687
|
+
<p>Lorem ipsum dolor sit amet...</p>
|
|
688
|
+
</article>
|
|
689
|
+
</div>
|
|
690
|
+
</template>
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
### With Fixed Header and Footer
|
|
694
|
+
```vue
|
|
695
|
+
<script setup>
|
|
696
|
+
import { ref } from 'vue'
|
|
697
|
+
import { TvProgressBar } from '@todovue/tv-progress-bar'
|
|
698
|
+
import '@todovue/tv-progress-bar/style.css'
|
|
699
|
+
|
|
700
|
+
const articleContainer = ref(null)
|
|
701
|
+
</script>
|
|
702
|
+
|
|
703
|
+
<template>
|
|
704
|
+
<div>
|
|
705
|
+
<header style="position: fixed; top: 0; height: 60px;">
|
|
706
|
+
<!-- Fixed header -->
|
|
707
|
+
</header>
|
|
708
|
+
|
|
709
|
+
<TvProgressBar
|
|
710
|
+
:target="articleContainer"
|
|
711
|
+
:offset-top="60"
|
|
712
|
+
:offset-bottom="80"
|
|
713
|
+
/>
|
|
714
|
+
|
|
715
|
+
<article ref="articleContainer" style="margin: 60px 0 80px;">
|
|
716
|
+
<h1>My Article</h1>
|
|
717
|
+
<p>Lorem ipsum dolor sit amet...</p>
|
|
718
|
+
</article>
|
|
719
|
+
|
|
720
|
+
<footer style="position: fixed; bottom: 0; height: 80px;">
|
|
721
|
+
<!-- Fixed footer -->
|
|
722
|
+
</footer>
|
|
723
|
+
</div>
|
|
724
|
+
</template>
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
### Conditional Rendering
|
|
728
|
+
```vue
|
|
729
|
+
<script setup>
|
|
730
|
+
import { ref } from 'vue'
|
|
731
|
+
import { TvProgressBar } from '@todovue/tv-progress-bar'
|
|
732
|
+
import '@todovue/tv-progress-bar/style.css'
|
|
733
|
+
|
|
734
|
+
const articleContainer = ref(null)
|
|
735
|
+
const showProgress = ref(true)
|
|
736
|
+
</script>
|
|
737
|
+
|
|
738
|
+
<template>
|
|
739
|
+
<div>
|
|
740
|
+
<button @click="showProgress = !showProgress">
|
|
741
|
+
Toggle Progress Bar
|
|
742
|
+
</button>
|
|
743
|
+
|
|
744
|
+
<TvProgressBar
|
|
745
|
+
:target="articleContainer"
|
|
746
|
+
:disabled="showProgress"
|
|
747
|
+
/>
|
|
748
|
+
|
|
749
|
+
<article ref="articleContainer">
|
|
750
|
+
<h1>My Article</h1>
|
|
751
|
+
<p>Lorem ipsum dolor sit amet...</p>
|
|
752
|
+
</article>
|
|
753
|
+
</div>
|
|
754
|
+
</template>
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
### All Props Combined
|
|
758
|
+
```vue
|
|
759
|
+
<script setup>
|
|
760
|
+
import { ref } from 'vue'
|
|
761
|
+
import { TvProgressBar } from '@todovue/tv-progress-bar'
|
|
762
|
+
import '@todovue/tv-progress-bar/style.css'
|
|
763
|
+
|
|
764
|
+
const articleContainer = ref(null)
|
|
765
|
+
</script>
|
|
766
|
+
|
|
767
|
+
<template>
|
|
768
|
+
<div>
|
|
769
|
+
<TvProgressBar
|
|
770
|
+
:target="articleContainer"
|
|
771
|
+
:offset-top="60"
|
|
772
|
+
:offset-bottom="40"
|
|
773
|
+
height="6px"
|
|
774
|
+
color="#ff6b6b"
|
|
775
|
+
:z-index="9999"
|
|
776
|
+
disabled
|
|
777
|
+
/>
|
|
778
|
+
|
|
779
|
+
<article ref="articleContainer">
|
|
780
|
+
<h1>My Article</h1>
|
|
781
|
+
<p>Lorem ipsum dolor sit amet...</p>
|
|
782
|
+
</article>
|
|
783
|
+
</div>
|
|
784
|
+
</template>
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
### Custom Implementation with Composable
|
|
788
|
+
```vue
|
|
789
|
+
<script setup>
|
|
790
|
+
import { ref, computed } from 'vue'
|
|
791
|
+
import { useProgressBar } from '@todovue/tv-progress-bar'
|
|
792
|
+
|
|
793
|
+
const contentRef = ref(null)
|
|
794
|
+
const { progress, progressPercent, recalculate } = useProgressBar(contentRef, {
|
|
795
|
+
offsetTop: computed(() => 0),
|
|
796
|
+
offsetBottom: computed(() => 0)
|
|
797
|
+
})
|
|
798
|
+
</script>
|
|
799
|
+
|
|
800
|
+
<template>
|
|
801
|
+
<div>
|
|
802
|
+
<!-- Circular progress indicator -->
|
|
803
|
+
<div class="circular-progress">
|
|
804
|
+
<svg width="60" height="60">
|
|
805
|
+
<circle
|
|
806
|
+
cx="30"
|
|
807
|
+
cy="30"
|
|
808
|
+
r="25"
|
|
809
|
+
fill="none"
|
|
810
|
+
stroke="#e0e0e0"
|
|
811
|
+
stroke-width="5"
|
|
812
|
+
/>
|
|
813
|
+
<circle
|
|
814
|
+
cx="30"
|
|
815
|
+
cy="30"
|
|
816
|
+
r="25"
|
|
817
|
+
fill="none"
|
|
818
|
+
stroke="#42b983"
|
|
819
|
+
stroke-width="5"
|
|
820
|
+
:stroke-dasharray="`${progress * 157} 157`"
|
|
821
|
+
transform="rotate(-90 30 30)"
|
|
822
|
+
/>
|
|
823
|
+
</svg>
|
|
824
|
+
<span class="percentage">{{ progressPercent }}%</span>
|
|
825
|
+
</div>
|
|
826
|
+
|
|
827
|
+
<article ref="contentRef">
|
|
828
|
+
<h1>Custom Progress Indicator</h1>
|
|
829
|
+
<p>Scroll to see the circular progress...</p>
|
|
830
|
+
<!-- Your content -->
|
|
831
|
+
</article>
|
|
832
|
+
</div>
|
|
833
|
+
</template>
|
|
834
|
+
|
|
835
|
+
<style scoped>
|
|
836
|
+
.circular-progress {
|
|
837
|
+
position: fixed;
|
|
838
|
+
bottom: 20px;
|
|
839
|
+
right: 20px;
|
|
840
|
+
display: flex;
|
|
841
|
+
align-items: center;
|
|
842
|
+
justify-content: center;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
.percentage {
|
|
846
|
+
position: absolute;
|
|
847
|
+
font-size: 12px;
|
|
848
|
+
font-weight: bold;
|
|
849
|
+
color: #42b983;
|
|
850
|
+
}
|
|
851
|
+
</style>
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
## Accessibility
|
|
855
|
+
- **ARIA Attributes**: Progress bar includes proper ARIA attributes:
|
|
856
|
+
- `role="progressbar"`
|
|
857
|
+
- `aria-label="Reading progress"`
|
|
858
|
+
- `aria-valuemin="0"`
|
|
859
|
+
- `aria-valuemax="100"`
|
|
860
|
+
- `aria-valuenow` (dynamically updated percentage)
|
|
861
|
+
- **Pointer Events**: Progress bar uses `pointer-events: none` to not interfere with page interactions
|
|
862
|
+
- **Reduced Motion**: Respects `prefers-reduced-motion` media query to disable transitions for users who prefer reduced motion
|
|
863
|
+
- **Semantic HTML**: Uses semantic div elements with proper ARIA roles
|
|
864
|
+
- **Visual Feedback**: Clear visual indication of reading progress
|
|
865
|
+
|
|
866
|
+
## SSR Notes
|
|
867
|
+
- **SSR-Safe**: No direct `window`/`document` access during module evaluation
|
|
868
|
+
- **Smart Guards**: Uses `typeof window !== 'undefined'` checks throughout
|
|
869
|
+
- **Lifecycle Hooks**: Scroll listeners and observers are added in `onMounted` hook
|
|
870
|
+
- **Cleanup**: Automatically removes event listeners and observers in `onBeforeUnmount`
|
|
871
|
+
- **Nuxt 3 Compatible**: Works seamlessly with Nuxt 3 out of the box
|
|
872
|
+
- **Hydration Safe**: No hydration mismatches
|
|
873
|
+
- **Performance**: Uses `requestAnimationFrame` for smooth updates
|
|
874
|
+
- **ResizeObserver**: Automatically recalculates on content resize
|
|
875
|
+
- **Passive Listeners**: Scroll event listeners use `passive: true` for better performance
|
|
876
|
+
|
|
877
|
+
## Development
|
|
878
|
+
```bash
|
|
879
|
+
git clone https://github.com/TODOvue/tv-progress-bar.git
|
|
880
|
+
cd tv-progress-bar
|
|
881
|
+
npm install
|
|
882
|
+
npm run dev # run demo playground
|
|
883
|
+
npm run build # build library
|
|
884
|
+
```
|
|
885
|
+
Local demo served from Vite using `index.html` and demo examples in `src/demo`.
|
|
886
|
+
|
|
887
|
+
## Contributing
|
|
888
|
+
PRs and issues welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
|
889
|
+
|
|
890
|
+
## License
|
|
891
|
+
MIT © TODOvue
|
|
892
|
+
|
|
893
|
+
### Attributions
|
|
894
|
+
Crafted for the TODOvue component ecosystem
|