Compare commits

...

17 Commits

Author SHA1 Message Date
f1afc57f45 add ErrorPopup component and export
All checks were successful
CI / test-and-build (push) Successful in 17s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 14:19:09 +02:00
1e4751f8cf add README preview screenshot
All checks were successful
CI / test-and-build (push) Successful in 9m40s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 12:35:10 +02:00
805b553038 add demo site
All checks were successful
CI / test-and-build (push) Successful in 9m37s
2026-04-16 09:46:52 +02:00
89459caca1 add MIT License
All checks were successful
CI / test-and-build (push) Successful in 9m30s
2026-03-24 15:56:07 +01:00
c7fcdcf81b fix install
All checks were successful
CI / test-and-build (push) Successful in 9m32s
2026-03-24 12:17:16 +01:00
5e35ecd921 fix build to contain ony release
Some checks failed
CI / test-and-build (push) Has been cancelled
2026-03-24 12:08:50 +01:00
ee43f5241b add note on how to add css
Some checks failed
CI / test-and-build (push) Has been cancelled
2026-03-24 12:04:19 +01:00
2836ca3530 update token name
Some checks failed
CI / test-and-build (push) Has been cancelled
2026-03-24 11:56:25 +01:00
93b5543017 move built files to release branch
Some checks failed
CI / test-and-build (push) Has been cancelled
2026-03-24 11:52:30 +01:00
e8ffb3e3e5 maybe fix colors
Some checks failed
CI / test-and-build (push) Has been cancelled
2026-03-24 11:48:41 +01:00
d38b3eeab2 try updating the button color
All checks were successful
CI / test-and-build (push) Successful in 9m30s
2026-03-24 11:37:23 +01:00
c8caacf1e2 fix color on buttons
Some checks failed
CI / test-and-build (push) Has been cancelled
2026-03-24 11:32:51 +01:00
e47dcf5983 add linters and fix build
Some checks failed
CI / test-and-build (push) Has been cancelled
2026-03-24 11:27:56 +01:00
d1f3e0f514 fix css import pt 2
Some checks failed
CI / test-and-build (push) Failing after 4m46s
2026-03-24 11:22:56 +01:00
7ed1b207b4 fix css import 2026-03-24 11:21:19 +01:00
705a1c4910 fox image export
Some checks failed
CI / test-and-build (push) Has been cancelled
2026-03-24 11:18:08 +01:00
a06cc64589 add image to export adn fix gloabal for filemaker api
Some checks failed
CI / test-and-build (push) Has been cancelled
2026-03-24 11:10:36 +01:00
22 changed files with 687 additions and 122 deletions

View File

@@ -12,6 +12,9 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.DEPLOY_TOKEN }}
- uses: actions/setup-node@v4
with:
@@ -26,3 +29,30 @@ jobs:
- name: Build
run: npm run build
- name: Push dist to release branch
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
git config user.name "ci"
git config user.email "git@limelight.de"
# Save distributable files before switching branch
mkdir -p /tmp/release
cp package.json theme.css font.css /tmp/release/
cp -r fonts assets /tmp/release/
# Create a clean orphan branch (no source files)
git checkout --orphan release
git rm -rf .
# Restore distributable files (dist/ survived as it was gitignored)
cp /tmp/release/package.json .
npm pkg delete scripts.prepare
cp /tmp/release/theme.css .
cp /tmp/release/font.css .
cp -r /tmp/release/fonts .
cp -r /tmp/release/assets .
git add package.json theme.css font.css dist/ fonts/ assets/
git commit -m "release: $(git log origin/main -1 --pretty=%s)" || echo "nothing to commit"
git push origin release --force

10
.oxlintrc.json Normal file
View File

@@ -0,0 +1,10 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["eslint", "typescript", "unicorn", "oxc", "vue"],
"env": {
"browser": true
},
"categories": {
"correctness": "error"
}
}

