├── .npmrc ├── src ├── routes │ ├── docs │ │ ├── [section] │ │ │ ├── About.svelte │ │ │ ├── +page.svelte │ │ │ ├── +page.ts │ │ │ ├── index.ts │ │ │ ├── Usage.svelte │ │ │ ├── Props.svelte │ │ │ ├── Events.svelte │ │ │ ├── Helpers.svelte │ │ │ └── Styling.svelte │ │ ├── +page.ts │ │ └── +layout.svelte │ ├── +layout.svelte │ ├── +page.svelte │ └── test │ │ └── +page.svelte ├── lib │ ├── components │ │ └── ui │ │ │ ├── progress │ │ │ ├── index.ts │ │ │ └── progress.svelte │ │ │ ├── table │ │ │ ├── table-body.svelte │ │ │ ├── table-caption.svelte │ │ │ ├── table-cell.svelte │ │ │ ├── table-footer.svelte │ │ │ ├── table.svelte │ │ │ ├── table-head.svelte │ │ │ ├── table-header.svelte │ │ │ ├── table-row.svelte │ │ │ └── index.ts │ │ │ ├── tabs │ │ │ ├── index.ts │ │ │ ├── tabs-list.svelte │ │ │ ├── tabs-content.svelte │ │ │ └── tabs-trigger.svelte │ │ │ ├── badge │ │ │ ├── badge.svelte │ │ │ └── index.ts │ │ │ ├── button │ │ │ ├── button.svelte │ │ │ └── index.ts │ │ │ └── PropsTable.svelte │ ├── package │ │ ├── index.ts │ │ ├── types.d.ts │ │ ├── marqueeck.css │ │ ├── helpers.ts │ │ ├── Marqueeck.svelte │ │ └── marqueeck.ts │ ├── style │ │ ├── container-grid.pcss │ │ └── highlightJS.pcss │ └── logic │ │ └── utils.ts ├── app.d.ts ├── app.html └── app.pcss ├── vite.config.ts ├── .gitignore ├── .eslintignore ├── .prettierignore ├── .vscode └── settings.json ├── .prettierrc ├── components.json ├── postcss.config.js ├── tsconfig.json ├── .eslintrc.cjs ├── static ├── favicon-dark.svg └── favicon-light.svg ├── svelte.config.js ├── README.md ├── tailwind.config.js ├── package.json └── pnpm-lock.yaml /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /src/routes/docs/[section]/About.svelte: -------------------------------------------------------------------------------- 1 |
... writing the documentation ...2 | -------------------------------------------------------------------------------- /src/lib/components/ui/progress/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./progress.svelte"; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Progress 7 | }; 8 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /dist 5 | /.svelte-kit 6 | /package 7 | .env 8 | .env.* 9 | !.env.example 10 | vite.config.js.timestamp-* 11 | vite.config.ts.timestamp-* 12 | -------------------------------------------------------------------------------- /src/routes/docs/[section]/+page.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
18 | Marqueeck is a performant full-featured marquee component for Svelte, style-free, highly 19 | customizable and dependency-less. 20 |
21 |18 | {description} 19 |
20 |Hover the marquees to slow them down
46 |Want to install and customize Marqueeck ?
50 | 51 |Use your preferred node package manager.
8 |npm i @arisbh/marqueeck
pnpm add @arisbh/marqueeck
yarn add @arisbh/marqueeck
16 | You can throw any element in Marqueeck, solo or grouped, a simple div or another Svelte component, 17 | or even just plain text for the sake of simplicity. 18 |
19 |20 | Your element will automaticaly be repeated inside the component, auto-updating the number of 21 | needed elements to fill its parent. 22 |
23 |Import the Marqueeck component, and wrap your content with it.
39 | You can use the reserved svelte:fragment to place a sticky element inside your component.
40 |
57 | You can use the reserved svelte:fragment to place a separator between your repeated components.
58 |
73 | Check the Props or Events pages for more information. 74 |
75 | -------------------------------------------------------------------------------- /src/routes/test/+page.svelte: -------------------------------------------------------------------------------- 1 | 35 | 36 |You can either pass your options directly in the Marqueeck component.
10 | 11 |23 | Or you can construct a MarqueeckOptions object, using the provided MarqueeckOptions type : 24 |
25 |If you don't pass any options to the Marqueeck element, it will use the following options.
43 |Editing documentation...47 | 48 | 49 |
51 | A marquee is by default an animated HTML tag. However, you might need the repeated effect without 52 | any translation of the position. 53 |
54 |Setting this prop will prevent the animating function to run.
55 |65 | By default, Marqueeck will bleed out to its sides. This is intended behaviour as you might need to 66 | apply a rotation to the component. This ensure it really fills your parent element on both sides. 67 |
68 |
69 | If you want to get rid of this behaviour, set the extend prop to false.
70 |
72 | You can set overflow-x: hidden; to the parent element to keep the prop active and still
73 | crop to the good width.
74 |
75 | Check the Styling page to know more about classes.
84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@arisbh/marqueeck", 3 | "version": "0.5.1", 4 | "description": "Marqueeck is a performant full-featured marquee component for Svelte, style-free, highly customizable and dependency-less.", 5 | "author": { 6 | "name": "AristideBH", 7 | "email": "aristide.bruneau@gmail.com", 8 | "url": "https://github.com/AristideBH" 9 | }, 10 | "homepage": "https://marqueeck.vercel.app", 11 | "license": "MIT", 12 | "exports": { 13 | ".": { 14 | "types": "./dist/package/index.d.ts", 15 | "svelte": "./dist/package/index.js" 16 | } 17 | }, 18 | "scripts": { 19 | "dev": "vite dev", 20 | "build": "vite build && npm run package", 21 | "preview": "vite preview", 22 | "package": "svelte-kit sync && svelte-package && publint", 23 | "prepublishOnly": "npm run package", 24 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 25 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 26 | "lint": "prettier --plugin-search-dir . --check . && eslint .", 27 | "format": "prettier --plugin-search-dir . --write ." 28 | }, 29 | "files": [ 30 | "dist/package", 31 | "!dist/package/**/*.test.*", 32 | "!dist/package/**/*.spec.*" 33 | ], 34 | "peerDependencies": { 35 | "svelte": "^5.0.0" 36 | }, 37 | "devDependencies": { 38 | "@sveltejs/adapter-auto": "^3.3.1", 39 | "@sveltejs/adapter-vercel": "^5.4.6", 40 | "@sveltejs/kit": "^2.7.3", 41 | "@sveltejs/package": "^2.3.7", 42 | "@sveltejs/vite-plugin-svelte": "^4.0.0", 43 | "@typescript-eslint/eslint-plugin": "^8.11.0", 44 | "@typescript-eslint/parser": "^8.11.0", 45 | "autoprefixer": "^10.4.20", 46 | "clsx": "^2.1.1", 47 | "eslint": "^9.13.0", 48 | "eslint-config-prettier": "^9.1.0", 49 | "eslint-plugin-svelte": "^2.46.0", 50 | "highlight.js": "^11.10.0", 51 | "lucide-svelte": "^0.453.0", 52 | "mode-watcher": "^0.4.1", 53 | "postcss": "^8.4.47", 54 | "postcss-load-config": "^6.0.1", 55 | "prettier": "^3.3.3", 56 | "prettier-plugin-tailwindcss": "^0.6.8", 57 | "publint": "^0.2.12", 58 | "svelte": "^5.1.3", 59 | "svelte-check": "^4.0.5", 60 | "svhighlight": "^0.7.1", 61 | "tailwind-merge": "^2.5.4", 62 | "tailwind-variants": "^0.2.1", 63 | "tailwindcss": "^3.4.14", 64 | "tslib": "^2.8.0", 65 | "typescript": "^5.6.3", 66 | "vite": "^5.4.10", 67 | "@macfja/svelte-persistent-store": "^2.4.2", 68 | "bits-ui": "^0.21.16" 69 | }, 70 | "svelte": "./dist/package/index.js", 71 | "types": "./dist/package/index.d.ts", 72 | "type": "module", 73 | "publishConfig": { 74 | "access": "public", 75 | "registry": "https://registry.npmjs.org/" 76 | }, 77 | "dependencies": {} 78 | } 79 | -------------------------------------------------------------------------------- /src/lib/components/ui/PropsTable.svelte: -------------------------------------------------------------------------------- 1 | 73 | 74 |
15 | Marqueeck provides a event when clicking on it. When you set this prop to a function
16 | , Marqueeck will automatically set role="button" and
17 | tabIndex="0" to the wrapper element for better accessibility.
18 |
20 | This event also triggerred when the component is focused, and23 | 24 |Enteror 21 |Spacekeys are pressed. 22 |
44 | Similarely to onClick, an event bubbles up when the mouse hovers in and out the
45 | component.
46 |
You can check the event.detail to see if it is true or false.
49 | This event also triggerred when the component is focused, either if bu the mouse or the keyboard. 50 |51 | 52 |
72 | Check the Styling page to set a global hoverClasses on the
73 | wrapper.
74 |
78 | For even more flexibility, you can retrieve the hovered state via this variable. It will return a 79 | boolean, making it easing to conditionnaly apply logic. 80 |
81 | 82 |Hovered
85 |Hovered
92 | `} 93 | /> 94 | -------------------------------------------------------------------------------- /src/app.pcss: -------------------------------------------------------------------------------- 1 | @import './lib/style/container-grid.pcss'; 2 | @import './lib/style/highlightJS.pcss'; 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | 7 | @layer base { 8 | :root { 9 | --background: 0 0% 98%; 10 | --foreground: 240 10% 3.9%; 11 | --card: 0 0% 100%; 12 | --card-foreground: 240 10% 3.9%; 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 240 10% 3.9%; 15 | --primary: 142.1 76.2% 36.3%; 16 | --primary-foreground: 355.7 100% 97.3%; 17 | --secondary: 240 4.8% 92.9%; 18 | --secondary-foreground: 240 5.9% 10%; 19 | --muted: 240 4.8% 91.9%; 20 | --muted-foreground: 240 3.8% 46.1%; 21 | --accent: 240 4.8% 95.9%; 22 | --accent-foreground: 240 5.9% 10%; 23 | --destructive: 0 84.2% 60.2%; 24 | --destructive-foreground: 0 0% 98%; 25 | --border: 240 5.9% 90%; 26 | --input: 240 5.9% 90%; 27 | --ring: 142.1 76.2% 36.3%; 28 | --radius: 0.5rem; 29 | 30 | --max-width: 960px; 31 | } 32 | 33 | .dark { 34 | --background: 0 0% 8.1%; 35 | --foreground: 0 0% 95%; 36 | --card: 24 0% 10%; 37 | --card-foreground: 0 0% 95%; 38 | --popover: 0 0% 9%; 39 | --popover-foreground: 0 0% 95%; 40 | --primary: 142.1 70.6% 45.3%; 41 | --primary-foreground: 144.9 80.4% 10%; 42 | --secondary: 240 3.7% 15.9%; 43 | --secondary-foreground: 0 0% 98%; 44 | --muted: 0 0% 15%; 45 | --muted-foreground: 240 5% 64.9%; 46 | --accent: 12 6.5% 15.1%; 47 | --accent-foreground: 0 0% 98%; 48 | --destructive: 0 62.8% 30.6%; 49 | --destructive-foreground: 0 85.7% 97.3%; 50 | --warn: 47 100% 40%; 51 | --border: 240 3.7% 15.9%; 52 | --input: 240 3.7% 15.9%; 53 | --ring: 142.4 71.8% 29.2%; 54 | 55 | --ring: hsl(212.7, 26.8%, 83.9); 56 | } 57 | } 58 | 59 | @layer base { 60 | * { 61 | @apply border-border; 62 | } 63 | body { 64 | @apply bg-background text-foreground; 65 | } 66 | 67 | /* TYPOGRAPHY */ 68 | h1 { 69 | @apply scroll-m-20 text-3xl font-extrabold tracking-tight lg:text-4xl; 70 | } 71 | 72 | h2 { 73 | @apply mb-3 mt-14 scroll-m-20 border-b pb-2 text-2xl font-semibold tracking-tight transition-colors first:mt-0; 74 | } 75 | 76 | h3 { 77 | @apply mt-8 scroll-m-20 text-xl font-semibold tracking-tight; 78 | } 79 | 80 | h4 { 81 | @apply scroll-m-20 text-lg font-semibold tracking-tight; 82 | } 83 | 84 | p { 85 | @apply leading-7; 86 | } 87 | 88 | a { 89 | @apply font-medium text-primary underline underline-offset-4; 90 | } 91 | 92 | ul { 93 | @apply my-6 ml-6 flex list-disc flex-col gap-2; 94 | } 95 | 96 | blockquote { 97 | @apply mt-6 border-l-2 border-primary pl-6 italic; 98 | } 99 | 100 | p code, 101 | blockquote code { 102 | @apply relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold; 103 | } 104 | 105 | .lead { 106 | @apply text-xl text-muted-foreground; 107 | } 108 | 109 | .small { 110 | @apply text-sm font-medium leading-none; 111 | } 112 | 113 | /* ? TEXT SELECT */ 114 | ::moz-selection { 115 | background: hsl(var(--primary)); 116 | color: hsl(var(--muted)); 117 | } 118 | 119 | ::selection { 120 | background: hsl(var(--primary)); 121 | color: hsl(var(--muted)); 122 | } 123 | 124 | .text-balance { 125 | text-wrap: balance; 126 | } 127 | } 128 | 129 | body { 130 | grid-template-rows: min-content auto min-content; 131 | } 132 | 133 | main:has(section) { 134 | @apply flex flex-col; 135 | } 136 | main:has(section) section:last-of-type { 137 | @apply flex-grow justify-center; 138 | } 139 | 140 | hr { 141 | @apply h-4 border-none; 142 | } 143 | 144 | .container { 145 | width: min(var(--max-width), calc(100% - 3rem)); 146 | margin-inline: auto; 147 | padding-inline: 0; 148 | } 149 | 150 | /* Marqueeck custom config */ 151 | :root { 152 | --marqueeck-bg-color: hsl(var(--primary)); 153 | --marqueeck-text-color: hsl(var(--background)); 154 | --marqueeck-padding-y: 0.65rem; 155 | } 156 | 157 | [data-marqueeck-wrapper] { 158 | margin-block: 0.35rem; 159 | } 160 | -------------------------------------------------------------------------------- /src/lib/package/helpers.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | import { tweened } from 'svelte/motion'; 3 | import { cubicOut, cubicInOut } from 'svelte/easing'; 4 | 5 | function createScrollState() { 6 | let lastPos: number | null = null; 7 | let timer: ReturnTypeI have cooked a few functionnality recipes for you to get the sense of Marqueeck versality ;)
49 |50 | Feel free to copy and/or improve them, and if you have more idea, please let me know in a 54 | GitHub issue. 56 |
57 |
58 | Most of these example makes use of the options.speedFactor proprepty to impact the component
59 | speed.
60 |
61 |
62 |
91 | When you construct a options object, you are easily able to update it.
92 |
Here we are creating three buttons to interact with the component.
94 |
137 | Here, we are using the scroll position to affect the speedFactor property in options.
138 |
140 | You could even get the direction of the scroll and update Marqueeck options.direction accordingly.
143 |
167 | CyclingValue: {JSON.stringify($cyclingValue, undefined, 2)}
168 |
Here, we are using a tweened value that cycles based on given parameters.
172 |9 | You have full control over the element you are slotting inside Marqueeck, therefore you can style 10 | it as you want. Drop a complex flex layout, another Svelte component, a simple icon or anything... 11 | You choose what you use, and style it the way you want ! 12 |
13 |36 | You can directly pass CSS variables for the background and text colors, and also for the vertical 37 | paddings, using any CSS appropriate propreties. 38 |
39 |Or define them globaly for all Marqueecks in your project :
55 |Marqueeck provides props to style its barebone structure and the default class tag.
86 |
89 | Default class on the Marqueeck component is used to style the wrapper of the
90 | element.
91 |
ribbonClasses is used to style the parent of your repeated element.
95 |96 | Its gap property is automaticaly inherited from options and is use to properly calculate the 97 | needed number of elements. Please do not redefined it here and use the gap property in 98 | options. 99 |
100 |101 | This is the animated element, please do not apply any transform proprety to it. 102 |103 |
childClasses is used to style your repeated element.
106 |You have full control here and no styling restriction.
107 |stickyClasses is used to style the sticky svelte:fragment.
Its inline paddings are set automaticaly from options but you're free to edit them.
111 |separatorClasses is used to style the separator svelte:fragment.
hoverClasses is used to set arbitrary classes to the wrapper when it is hovered.
117 |122 | Although Marqueeck has a minimal styling approach, it still needs a bit of CSS to operate 123 | proprely. 124 |
125 |