feat(icons): setup icons package (#536)

This commit is contained in:
Sam Potts
2026-02-16 14:11:40 +11:00
committed by GitHub
parent edefc2a2d6
commit 78de97ec23
35 changed files with 504 additions and 14 deletions
@@ -109,6 +109,7 @@ import '@videojs/html/ui/mute-button';
```
Import registration for:
- `@videojs/html/video/player` — always needed (registers `<video-player>`)
- `@videojs/html/ui/{component}` — registers the component's custom element
@@ -117,7 +118,8 @@ Import registration for:
### .tsx (component)
```tsx
import { createPlayer, features, MuteButton, Video } from '@videojs/react';
import { createPlayer, features, MuteButton } from '@videojs/react';
import { Video } from '@videojs/react/video';
import './BasicUsage.css';
@@ -147,6 +149,7 @@ export default function BasicUsage() {
```
Key patterns:
- `createPlayer({ features: [...features.video] })` creates the player
- Video attributes: `autoPlay muted playsInline loop` (React camelCase)
- `render` prop for state-based rendering: `render={(props, state) => ...}`
@@ -24,7 +24,8 @@ function App() {
}
// ✅ Complete — copy, paste, run
import { createPlayer, features, PlayButton, Video } from '@videojs/react';
import { createPlayer, features, PlayButton } from '@videojs/react';
import { Video } from '@videojs/react/video';
const Player = createPlayer({ features: [...features.video] });
@@ -77,7 +78,8 @@ Site pages use `<FrameworkCase>` and `<StyleCase>` to show code per framework. N
**React:**
```tsx
import { createPlayer, features, PlayButton, Video } from '@videojs/react';
import { createPlayer, features, PlayButton } from '@videojs/react';
import { Video } from '@videojs/react/video';
const Player = createPlayer({ features: [...features.video] });
@@ -222,7 +224,8 @@ Show which file code belongs to when multiple files are involved:
````markdown
```tsx title="App.tsx"
import { createPlayer, features, PlayButton, Video } from '@videojs/react';
import { createPlayer, features, PlayButton } from '@videojs/react';
import { Video } from '@videojs/react/video';
import './App.css';
const Player = createPlayer({ features: [...features.video] });
+43 -3
View File
@@ -1,15 +1,55 @@
{
"name": "@videojs/icons",
"type": "module",
"private": true,
"version": "0.1.0-alpha.1",
"description": "SVG icon library for Video.js",
"license": "Apache-2.0",
"files": [],
"sideEffects": false,
"exports": {
"./react": {
"types": "./dist/react/default/index.d.ts",
"default": "./dist/react/default/index.js"
},
"./react/*": {
"types": "./dist/react/*/index.d.ts",
"default": "./dist/react/*/index.js"
},
"./html": {
"types": "./dist/html/default/index.d.ts",
"default": "./dist/html/default/index.js"
},
"./html/*": {
"types": "./dist/html/*/index.d.ts",
"default": "./dist/html/*/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "node --import tsx scripts/build.ts",
"clean": "rm -rf dist"
},
"dependencies": {
"svgo": "^3.3.2"
},
"devDependencies": {
"tsdown": "^0.20.3",
"@svgr/core": "^8.1.0",
"@svgr/plugin-jsx": "^8.1.0",
"@svgr/plugin-svgo": "^8.1.0",
"@types/react": "^19.0.0",
"@videojs/utils": "workspace:*",
"react": "^19.0.0",
"tsx": "^4.19.0",
"typescript": "^5.9.3"
},
"publishConfig": {
"access": "public"
}
},
"keywords": [
"videojs",
"icons",
"svg"
]
}
+136
View File
@@ -0,0 +1,136 @@
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { transform } from '@svgr/core';
import { camelCase, pascalCase } from '@videojs/utils/string';
import { transform as esbuildTransform } from 'esbuild';
import { optimize } from 'svgo';
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..');
const ASSETS_DIR = join(ROOT, 'src/assets');
const DIST_DIR = join(ROOT, 'dist');
const FRAMEWORKS = ['react', 'html'] as const;
type Framework = (typeof FRAMEWORKS)[number];
const SVGO_CONFIG = {
multipass: true,
plugins: [],
};
function ensureDir(path: string): void {
if (!existsSync(path)) mkdirSync(path, { recursive: true });
}
function cleanDist(): void {
if (existsSync(DIST_DIR)) rmSync(DIST_DIR, { recursive: true, force: true });
}
function getIconSets(): string[] {
if (!existsSync(ASSETS_DIR)) {
console.error(`Assets directory not found: ${ASSETS_DIR}`);
process.exit(1);
}
return readdirSync(ASSETS_DIR).filter((item) => !item.startsWith('.') && item !== 'index');
}
function getSvgFiles(setName: string): string[] {
return readdirSync(join(ASSETS_DIR, setName)).filter((f) => f.endsWith('.svg'));
}
function optimizeSvg(svgContent: string): string {
return optimize(svgContent, SVGO_CONFIG).data;
}
async function buildReactComponent(svgContent: string, componentName: string): Promise<{ js: string; tsx: string }> {
const transformOpts = {
plugins: ['@svgr/plugin-svgo', '@svgr/plugin-jsx'],
svgoConfig: SVGO_CONFIG,
};
const tsxCode = await transform(svgContent, { ...transformOpts, typescript: true }, { componentName });
const jsxCode = await transform(svgContent, transformOpts, { componentName });
// SVGR outputs JSX syntax which is invalid in .js files — compile to JS
const { code } = await esbuildTransform(jsxCode, { loader: 'jsx', jsx: 'automatic' });
return { js: code, tsx: tsxCode };
}
function buildHtmlExport(svgContent: string, varName: string): string {
return `export const ${varName} = \`${optimizeSvg(svgContent)}\`;\n`;
}
function buildIndexExports(icons: { name: string; varName: string }[], framework: Framework): string {
return icons
.map(({ name, varName }) =>
framework === 'react'
? `export { default as ${pascalCase(varName)}Icon } from './${name}.js';`
: `export { ${camelCase(varName)}Icon } from './${name}.js';`
)
.join('\n');
}
function buildIndexTypes(icons: { name: string; varName: string }[], framework: Framework): string {
const types = icons.map(({ varName }) =>
framework === 'react'
? `export declare const ${pascalCase(varName)}Icon: React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement> & React.RefAttributes<SVGSVGElement>>;`
: `export declare const ${camelCase(varName)}Icon: string;`
);
return `/// <reference types="react" />\n${types.join('\n')}\n`;
}
async function buildIconSet(setName: string): Promise<void> {
const svgFiles = getSvgFiles(setName);
console.log(` Building set: ${setName} (${svgFiles.length} icons)`);
const icons = svgFiles.map((file) => ({
name: file.replace('.svg', ''),
varName: file.replace('.svg', ''),
content: readFileSync(join(ASSETS_DIR, setName, file), 'utf8'),
}));
for (const framework of FRAMEWORKS) {
const outDir = join(DIST_DIR, framework, setName);
ensureDir(outDir);
for (const icon of icons) {
const { name, varName, content } = icon;
if (framework === 'react') {
const componentName = `${pascalCase(varName)}Icon`;
const { js, tsx } = await buildReactComponent(content, componentName);
writeFileSync(join(outDir, `${name}.js`), js);
writeFileSync(join(outDir, `${name}.tsx`), tsx);
writeFileSync(
join(outDir, `${name}.d.ts`),
`import * as React from 'react';\ndeclare const ${componentName}: React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement> & React.RefAttributes<SVGSVGElement>>;\nexport default ${componentName};\n`
);
} else {
const varNameCamel = camelCase(varName);
writeFileSync(join(outDir, `${name}.js`), buildHtmlExport(content, `${varNameCamel}Icon`));
writeFileSync(join(outDir, `${name}.d.ts`), `export declare const ${varNameCamel}Icon: string;\n`);
}
}
writeFileSync(join(outDir, 'index.js'), buildIndexExports(icons, framework));
writeFileSync(join(outDir, 'index.d.ts'), buildIndexTypes(icons, framework));
}
}
async function main(): Promise<void> {
console.log('Building icons...\n');
cleanDist();
const sets = getIconSets();
console.log(`Found ${sets.length} icon sets: ${sets.join(', ')}\n`);
for (const set of sets) {
await buildIconSet(set);
}
console.log('\nBuild complete!');
}
main().catch(console.error);
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
<path
d="M9.57,3.617c-.156-.375-.519-.617-.924-.617H4c-.552,0-1,.449-1,1v4.646c0,.406,.242,.769,.618,.924,.124,.051,.255,.076,.383,.076,.261,0,.515-.102,.706-.293l4.647-4.647c.286-.287,.371-.715,.216-1.089Z"
class="arrow-1"
/>
<path
d="M14.382,8.429c-.377-.156-.804-.068-1.089,.217l-4.647,4.647c-.286,.287-.371,.715-.216,1.089,.156,.375,.519,.617,.924,.617h4.646c.552,0,1-.449,1-1v-4.646c0-.406-.242-.769-.618-.924Z"
class="arrow-2"
/>
</svg>

After

Width:  |  Height:  |  Size: 571 B

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" viewBox="0 0 18 18" fill="currentColor">
<path
d="M7.883,1.93c-.375-.157-.803-.07-1.09,.217L2.146,6.793c-.287,.287-.372,.715-.217,1.09s.518,.617,.924,.617H7.5c.551,0,1-.449,1-1V2.854c0-.406-.242-.769-.617-.924Z"
class="arrow-1"
/>
<path
d="M15.146,9.5h-4.646c-.551,0-1,.449-1,1v4.646c0,.406,.242,.769,.617,.924,.125,.052,.255,.077,.384,.077,.26,0,.514-.102,.706-.293l4.646-4.646c.287-.287,.372-.715,.217-1.09s-.518-.617-.924-.617Z"
class="arrow-2"
/>
</svg>

After

Width:  |  Height:  |  Size: 545 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
<rect x="2" y="2" width="5" height="14" rx="1.75" ry="1.75" />
<rect x="11" y="2" width="5" height="14" rx="1.75" ry="1.75" />
</svg>

After

Width:  |  Height:  |  Size: 242 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
<path
d="M15.1,7.478L5.608,2.222c-.553-.306-1.206-.297-1.749,.023-.538,.317-.859,.877-.859,1.499V14.256c0,.622,.321,1.182,.859,1.499,.279,.164,.586,.247,.895,.247,.293,0,.586-.075,.854-.223l9.491-5.256c.556-.307,.901-.891,.901-1.522s-.345-1.215-.9-1.522Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 378 B

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
<rect width="2" height="5" x="8" y=".5" rx="1" opacity="0.5">
<animate attributeName="opacity" values="1;0" dur="1s" begin="0s" repeatCount="indefinite" calcMode="linear" />
</rect>
<rect width="2" height="5" x="12.243" y="2.257" rx="1" transform="rotate(45 13.243 4.757)" opacity="0.45">
<animate attributeName="opacity" values="1;0" dur="1s" begin="0.125s" repeatCount="indefinite" calcMode="linear" />
</rect>
<rect width="5" height="2" x="12.5" y="8" rx="1" opacity="0.4">
<animate attributeName="opacity" values="1;0" dur="1s" begin="0.25s" repeatCount="indefinite" calcMode="linear" />
</rect>
<rect width="5" height="2" x="10.743" y="12.243" rx="1" transform="rotate(45 13.243 13.243)" opacity="0.35">
<animate attributeName="opacity" values="1;0" dur="1s" begin="0.375s" repeatCount="indefinite" calcMode="linear" />
</rect>
<rect width="2" height="5" x="8" y="12.5" rx="1" opacity="0.3">
<animate attributeName="opacity" values="1;0" dur="1s" begin="0.5s" repeatCount="indefinite" calcMode="linear" />
</rect>
<rect width="2" height="5" x="3.757" y="10.743" rx="1" transform="rotate(45 4.757 13.243)" opacity="0.25">
<animate attributeName="opacity" values="1;0" dur="1s" begin="0.625s" repeatCount="indefinite" calcMode="linear" />
</rect>
<rect width="5" height="2" x=".5" y="8" rx="1" opacity="0.15">
<animate attributeName="opacity" values="1;0" dur="1s" begin="0.75s" repeatCount="indefinite" calcMode="linear" />
</rect>
<rect
width="5"
height="2"
x="2.257"
y="3.757"
fill-rule="nonzero"
rx="1"
transform="rotate(45 4.757 4.757)"
opacity="0.1"
>
<animate attributeName="opacity" values="1;0" dur="1s" begin="0.875s" repeatCount="indefinite" calcMode="linear" />
</rect>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
<path
d="M15.5999996,3.3 C15.1999996,2.9 14.5999996,2.9 14.1999996,3.3 C13.7999996,3.7 13.7999996,4.3 14.1999996,4.7 C15.3999996,5.9 15.9999996,7.4 15.9999996,9 C15.9999996,10.6 15.3999996,12.1 14.1999996,13.3 C13.7999996,13.7 13.7999996,14.3 14.1999996,14.7 C14.3999996,14.9 14.6999996,15 14.8999996,15 C15.1999996,15 15.3999996,14.9 15.5999996,14.7 C17.0999996,13.2 17.9999996,11.2 17.9999996,9 C17.9999996,6.8 17.0999996,4.8 15.5999996,3.3 L15.5999996,3.3 Z"
/>
<path
d="M11.2819745,5.28197449 C10.9060085,5.65794047 10.9060085,6.22188944 11.2819745,6.59785542 C12.0171538,7.33303477 12.2772954,8.05605449 12.2772954,9 C12.2772954,9.93588462 11.851678,10.9172014 11.2819745,11.4869049 C10.9060085,11.8628709 10.9060085,12.4268199 11.2819745,12.8027859 C11.4271642,12.9479755 11.9176724,13.0649528 12.2998149,12.9592565 C12.4124479,12.9281035 12.5156669,12.8776063 12.5978555,12.8027859 C13.773371,11.732654 14.1311161,10.1597914 14.1312524,9 C14.1312524,8.8299555 14.1286311,8.66015647 14.119665,8.4897429 C14.0674781,7.49784946 13.8010171,6.48513613 12.5978554,5.28197449 C12.2218894,4.9060085 11.6579405,4.9060085 11.2819745,5.28197449 Z M3.78571429,6.00820648 L0.714285714,6.00820648 C0.285714286,6.00820648 0,6.30901277 0,6.76022222 L0,11.2723167 C0,11.7235261 0.285714286,12.0243324 0.714285714,12.0243324 L3.78571429,12.0243324 L7.85714286,15.8819922 C8.35714286,16.1827985 9,15.8819922 9,15.2803796 L9,2.75215925 C9,2.15054666 8.35714286,1.77453879 7.85714286,2.15054666 L3.78571429,6.00820648 Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
<path
d="M11.2819745 5.28197449C10.9060085 5.65794047 10.9060085 6.22188944 11.2819745 6.59785542 12.0171538 7.33303477 12.2772954 8.05605449 12.2772954 9 12.2772954 9.93588462 11.851678 10.9172014 11.2819745 11.4869049 10.9060085 11.8628709 10.9060085 12.4268199 11.2819745 12.8027859 11.4271642 12.9479755 11.9176724 13.0649528 12.2998149 12.9592565 12.4124479 12.9281035 12.5156669 12.8776063 12.5978555 12.8027859 13.773371 11.732654 14.1311161 10.1597914 14.1312524 9 14.1312524 8.8299555 14.1286311 8.66015647 14.119665 8.4897429 14.0674781 7.49784946 13.8010171 6.48513613 12.5978554 5.28197449 12.2218894 4.9060085 11.6579405 4.9060085 11.2819745 5.28197449ZM3.78571429 6.00820648.714285714 6.00820648C.285714286 6.00820648 0 6.30901277 0 6.76022222L0 11.2723167C0 11.7235261.285714286 12.0243324.714285714 12.0243324L3.78571429 12.0243324 7.85714286 15.8819922C8.35714286 16.1827985 9 15.8819922 9 15.2803796L9 2.75215925C9 2.15054666 8.35714286 1.77453879 7.85714286 2.15054666L3.78571429 6.00820648Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
<path
d="M12.732233,5.81801948 L14.5,7.586 L16.267767,5.81801948 C16.6582912,5.42749519 17.2914562,5.42749519 17.6819805,5.81801948 C18.0725048,6.20854378 18.0725048,6.84170876 17.6819805,7.23223305 L15.914,9 L17.6819805,10.767767 C18.0725048,11.1582912 18.0725048,11.7914562 17.6819805,12.1819805 C17.2914562,12.5725048 16.6582912,12.5725048 16.267767,12.1819805 L14.5,10.414 L12.732233,12.1819805 C12.3417088,12.5725048 11.7085438,12.5725048 11.3180195,12.1819805 C10.9274952,11.7914562 10.9274952,11.1582912 11.3180195,10.767767 L13.085,9 L11.3180195,7.23223305 C10.9274952,6.84170876 10.9274952,6.20854378 11.3180195,5.81801948 C11.7085438,5.42749519 12.3417088,5.42749519 12.732233,5.81801948 Z M3.78571429,6.00820648 L0.714285714,6.00820648 C0.285714286,6.00820648 0,6.30901277 0,6.76022222 L0,11.2723167 C0,11.7235261 0.285714286,12.0243324 0.714285714,12.0243324 L3.78571429,12.0243324 L7.85714286,15.8819922 C8.35714286,16.1827985 9,15.8819922 9,15.2803796 L9,2.75215925 C9,2.15054666 8.35714286,1.77453879 7.85714286,2.15054666 L3.78571429,6.00820648 Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
<path
d="M15.25 2h.017q.03 0 .06.004za.75.75 0 0 1 .599.298l-.069-.078a.75.75 0 0 1 .22.53v4.5a.75.75 0 1 1-1.5 0V4.56l-3.22 3.22a.75.75 0 0 1-1.06-1.06l3.218-3.22H10.75a.75.75 0 0 1-.743-.648L10 2.75a.75.75 0 0 1 .75-.75z"
class="arrow-1"
/>
<path
d="M2.75 10a.75.75 0 0 1 .75.75v2.688l3.22-3.218a.75.75 0 0 1 1.06 1.06L4.56 14.5h2.69a.75.75 0 0 1 .743.648L8 15.25a.75.75 0 0 1-.75.75H2.722l-.046-.004.074.004a.75.75 0 0 1-.599-.298l.069.078a.75.75 0 0 1-.22-.53v-4.5a.75.75 0 0 1 .75-.75"
class="arrow-2"
/>
</svg>

After

Width:  |  Height:  |  Size: 645 B

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
<path
d="M10.75 2a.75.75 0 0 1 .75.75v2.688l3.22-3.218a.75.75 0 0 1 1.06 1.06L12.56 6.5h2.69a.75.75 0 0 1 .743.648L16 7.25a.75.75 0 0 1-.75.75h-4.528l-.046-.004.074.004a.75.75 0 0 1-.599-.298l.069.078a.75.75 0 0 1-.22-.53v-4.5a.75.75 0 0 1 .75-.75"
class="arrow-1"
/>
<path
d="M7.25 10h.017q.03 0 .06.004za.75.75 0 0 1 .599.298l-.069-.078a.75.75 0 0 1 .22.53v4.5a.75.75 0 1 1-1.5 0v-2.69l-3.22 3.22a.75.75 0 0 1-1.06-1.06l3.218-3.22H2.75a.75.75 0 0 1-.743-.648L2 10.75a.75.75 0 0 1 .75-.75z"
class="arrow-2"
/>
</svg>

After

Width:  |  Height:  |  Size: 646 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
<rect x="2" y="2" width="5" height="14" rx="1.75" ry="1.75" />
<rect x="11" y="2" width="5" height="14" rx="1.75" ry="1.75" />
</svg>

After

Width:  |  Height:  |  Size: 242 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
<path
d="M15.1,7.478L5.608,2.222c-.553-.306-1.206-.297-1.749,.023-.538,.317-.859,.877-.859,1.499V14.256c0,.622,.321,1.182,.859,1.499,.279,.164,.586,.247,.895,.247,.293,0,.586-.075,.854-.223l9.491-5.256c.556-.307,.901-.891,.901-1.522s-.345-1.215-.9-1.522Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 378 B

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
<rect width="2" height="5" x="8" y=".5" rx="1" opacity="0.5">
<animate attributeName="opacity" values="1;0" dur="1s" begin="0s" repeatCount="indefinite" calcMode="linear" />
</rect>
<rect width="2" height="5" x="12.243" y="2.257" rx="1" transform="rotate(45 13.243 4.757)" opacity="0.45">
<animate attributeName="opacity" values="1;0" dur="1s" begin="0.125s" repeatCount="indefinite" calcMode="linear" />
</rect>
<rect width="5" height="2" x="12.5" y="8" rx="1" opacity="0.4">
<animate attributeName="opacity" values="1;0" dur="1s" begin="0.25s" repeatCount="indefinite" calcMode="linear" />
</rect>
<rect width="5" height="2" x="10.743" y="12.243" rx="1" transform="rotate(45 13.243 13.243)" opacity="0.35">
<animate attributeName="opacity" values="1;0" dur="1s" begin="0.375s" repeatCount="indefinite" calcMode="linear" />
</rect>
<rect width="2" height="5" x="8" y="12.5" rx="1" opacity="0.3">
<animate attributeName="opacity" values="1;0" dur="1s" begin="0.5s" repeatCount="indefinite" calcMode="linear" />
</rect>
<rect width="2" height="5" x="3.757" y="10.743" rx="1" transform="rotate(45 4.757 13.243)" opacity="0.25">
<animate attributeName="opacity" values="1;0" dur="1s" begin="0.625s" repeatCount="indefinite" calcMode="linear" />
</rect>
<rect width="5" height="2" x=".5" y="8" rx="1" opacity="0.15">
<animate attributeName="opacity" values="1;0" dur="1s" begin="0.75s" repeatCount="indefinite" calcMode="linear" />
</rect>
<rect
width="5"
height="2"
x="2.257"
y="3.757"
fill-rule="nonzero"
rx="1"
transform="rotate(45 4.757 4.757)"
opacity="0.1"
>
<animate attributeName="opacity" values="1;0" dur="1s" begin="0.875s" repeatCount="indefinite" calcMode="linear" />
</rect>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
<path
d="M15.5999996,3.3 C15.1999996,2.9 14.5999996,2.9 14.1999996,3.3 C13.7999996,3.7 13.7999996,4.3 14.1999996,4.7 C15.3999996,5.9 15.9999996,7.4 15.9999996,9 C15.9999996,10.6 15.3999996,12.1 14.1999996,13.3 C13.7999996,13.7 13.7999996,14.3 14.1999996,14.7 C14.3999996,14.9 14.6999996,15 14.8999996,15 C15.1999996,15 15.3999996,14.9 15.5999996,14.7 C17.0999996,13.2 17.9999996,11.2 17.9999996,9 C17.9999996,6.8 17.0999996,4.8 15.5999996,3.3 L15.5999996,3.3 Z"
/>
<path
d="M11.2819745,5.28197449 C10.9060085,5.65794047 10.9060085,6.22188944 11.2819745,6.59785542 C12.0171538,7.33303477 12.2772954,8.05605449 12.2772954,9 C12.2772954,9.93588462 11.851678,10.9172014 11.2819745,11.4869049 C10.9060085,11.8628709 10.9060085,12.4268199 11.2819745,12.8027859 C11.4271642,12.9479755 11.9176724,13.0649528 12.2998149,12.9592565 C12.4124479,12.9281035 12.5156669,12.8776063 12.5978555,12.8027859 C13.773371,11.732654 14.1311161,10.1597914 14.1312524,9 C14.1312524,8.8299555 14.1286311,8.66015647 14.119665,8.4897429 C14.0674781,7.49784946 13.8010171,6.48513613 12.5978554,5.28197449 C12.2218894,4.9060085 11.6579405,4.9060085 11.2819745,5.28197449 Z M3.78571429,6.00820648 L0.714285714,6.00820648 C0.285714286,6.00820648 0,6.30901277 0,6.76022222 L0,11.2723167 C0,11.7235261 0.285714286,12.0243324 0.714285714,12.0243324 L3.78571429,12.0243324 L7.85714286,15.8819922 C8.35714286,16.1827985 9,15.8819922 9,15.2803796 L9,2.75215925 C9,2.15054666 8.35714286,1.77453879 7.85714286,2.15054666 L3.78571429,6.00820648 Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
<path
d="M11.2819745 5.28197449C10.9060085 5.65794047 10.9060085 6.22188944 11.2819745 6.59785542 12.0171538 7.33303477 12.2772954 8.05605449 12.2772954 9 12.2772954 9.93588462 11.851678 10.9172014 11.2819745 11.4869049 10.9060085 11.8628709 10.9060085 12.4268199 11.2819745 12.8027859 11.4271642 12.9479755 11.9176724 13.0649528 12.2998149 12.9592565 12.4124479 12.9281035 12.5156669 12.8776063 12.5978555 12.8027859 13.773371 11.732654 14.1311161 10.1597914 14.1312524 9 14.1312524 8.8299555 14.1286311 8.66015647 14.119665 8.4897429 14.0674781 7.49784946 13.8010171 6.48513613 12.5978554 5.28197449 12.2218894 4.9060085 11.6579405 4.9060085 11.2819745 5.28197449ZM3.78571429 6.00820648.714285714 6.00820648C.285714286 6.00820648 0 6.30901277 0 6.76022222L0 11.2723167C0 11.7235261.285714286 12.0243324.714285714 12.0243324L3.78571429 12.0243324 7.85714286 15.8819922C8.35714286 16.1827985 9 15.8819922 9 15.2803796L9 2.75215925C9 2.15054666 8.35714286 1.77453879 7.85714286 2.15054666L3.78571429 6.00820648Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="currentColor">
<path
d="M12.732233,5.81801948 L14.5,7.586 L16.267767,5.81801948 C16.6582912,5.42749519 17.2914562,5.42749519 17.6819805,5.81801948 C18.0725048,6.20854378 18.0725048,6.84170876 17.6819805,7.23223305 L15.914,9 L17.6819805,10.767767 C18.0725048,11.1582912 18.0725048,11.7914562 17.6819805,12.1819805 C17.2914562,12.5725048 16.6582912,12.5725048 16.267767,12.1819805 L14.5,10.414 L12.732233,12.1819805 C12.3417088,12.5725048 11.7085438,12.5725048 11.3180195,12.1819805 C10.9274952,11.7914562 10.9274952,11.1582912 11.3180195,10.767767 L13.085,9 L11.3180195,7.23223305 C10.9274952,6.84170876 10.9274952,6.20854378 11.3180195,5.81801948 C11.7085438,5.42749519 12.3417088,5.42749519 12.732233,5.81801948 Z M3.78571429,6.00820648 L0.714285714,6.00820648 C0.285714286,6.00820648 0,6.30901277 0,6.76022222 L0,11.2723167 C0,11.7235261 0.285714286,12.0243324 0.714285714,12.0243324 L3.78571429,12.0243324 L7.85714286,15.8819922 C8.35714286,16.1827985 9,15.8819922 9,15.2803796 L9,2.75215925 C9,2.15054666 8.35714286,1.77453879 7.85714286,2.15054666 L3.78571429,6.00820648 Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+10
View File
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"jsx": "react-jsx"
},
"include": ["src/**/*", "dist/**/*"]
}
+8
View File
@@ -30,6 +30,14 @@
"types": "./dist/predicate.d.ts",
"default": "./dist/predicate.js"
},
"./string": {
"types": "./dist/string.d.ts",
"default": "./dist/string.js"
},
"./style": {
"types": "./dist/style.d.ts",
"default": "./dist/style.js"
},
"./time": {
"types": "./dist/time.d.ts",
"default": "./dist/time.js"
+7
View File
@@ -0,0 +1,7 @@
export function pascalCase(str: string): string {
return str.replace(/[-_](.)/g, (_, c) => c.toUpperCase()).replace(/^(.)/, (_, c) => c.toUpperCase());
}
export function camelCase(str: string): string {
return pascalCase(str).replace(/^(.)/, (_, c) => c.toLowerCase());
}
+1
View File
@@ -0,0 +1 @@
export * from './casing';
@@ -0,0 +1,44 @@
import { describe, expect, it } from 'vitest';
import { camelCase, pascalCase } from '../casing';
describe('casing', () => {
describe('pascalCase', () => {
it('converts simple strings', () => {
expect(pascalCase('hello')).toBe('Hello');
});
it('converts kebab-case', () => {
expect(pascalCase('hello-world')).toBe('HelloWorld');
});
it('converts snake_case', () => {
expect(pascalCase('hello_world')).toBe('HelloWorld');
});
it('converts mixed case', () => {
expect(pascalCase('hello-World')).toBe('HelloWorld');
});
it('handles already pascal case', () => {
expect(pascalCase('HelloWorld')).toBe('HelloWorld');
});
});
describe('camelCase', () => {
it('converts simple strings', () => {
expect(camelCase('hello')).toBe('hello');
});
it('converts pascal case', () => {
expect(camelCase('HelloWorld')).toBe('helloWorld');
});
it('converts kebab-case', () => {
expect(camelCase('hello-world')).toBe('helloWorld');
});
it('converts snake_case', () => {
expect(camelCase('hello_world')).toBe('helloWorld');
});
});
});
+11
View File
@@ -0,0 +1,11 @@
/**
* A (very basic) utility to merge class names and make them a little easier to read.
* Aims to replicate the API of popular libraries like `clsx` and `classnames` but with a much simpler implementation.
* This is not intended to be a full replacement for those libraries, but it should be sufficient for our use case.
* It also allows us to avoid adding an additional dependency to our packages.
* @param classes - An array of class names, which can be strings or undefined. Undefined values will be filtered out.
* @returns A single string of class names, separated by spaces.
*/
export function cn(...classes: (string | undefined)[]): string {
return classes.filter(Boolean).join(' ');
}
+1
View File
@@ -0,0 +1 @@
export * from './cn';
+32
View File
@@ -0,0 +1,32 @@
import { describe, expect, it } from 'vitest';
import { cn } from '../cn';
describe('cn', () => {
it('returns an empty string for no arguments', () => {
expect(cn()).toBe('');
});
it('returns a single class name', () => {
expect(cn('foo')).toBe('foo');
});
it('joins multiple class names with a space', () => {
expect(cn('foo', 'bar', 'baz')).toBe('foo bar baz');
});
it('filters out undefined values', () => {
expect(cn('foo', undefined, 'bar')).toBe('foo bar');
});
it('handles all undefined values', () => {
expect(cn(undefined, undefined)).toBe('');
});
it('filters out empty strings', () => {
expect(cn('foo', '', 'bar')).toBe('foo bar');
});
it('preserves class names with multiple words', () => {
expect(cn('foo bar', 'baz')).toBe('foo bar baz');
});
});
+2
View File
@@ -9,6 +9,8 @@ export default defineConfig({
number: './src/number/index.ts',
object: './src/object/index.ts',
predicate: './src/predicate/index.ts',
string: './src/string/index.ts',
style: './src/style/index.ts',
time: './src/time/index.ts',
types: './src/types/index.ts',
},
+25 -3
View File
@@ -235,10 +235,32 @@ importers:
version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.3)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
packages/icons:
dependencies:
svgo:
specifier: ^3.3.2
version: 3.3.2
devDependencies:
tsdown:
specifier: ^0.20.3
version: 0.20.3(typescript@5.9.3)
'@svgr/core':
specifier: ^8.1.0
version: 8.1.0(typescript@5.9.3)
'@svgr/plugin-jsx':
specifier: ^8.1.0
version: 8.1.0(@svgr/core@8.1.0(typescript@5.9.3))
'@svgr/plugin-svgo':
specifier: ^8.1.0
version: 8.1.0(@svgr/core@8.1.0(typescript@5.9.3))(typescript@5.9.3)
'@types/react':
specifier: ^19.0.0
version: 19.2.7
'@videojs/utils':
specifier: workspace:*
version: link:../utils
react:
specifier: ^19.0.0
version: 19.2.3
tsx:
specifier: ^4.19.0
version: 4.21.0
typescript:
specifier: ^5.9.3
version: 5.9.3
@@ -1,4 +1,5 @@
import { BufferingIndicator, createPlayer, features, Video } from '@videojs/react';
import { BufferingIndicator, createPlayer, features } from '@videojs/react';
import { Video } from '@videojs/react/video';
import './BasicUsage.css';
@@ -1,4 +1,5 @@
import { createPlayer, features, MuteButton, Video } from '@videojs/react';
import { createPlayer, features, MuteButton } from '@videojs/react';
import { Video } from '@videojs/react/video';
import './VolumeLevels.css';
@@ -1,4 +1,5 @@
import { createPlayer, features, PiPButton, Video } from '@videojs/react';
import { createPlayer, features, PiPButton } from '@videojs/react';
import { Video } from '@videojs/react/video';
import './BasicUsage.css';
@@ -1,4 +1,5 @@
import { createPlayer, features, SeekButton, Video } from '@videojs/react';
import { createPlayer, features, SeekButton } from '@videojs/react';
import { Video } from '@videojs/react/video';
import './BasicUsage.css';
+2
View File
@@ -16,6 +16,8 @@
{ "path": "packages/core" },
{ "path": "packages/core/src/dom" },
{ "path": "packages/icons" },
{ "path": "packages/html" },
{ "path": "packages/react" }
],