6
.prettierrc.json Normal file
View File

@@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": true,
"singleQuote": true,
"printWidth": 100
}

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Limelight GmbH Veranstaltungstechnik
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,2 +1,14 @@
# CommonWebComponents
![Preview](assets/preview.png)
## CSS Import
create a css file with the following content:
```css
@import 'tailwindcss';
@source '@/../node_modules/limelight-common-web-components/src';
@import 'limelight-common-web-components/theme.css';
@import 'limelight-common-web-components/style.css';
```

BIN
assets/preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

26
eslint.config.ts Normal file
View File

@@ -0,0 +1,26 @@
import { globalIgnores } from 'eslint/config'
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
import pluginVue from 'eslint-plugin-vue'
import pluginOxlint from 'eslint-plugin-oxlint'
import skipFormatting from 'eslint-config-prettier/flat'
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
// import { configureVueProject } from '@vue/eslint-config-typescript'
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
export default defineConfigWithVueTs(
{
name: 'app/files-to-lint',
files: ['**/*.{vue,ts,mts,tsx}'],
},
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
...pluginVue.configs['flat/essential'],
vueTsConfigs.recommended,
...pluginOxlint.buildFromOxlintConfigFile('.oxlintrc.json'),
skipFormatting,
)

12
index.html Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CommonWebComponents — Demo</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -1,31 +0,0 @@
@import "tailwindcss";
@import "./font.css";
@theme {
--color-black: #000;
--color-white: #fff;
--color-primary: #ffff00;
--color-primary-dark: #504e18;
--color-primary-light: #ffff7b;
--color-secondary: #404040;
--color-secondary-light: #6b6b6b;
--color-secondary-dark: #2a2800;
--color-alive: oklch(69.6% 0.17 162.48); /* Tailwind emerald-500*/
--color-green: oklch(62.7% 0.194 149.214); /* Tailwind green-600 */
--color-yellow: oklch(68.1% 0.162 75.834); /* Tailwind yellow-600 */
--color-red: oklch(57.7% 0.245 27.325); /* Tailwind red-600 */
--color-on-primary: var(--color-secondary);
--color-on-secondary: var(--color-white);
--color-hover: var(--color-primary-dark);
--font-limelight: "Open Sans";
--default-font-family: var(--font-limelight);
--breakpoint-3xl: 112rem;
--breakpoint-4xl: 128rem;
}

236
package-lock.json generated
View File

@@ -16,6 +16,7 @@
"typescript": "^5.0.0",
"vite": "^6.0.0",
"vite-plugin-dts": "^4.5.4",
"vite-plugin-static-copy": "^3.4.0",
"vitest": "^3.2.4",
"vue": "^3.5.0"
},
@@ -2146,6 +2147,33 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"license": "ISC",
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/anymatch/node_modules/picomatch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -2183,6 +2211,19 @@
"require-from-string": "^2.0.2"
}
},
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
@@ -2193,6 +2234,19 @@
"balanced-match": "^1.0.0"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/cac": {
"version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
@@ -2230,6 +2284,31 @@
"node": ">= 16"
}
},
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2598,6 +2677,19 @@
}
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
@@ -2677,6 +2769,19 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -2788,6 +2893,19 @@
"dev": true,
"license": "ISC"
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
@@ -2804,6 +2922,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -2814,6 +2942,29 @@
"node": ">=8"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -3394,6 +3545,29 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/p-map": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz",
"integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@@ -3587,6 +3761,32 @@
],
"license": "MIT"
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/readdirp/node_modules/picomatch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -4065,6 +4265,19 @@
"dev": true,
"license": "MIT"
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/tough-cookie": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz",
@@ -4247,6 +4460,29 @@
}
}
},
"node_modules/vite-plugin-static-copy": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-3.4.0.tgz",
"integrity": "sha512-ekryzCw0ouAOE8tw4RvVL/dfqguXzumsV3FBKoKso4MQ1MUUrUXtl5RI4KpJQUNGqFEsg9kxl4EvDl02YtA9VQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^3.6.0",
"p-map": "^7.0.4",
"picocolors": "^1.1.1",
"tinyglobby": "^0.2.15"
},
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/sapphi-red"
},
"peerDependencies": {
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/vitest": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",

View File

@@ -1,12 +1,14 @@
{
"name": "limelight-common-web-components",
"version": "0.1.0",
"license": "MIT",
"type": "module",
"files": [
"dist",
"font.css",
"theme.css",
"fonts"
"fonts",
"assets"
],
"main": "./dist/index.cjs",
"module": "./dist/index.js",
@@ -18,7 +20,8 @@
"require": "./dist/index.cjs"
},
"./style.css": "./dist/index.css",
"./theme.css": "./theme.css"
"./theme.css": "./theme.css",
"./assets/*": "./dist/assets/*"
},
"peerDependencies": {
"vue": "^3.0.0"
@@ -32,10 +35,12 @@
"typescript": "^5.0.0",
"vite": "^6.0.0",
"vite-plugin-dts": "^4.5.4",
"vite-plugin-static-copy": "^3.4.0",
"vitest": "^3.2.4",
"vue": "^3.5.0"
},
"scripts": {
"serve": "vite",
"prepare": "vite build",
"build": "vite build",
"dev": "vite build --watch",

193
src/App.vue Normal file
View File

@@ -0,0 +1,193 @@
<template>
<div class="page">
<h1 class="title">CommonWebComponents</h1>
<p class="sub">Gaffer Tape Edge Demo LimelightButton</p>
<!-- Before / After -->
<section class="section">
<p class="section-label">Before / After</p>
<div class="compare">
<div class="compare-box">
<span class="compare-box-label">Before diagonal cut</span>
<button class="btn-old btn-old--primary">Primary</button>
<button class="btn-old btn-old--danger">Danger</button>
</div>
<div class="compare-box">
<span class="compare-box-label">After torn tape</span>
<LimelightButton variant="primary">Primary</LimelightButton>
<LimelightButton variant="danger">Danger</LimelightButton>
</div>
</div>
</section>
<!-- All variants -->
<section class="section">
<p class="section-label">All Variants</p>
<div class="row">
<LimelightButton variant="primary">Primary</LimelightButton>
<LimelightButton variant="outline">Outline</LimelightButton>
<LimelightButton variant="ghost">Ghost</LimelightButton>
<LimelightButton variant="danger">Danger</LimelightButton>
<LimelightButton variant="primary" :disabled="true">Disabled</LimelightButton>
</div>
</section>
<!-- Interactive -->
<section class="section">
<p class="section-label">Interactive States</p>
<div class="row">
<LimelightButton variant="primary" @click="count++">
Clicked {{ count }}×
</LimelightButton>
<LimelightButton variant="danger" @click="armed = !armed">
{{ armed ? 'ARMED' : 'Arm' }}
</LimelightButton>
<LimelightButton variant="ghost" @click="live = !live">
{{ live ? 'Go Offline' : 'Go Live' }}
</LimelightButton>
<LimelightButton variant="outline" :disabled="!live">
{{ live ? 'Streaming' : 'Offline' }}
</LimelightButton>
</div>
</section>
<!-- Production board -->
<section class="section">
<p class="section-label">Production Board</p>
<div class="board">
<div v-for="ch in channels" :key="ch.name" class="board-row">
<span class="board-row-label">{{ ch.id }}</span>
<span class="board-chip" :class="ch.hot ? 'board-chip--hot' : ''">{{ ch.name }}</span>
<LimelightButton variant="ghost" class="btn-sm">Arm</LimelightButton>
<LimelightButton :variant="ch.muted ? 'danger' : 'outline'" class="btn-sm" @click="ch.muted = !ch.muted">
{{ ch.muted ? 'Muted' : 'Mute' }}
</LimelightButton>
</div>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import LimelightButton from './components/LimelightButton.vue'
const count = ref(0)
const armed = ref(false)
const live = ref(false)
const channels = reactive([
{ id: 'CH 01', name: 'Kick In', hot: false, muted: false },
{ id: 'CH 02', name: 'Kick Out', hot: false, muted: false },
{ id: 'CH 12', name: 'Vox Lead', hot: true, muted: false },
{ id: 'CH 16', name: 'DI Bass', hot: false, muted: true },
])
</script>
<style>
@reference "tailwindcss";
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: #0f0f0f;
color: #d0d0d0;
font-family: 'Open Sans', 'Segoe UI', system-ui, sans-serif;
min-height: 100vh;
padding: 3rem 2rem;
}
.title {
color: var(--color-primary);
font-size: 1.5rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
margin-bottom: 0.25rem;
}
.sub {
color: #3a3a3a;
font-size: 0.7rem;
letter-spacing: 0.15em;
text-transform: uppercase;
margin-bottom: 3rem;
}
.section { margin-bottom: 3rem; }
.section-label {
color: #3a3a3a;
font-size: 0.62rem;
letter-spacing: 0.15em;
text-transform: uppercase;
margin-bottom: 1rem;
padding-bottom: 0.4rem;
border-bottom: 1px solid #1a1a1a;
}
.row { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; }
/* Before/after */
.compare { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; max-width: 560px; }
.compare-box {
background: #141414;
border: 1px solid #1e1e1e;
padding: 1.5rem;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.compare-box-label { color: #333; font-size: 0.6rem; letter-spacing: 0.1em; text-transform: uppercase; }
.btn-old {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 1.5rem;
height: 2.5rem;
font-size: 0.8rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
border: none;
cursor: pointer;
clip-path: polygon(8px 0%, 100% 0%, calc(100% - 8px) 100%, 0% 100%);
}
.btn-old--primary { background: var(--color-primary); color: var(--color-black); }
.btn-old--danger { background: var(--color-red); color: #fff; }
/* Board */
.board {
background: #111;
border: 1px solid #1a1a1a;
padding: 1.5rem;
max-width: 520px;
display: flex;
flex-direction: column;
gap: 0.85rem;
}
.board-row { display: flex; align-items: center; gap: 0.75rem; }
.board-row-label {
color: #2e2e2e;
font-size: 0.6rem;
letter-spacing: 0.1em;
text-transform: uppercase;
width: 60px;
flex-shrink: 0;
}
.board-chip {
display: inline-flex;
align-items: center;
padding: 0.2rem 0.75rem;
font-size: 0.62rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
background: #1a1a1a;
color: #444;
min-width: 90px;
}
.board-chip--hot { background: #2a2800; color: var(--color-primary); }
.btn-sm { font-size: 0.65rem !important; }
</style>

View File

@@ -0,0 +1,37 @@
import { describe, it, expect } from "vitest";
import { mount } from "@vue/test-utils";
import ErrorPopup from "./ErrorPopup.vue";
describe("ErrorPopup", () => {
it("renders nothing when show is false", () => {
const w = mount(ErrorPopup, { props: { show: false, message: "error" } });
expect(w.find("[class*=fixed]").exists()).toBe(false);
});
it("renders nothing when message is null even if show is true", () => {
const w = mount(ErrorPopup, { props: { show: true, message: null } });
expect(w.find("[class*=fixed]").exists()).toBe(false);
});
it("renders popup when show is true and message is set", () => {
const w = mount(ErrorPopup, { props: { show: true, message: "Verbindung fehlgeschlagen" } });
expect(w.text()).toContain("Verbindung fehlgeschlagen");
});
it("emits close when backdrop is clicked", async () => {
const w = mount(ErrorPopup, { props: { show: true, message: "err" } });
await w.find(".absolute.inset-0").trigger("click");
expect(w.emitted("close")).toHaveLength(1);
});
it("emits close when OK button is clicked", async () => {
const w = mount(ErrorPopup, { props: { show: true, message: "err" } });
await w.find("button").trigger("click");
expect(w.emitted("close")).toHaveLength(1);
});
it("displays Verbindungsfehler heading", () => {
const w = mount(ErrorPopup, { props: { show: true, message: "x" } });
expect(w.text()).toContain("Verbindungsfehler");
});
});

View File

@@ -0,0 +1,58 @@
<script setup lang="ts">
import LimelightButton from "./LimelightButton.vue";
const props = defineProps<{
message: string | null;
show: boolean;
title?: string;
}>();
const emit = defineEmits<{
(e: "close"): void;
}>();
</script>
<template>
<div
v-if="show && message"
class="fixed inset-0 z-50 flex items-center justify-center"
>
<!-- backdrop -->
<div class="absolute inset-0 bg-black/60" @click="$emit('close')" />
<!-- popup -->
<div
class="relative bg-secondary border border-red-500/40 rounded-2xl p-5 max-w-sm w-[90%] shadow-xl"
>
<div class="flex items-center gap-3 mb-3">
<div
class="w-10 h-10 rounded-full bg-red/15 flex items-center justify-center"
>
<svg
class="w-5 h-5 text-red"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 9v2m0 4h.01M10.29 3.86l-8.2 14A1.5 1.5 0 003.3 20h16.4a1.5 1.5 0 001.21-2.14l-8.2-14a1.5 1.5 0 00-2.42 0z"
/>
</svg>
</div>
<p class="text-white font-semibold">{{ title ?? "Verbindungsfehler" }}</p>
</div>
<p class="text-white/80 text-sm whitespace-pre-line">
{{ message }}
</p>
<LimelightButton variant="danger" class="mt-4 w-full" @click="$emit('close')">
OK
</LimelightButton>
</div>
</div>
</template>

View File

@@ -11,13 +11,13 @@
<script setup lang="ts">
defineProps<{
variant?: 'primary' | 'outline' | 'ghost' | 'danger'
disabled?: boolean
}>()
variant?: 'primary' | 'outline' | 'ghost' | 'danger';
disabled?: boolean;
}>();
</script>
<style scoped>
@import 'tailwindcss';
@reference "tailwindcss";
.btn {
@apply h-10 text-sm;
padding: 0 1.5rem;
@@ -37,7 +37,7 @@ defineProps<{
}
.btn--primary {
background: var(--color-primary);
color: var(--color-secondary-dark);
color: var(--color-black);
}
.btn--primary:hover {
background: var(--color-primary-light);
@@ -81,4 +81,8 @@ defineProps<{
opacity: 0.5;
cursor: not-allowed;
}
.btn--disabled:hover {
transform: scale(1) !important;
}
</style>

1
src/env.d.ts vendored
View File

@@ -1 +1,2 @@
/// <reference path="./utils/filemaker.d.ts" />
/// <reference types="vite/client" />

View File

@@ -1,3 +1,3 @@
export { default as LimelightButton } from './components/LimelightButton.vue'
export { default as ErrorPopup } from './components/ErrorPopup.vue'
export * from './utils/webviewer'
export type { FileMakerAPI } from './utils/filemaker'

5
src/main.ts Normal file
View File

@@ -0,0 +1,5 @@
import { createApp } from 'vue'
import App from './App.vue'
import './style.css'
createApp(App).mount('#app')

2
src/style.css Normal file
View File

@@ -0,0 +1,2 @@
@import "tailwindcss";
@import "../theme.css";

View File

@@ -1,82 +0,0 @@
/**
* TypeScript definitions for the FileMaker WebViewer JavaScript API
*
* These bindings cover the `FileMaker` global object injected into web viewers
* by Claris FileMaker Pro / WebDirect (FileMaker 19+).
*
* @see https://help.claris.com/en/pro-help/content/scripting-javascript-in-web-viewers.html
*/
// ---------------------------------------------------------------------------
// Core FileMaker global namespace
// ---------------------------------------------------------------------------
/**
* The `FileMaker` object is automatically injected into every web viewer's
* JavaScript context by the FileMaker runtime. It is **not** available in
* ordinary browsers.
*
* ### Important notes
* - All `PerformScript*` calls are **asynchronous** FileMaker does not block
* JavaScript execution while the script runs.
* - The object is only available after the web page has **finished loading**.
* - The web viewer must have *"Allow JavaScript to perform FileMaker scripts"*
* enabled in its object settings.
* - In WebDirect the page source must use the `data:text/html,` MIME prefix
* (not `data:text/html; charset=UTF-8,`) for these calls to work.
*/
export interface FileMakerAPI {
/**
* Calls a FileMaker script by name.
*
* Runs asynchronously JavaScript does not wait for the script to finish
* and no return value is provided back to JavaScript.
*
* @param script - Name of the FileMaker script to execute (not
* case-sensitive).
* @param parameter - Optional string parameter accessible inside the script
* via `Get(ScriptParameter)`.
*
* @example
* FileMaker.PerformScript("Save Record", JSON.stringify({ id: 42 }));
*/
PerformScript(script: string, parameter?: string): void
/**
* Calls a FileMaker script by name with an explicit concurrency option.
*
* Behaves identically to `PerformScript` when `option` is `"0"` (pause
* current script).
*
* @param script - Name of the FileMaker script to execute.
* @param parameter - Optional string parameter for the script.
* @param option - How to handle any currently running script.
* See {@link ScriptOption} for the full table.
*
* @example
* // Run concurrently without disturbing existing scripts
* FileMaker.PerformScriptWithOption("Sync Data", "", "3");
*/
PerformScriptWithOption(script: string, parameter?: string, option?: ScriptOption): void
}
// ---------------------------------------------------------------------------
// Global augmentation
// ---------------------------------------------------------------------------
declare global {
/**
* Global `FileMaker` object injected by the FileMaker runtime.
*
* May be `undefined` when the page is loaded outside of a FileMaker web
* viewer (e.g. in a regular browser during development).
*
* Always guard access with a runtime check:
* ```ts
* if (typeof FileMaker !== "undefined") {
* FileMaker.PerformScript("My Script");
* }
* ```
*/
const FileMaker: FileMakerAPI | undefined
}

View File

@@ -1,3 +1,21 @@
// ---------------------------------------------------------------------------
// FileMaker global API
// ---------------------------------------------------------------------------
/** The FileMaker object injected by the runtime into web viewers. */
export interface FileMakerAPI {
PerformScript(script: string, parameter?: string): void
PerformScriptWithOption(script: string, parameter?: string, option?: ScriptOption): void
}
declare global {
/** Available only inside a FileMaker web viewer. Always guard with `typeof FileMaker !== 'undefined'`. */
const FileMaker: FileMakerAPI | undefined
interface Window {
resolveFileMakerCallback: typeof resolveFileMakerCallback
}
}
// ---------------------------------------------------------------------------
// Script execution options
// ---------------------------------------------------------------------------

View File

@@ -2,13 +2,15 @@ import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import tailwindcss from '@tailwindcss/vite'
import dts from 'vite-plugin-dts'
import { viteStaticCopy } from 'vite-plugin-static-copy'
import { resolve } from 'path'
export default defineConfig({
plugins: [
tailwindcss(),
vue(),
dts({ tsconfigPath: './tsconfig.build.json', insertTypesEntry: true }),
dts({ tsconfigPath: './tsconfig.build.json', insertTypesEntry: true, copyDtsFiles: true }),
viteStaticCopy({ targets: [{ src: 'assets/*', dest: 'assets' }] }),
],
test: {
environment: 'jsdom',