Browse Source
* Add Astro package support and update dependencies * Update build script to use ES module for icon building * Add new Open Graph images for Astro package * Add imports check script to package.json and create .gitkeep in icons directory * Update README and documentation to use 'main' branch for image sources and adjust package.json for Astro icons * Correct capitalization of 'GitHub' in documentation and configuration files * Add color toggle functionality and new icons in various frameworks * Update documentation to reference 'main' branch for license links across all packages * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>pull/1562/head
committed by
GitHub
73 changed files with 3456 additions and 318 deletions
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 81 KiB |
@ -0,0 +1,39 @@ |
|||
--- |
|||
title: Tabler Icons for Astro |
|||
--- |
|||
|
|||
 |
|||
|
|||
## Installation |
|||
|
|||
<TabsPackage name="@tabler/icons-astro" /> |
|||
|
|||
or just [download from GitHub](https://github.com/tabler/tabler-icons/releases). |
|||
|
|||
## How to use |
|||
|
|||
It's built with ES modules so it's completely tree-shakable. Each icon can be imported as an Astro component. |
|||
|
|||
```astro |
|||
--- |
|||
import { IconArrowRight } from '@tabler/icons-astro'; |
|||
--- |
|||
|
|||
<IconArrowRight /> |
|||
``` |
|||
|
|||
You can pass additional props to adjust the icon. |
|||
|
|||
```astro |
|||
<IconArrowRight color="red" size={48} /> |
|||
``` |
|||
|
|||
Any valid SVG attribute is forwarded to the rendered `<svg>` element. |
|||
|
|||
### Props |
|||
|
|||
| name | type | default | |
|||
| ------------- | -------- | ------------ | |
|||
| `size` | _Number_ | 24 | |
|||
| `color` | _String_ | currentColor | |
|||
| `stroke` | _Number_ | 2 | |
|||
@ -0,0 +1,2 @@ |
|||
src/aliases.ts |
|||
src/icons-list.ts |
|||
@ -0,0 +1,88 @@ |
|||
# Tabler Icons for Astro |
|||
|
|||
<p align="center"> |
|||
<a href="https://tabler.io/icons?ref=tabler-icons-readme"><img src="https://raw.githubusercontent.com/tabler/tabler-icons/main/.github/packages/og-package-astro.png" alt="Tabler Icons" width="838"></a> |
|||
</p> |
|||
|
|||
<p align="center"> |
|||
Implementation of the Tabler Icons library for Astro applications. |
|||
</p> |
|||
|
|||
<p align="center"> |
|||
<a href="https://tabler.io/icons"><strong>Browse all icons at tabler.io →</strong></a> |
|||
</p> |
|||
|
|||
<p align="center"> |
|||
<a href="https://github.com/tabler/tabler-icons/releases"><img src="https://img.shields.io/npm/v/@tabler/icons" alt="Latest Release"></a> |
|||
<a href="https://github.com/tabler/tabler-icons/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@tabler/icons.svg" alt="License"></a> |
|||
</p> |
|||
|
|||
## Sponsors |
|||
|
|||
**If you want to support my project and help me grow it, you can [become a sponsor on GitHub](https://github.com/sponsors/codecalm) or just [donate on PayPal](https://paypal.me/codecalm) :)** |
|||
|
|||
<a href="https://github.com/sponsors/codecalm"> |
|||
<img src="https://cdn.jsdelivr.net/gh/tabler/sponsors@latest/sponsors.svg" alt="Tabler sponsors"> |
|||
</a> |
|||
|
|||
## Installation |
|||
|
|||
``` |
|||
yarn add @tabler/icons-astro |
|||
``` |
|||
|
|||
or |
|||
|
|||
``` |
|||
npm install @tabler/icons-astro |
|||
``` |
|||
|
|||
or |
|||
|
|||
``` |
|||
pnpm install @tabler/icons-astro |
|||
``` |
|||
|
|||
or just [download from GitHub](https://github.com/tabler/tabler-icons/releases). |
|||
|
|||
## How to use |
|||
|
|||
It's built with ES modules so it's completely tree-shakable. Each icon can be imported as an Astro component. |
|||
|
|||
```astro |
|||
--- |
|||
import { IconArrowRight } from '@tabler/icons-astro'; |
|||
--- |
|||
|
|||
<IconArrowRight /> |
|||
``` |
|||
|
|||
You can pass additional props to adjust the icon. |
|||
|
|||
```astro |
|||
<IconArrowRight color="red" size={48} /> |
|||
``` |
|||
|
|||
Any valid SVG attribute is forwarded to the rendered `<svg>` element. |
|||
|
|||
### Props |
|||
|
|||
| name | type | default | |
|||
| ------------- | -------- | ------------ | |
|||
| `size` | _Number_ | 24 | |
|||
| `color` | _String_ | currentColor | |
|||
| `stroke` | _Number_ | 2 | |
|||
|
|||
## Contributing |
|||
|
|||
For more info on how to contribute please see the [contribution guidelines](https://github.com/tabler/tabler-icons/blob/main/CONTRIBUTING.md). |
|||
|
|||
Caught a mistake or want to contribute to the documentation? [Edit this page on GitHub](https://github.com/tabler/tabler-icons/blob/main/packages/icons-astro/README.md) |
|||
|
|||
## License |
|||
|
|||
Tabler Icons is licensed under the [MIT License](https://github.com/tabler/tabler-icons/blob/main/LICENSE). |
|||
|
|||
## Sponsor Tabler |
|||
|
|||
<a href="https://github.com/sponsors/codecalm" target="_blank"><img src="https://github.com/tabler/tabler/raw/dev/src/static/sponsor-banner-readme.png?raw=true" alt="Sponsor Tabler" /></a> |
|||
@ -0,0 +1,43 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
import { buildJsIcons, buildIconsList } from '../../.build/build-icons.mjs' |
|||
|
|||
const componentTemplate = ({ |
|||
type, |
|||
name, |
|||
namePascal, |
|||
children |
|||
}) => `\
|
|||
import createTablerIcon from '../createTablerIcon'; |
|||
import type { AstroComponent } from '../types'; |
|||
|
|||
/** |
|||
* @component @name Icon${namePascal} |
|||
* @description Tabler SVG icon component, renders SVG Element with children. |
|||
* |
|||
* @param {import('../types').IconProps} props - Tabler icons props and any valid SVG attribute |
|||
* @returns {any} Astro Component |
|||
*/ |
|||
const Icon${namePascal} = createTablerIcon('${name}', ${JSON.stringify(children)}, '${type}') as AstroComponent; |
|||
|
|||
export default Icon${namePascal};`;
|
|||
|
|||
const indexItemTemplate = ({ |
|||
name, |
|||
namePascal |
|||
}) => `export { default as Icon${namePascal} } from './${name}';` |
|||
|
|||
const aliasTemplate = ({ fromPascal, to }) => `export { default as Icon${fromPascal} } from './icons/${to}';\n` |
|||
|
|||
buildJsIcons({ |
|||
name: 'icons-astro', |
|||
componentTemplate, |
|||
indexItemTemplate, |
|||
aliasTemplate, |
|||
key: false, |
|||
extension: 'ts', |
|||
indexFile: 'index.ts', |
|||
pascalName: false |
|||
}) |
|||
|
|||
buildIconsList('icons-astro') |
|||
@ -0,0 +1,49 @@ |
|||
{ |
|||
"name": "@tabler/icons-astro", |
|||
"version": "3.44.0", |
|||
"license": "MIT", |
|||
"author": "codecalm", |
|||
"description": "A set of free MIT-licensed high-quality SVG icons for you to use in your web projects.", |
|||
"homepage": "https://tabler.io/icons", |
|||
"bugs": { |
|||
"url": "https://github.com/tabler/tabler-icons/issues" |
|||
}, |
|||
"funding": { |
|||
"type": "github", |
|||
"url": "https://github.com/sponsors/codecalm" |
|||
}, |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "git+https://github.com/tabler/tabler-icons.git", |
|||
"directory": "packages/icons-astro" |
|||
}, |
|||
"sideEffects": false, |
|||
"amdName": "TablerIconsAstro", |
|||
"main": "./dist/cjs/tabler-icons-astro.cjs", |
|||
"module": "./dist/esm/tabler-icons-astro.mjs", |
|||
"types": "./dist/tabler-icons-astro.d.ts", |
|||
"files": [ |
|||
"dist" |
|||
], |
|||
"scripts": { |
|||
"build": "pnpm run clean && pnpm run copy:license && pnpm run build:icons && pnpm typecheck && pnpm run build:bundles", |
|||
"build:icons": "node build.mjs", |
|||
"build:bundles": "rollup -c ./rollup.config.mjs", |
|||
"copy:license": "cp ../../LICENSE ./LICENSE", |
|||
"clean": "find dist -mindepth 1 -delete 2>/dev/null; rm -rf dist && find . ! -name '.gitkeep' -path '*/src/icons/*' -exec rm -rf {} +", |
|||
"test": "vitest run", |
|||
"typecheck": "tsc", |
|||
"imports-check": "attw $(npm pack)" |
|||
}, |
|||
"dependencies": { |
|||
"@tabler/icons": "workspace:" |
|||
}, |
|||
"peerDependencies": { |
|||
"astro": ">=4.0.0" |
|||
}, |
|||
"devDependencies": { |
|||
"astro": "catalog:", |
|||
"typescript": "catalog:", |
|||
"vitest": "catalog:" |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
import fs from 'fs' |
|||
import { getRollupConfig } from '../../.build/rollup-plugins.mjs' |
|||
import dts from 'rollup-plugin-dts' |
|||
|
|||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8')) |
|||
|
|||
const outputFileName = 'tabler-icons-astro' |
|||
const inputs = ['./src/tabler-icons-astro.ts'] |
|||
|
|||
// Astro's runtime helpers stay external — they are resolved by the
|
|||
// consumer's Astro/Vite pipeline via the `astro` peer dependency.
|
|||
const external = [/^astro(\/|$)/] |
|||
|
|||
const bundles = [ |
|||
{ |
|||
format: 'cjs', |
|||
extension: 'cjs', |
|||
inputs, |
|||
external, |
|||
}, |
|||
{ |
|||
format: 'esm', |
|||
inputs, |
|||
preserveModules: true, |
|||
extension: 'mjs', |
|||
external, |
|||
}, |
|||
] |
|||
|
|||
export default [ |
|||
{ |
|||
input: inputs[0], |
|||
external, |
|||
output: [ |
|||
{ |
|||
file: `dist/${outputFileName}.d.ts`, |
|||
format: 'es', |
|||
}, |
|||
], |
|||
plugins: [dts()], |
|||
}, |
|||
...getRollupConfig(pkg, outputFileName, bundles, {}), |
|||
] |
|||
@ -0,0 +1,72 @@ |
|||
import { |
|||
createComponent, |
|||
render as renderTemplate, |
|||
renderSlot, |
|||
spreadAttributes, |
|||
unescapeHTML, |
|||
} from 'astro/compiler-runtime'; |
|||
import defaultAttributes from './defaultAttributes'; |
|||
import type { AstroComponent, IconNode, IconProps, SVGAttributes } from './types'; |
|||
|
|||
/** |
|||
* Creates an Astro component factory for a Tabler icon. |
|||
* |
|||
* Astro compiles a `.astro` file down to a `createComponent()` call whose |
|||
* render function returns a `renderTemplate` tagged-template result. We build |
|||
* the same shape by hand so every icon can be shipped as a plain `.ts` module. |
|||
* |
|||
* @param name Kebab-case icon name (e.g. `wallet`, `accessible-filled`) |
|||
* @param iconNode Serialized SVG children as `[tag, attributes]` tuples |
|||
* @param type `outline` (default) or `filled` |
|||
*/ |
|||
const createTablerIcon = ( |
|||
name: string, |
|||
iconNode: IconNode, |
|||
type: 'outline' | 'filled' = 'outline', |
|||
): AstroComponent => { |
|||
const base = defaultAttributes[type]; |
|||
|
|||
// Serialize the icon paths once at module init — they never change per render.
|
|||
const nodeHtml = iconNode |
|||
.map(([tag, attrs]) => `<${tag}${String(spreadAttributes(attrs as SVGAttributes))}></${tag}>`) |
|||
.join(''); |
|||
|
|||
return createComponent( |
|||
(result: any, props: IconProps, slots: any) => { |
|||
const { |
|||
size = 24, |
|||
color, |
|||
stroke, |
|||
strokeWidth, |
|||
title, |
|||
class: className, |
|||
...rest |
|||
} = props ?? {}; |
|||
|
|||
const colorAttributes: SVGAttributes = |
|||
type === 'filled' |
|||
? { fill: color ?? 'currentColor' } |
|||
: { |
|||
stroke: color ?? 'currentColor', |
|||
'stroke-width': stroke ?? strokeWidth ?? base['stroke-width'], |
|||
}; |
|||
|
|||
const attributes: SVGAttributes = { |
|||
...base, |
|||
...colorAttributes, |
|||
width: size, |
|||
height: size, |
|||
class: `tabler-icon tabler-icon-${name}${className ? ` ${className}` : ''}`, |
|||
...rest, |
|||
}; |
|||
|
|||
return renderTemplate`<svg${spreadAttributes(attributes)}>${ |
|||
title != null ? renderTemplate`<title>${title}</title>` : '' |
|||
}${unescapeHTML(nodeHtml)}${slots?.default ? renderSlot(result, slots.default) : ''}</svg>`;
|
|||
}, |
|||
'tabler-icon', |
|||
undefined, |
|||
) as AstroComponent; |
|||
}; |
|||
|
|||
export default createTablerIcon; |
|||
@ -0,0 +1,25 @@ |
|||
import { SVGAttributes } from './types'; |
|||
|
|||
const defaultAttributes: Record<'outline' | 'filled', SVGAttributes> = { |
|||
outline: { |
|||
xmlns: 'http://www.w3.org/2000/svg', |
|||
width: 24, |
|||
height: 24, |
|||
viewBox: '0 0 24 24', |
|||
fill: 'none', |
|||
stroke: 'currentColor', |
|||
'stroke-width': 2, |
|||
'stroke-linecap': 'round', |
|||
'stroke-linejoin': 'round', |
|||
}, |
|||
filled: { |
|||
xmlns: 'http://www.w3.org/2000/svg', |
|||
width: 24, |
|||
height: 24, |
|||
viewBox: '0 0 24 24', |
|||
fill: 'currentColor', |
|||
stroke: 'none', |
|||
}, |
|||
}; |
|||
|
|||
export default defaultAttributes; |
|||
@ -0,0 +1,7 @@ |
|||
export * from './icons/index'; |
|||
export * as icons from './icons/index'; |
|||
export * as iconsList from './icons-list'; |
|||
export * from './aliases'; |
|||
export { default as createTablerIcon } from './createTablerIcon'; |
|||
|
|||
export type { IconNode, IconProps, AstroComponent } from './types'; |
|||
@ -0,0 +1,20 @@ |
|||
export type IconNode = [elementName: string, attrs: Record<string, string | number>][]; |
|||
|
|||
export type SVGAttributes = Record<string, string | number | boolean | undefined>; |
|||
|
|||
export interface IconProps extends Record<string, any> { |
|||
size?: string | number; |
|||
color?: string; |
|||
stroke?: string | number; |
|||
strokeWidth?: string | number; |
|||
title?: string; |
|||
class?: string; |
|||
} |
|||
|
|||
/** |
|||
* Structural type for an Astro component factory. The runtime marker |
|||
* `isAstroComponentFactory` is attached by Astro's `createComponent`. |
|||
*/ |
|||
export type AstroComponent = ((result: any, props: IconProps, slots: any) => any) & { |
|||
isAstroComponentFactory: true; |
|||
}; |
|||
@ -0,0 +1,68 @@ |
|||
import { describe, it, expect, beforeAll } from 'vitest'; |
|||
import { experimental_AstroContainer as AstroContainer } from 'astro/container'; |
|||
import { IconAccessible, IconAccessibleFilled } from './src/tabler-icons-astro'; |
|||
|
|||
describe('Astro Icon component', () => { |
|||
let container: Awaited<ReturnType<typeof AstroContainer.create>>; |
|||
|
|||
beforeAll(async () => { |
|||
container = await AstroContainer.create(); |
|||
}); |
|||
|
|||
const openingTag = (html: string) => html.slice(0, html.indexOf('>') + 1); |
|||
|
|||
it('should render an svg element', async () => { |
|||
const html = await container.renderToString(IconAccessible as any); |
|||
expect(html).toContain('<svg'); |
|||
expect(html).toContain('</svg>'); |
|||
}); |
|||
|
|||
it('should apply default attributes', async () => { |
|||
const tag = openingTag(await container.renderToString(IconAccessible as any)); |
|||
expect(tag).toContain('width="24"'); |
|||
expect(tag).toContain('height="24"'); |
|||
expect(tag).toContain('stroke="currentColor"'); |
|||
expect(tag).toContain('stroke-width="2"'); |
|||
expect(tag).toContain('class="tabler-icon tabler-icon-accessible"'); |
|||
}); |
|||
|
|||
it('should update svg attributes from props', async () => { |
|||
const tag = openingTag( |
|||
await container.renderToString(IconAccessible as any, { |
|||
props: { size: 48, color: 'red', stroke: 4 }, |
|||
}), |
|||
); |
|||
expect(tag).toContain('width="48"'); |
|||
expect(tag).toContain('height="48"'); |
|||
expect(tag).toContain('stroke="red"'); |
|||
expect(tag).toContain('stroke-width="4"'); |
|||
}); |
|||
|
|||
it('should render the filled variant', async () => { |
|||
const tag = openingTag( |
|||
await container.renderToString(IconAccessibleFilled as any, { |
|||
props: { size: 48, color: 'red' }, |
|||
}), |
|||
); |
|||
expect(tag).toContain('width="48"'); |
|||
expect(tag).toContain('fill="red"'); |
|||
expect(tag).toContain('stroke="none"'); |
|||
expect(tag).not.toContain('stroke-width'); |
|||
}); |
|||
|
|||
it('should append a custom class', async () => { |
|||
const tag = openingTag( |
|||
await container.renderToString(IconAccessible as any, { |
|||
props: { class: 'test-class' }, |
|||
}), |
|||
); |
|||
expect(tag).toContain('class="tabler-icon tabler-icon-accessible test-class"'); |
|||
}); |
|||
|
|||
it('should render a title element', async () => { |
|||
const html = await container.renderToString(IconAccessible as any, { |
|||
props: { title: 'Accessible icon' }, |
|||
}); |
|||
expect(html).toContain('<title>Accessible icon</title>'); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,15 @@ |
|||
{ |
|||
"compilerOptions": { |
|||
"strict": true, |
|||
"target": "ESNext", |
|||
"module": "ESNext", |
|||
"moduleResolution": "Bundler", |
|||
"allowSyntheticDefaultImports": true, |
|||
"esModuleInterop": true, |
|||
"skipLibCheck": true, |
|||
"noEmit": true, |
|||
"isolatedModules": true, |
|||
"types": ["astro/client"] |
|||
}, |
|||
"include": ["src", "test.spec.ts"] |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
import { getViteConfig } from 'astro/config'; |
|||
|
|||
export default getViteConfig({ |
|||
test: { |
|||
globals: true, |
|||
environment: 'node', |
|||
}, |
|||
}); |
|||
File diff suppressed because it is too large
@ -0,0 +1,3 @@ |
|||
dist |
|||
.astro |
|||
.output |
|||
@ -0,0 +1,4 @@ |
|||
import { defineConfig } from 'astro/config'; |
|||
|
|||
// https://astro.build/config
|
|||
export default defineConfig({}); |
|||
@ -0,0 +1,18 @@ |
|||
{ |
|||
"name": "test-astro", |
|||
"private": true, |
|||
"version": "3.37.1", |
|||
"type": "module", |
|||
"scripts": { |
|||
"dev": "astro dev", |
|||
"build": "astro build", |
|||
"preview": "astro preview", |
|||
"clean": "rm -rf dist .astro" |
|||
}, |
|||
"dependencies": { |
|||
"@tabler/icons-astro": "workspace:*", |
|||
"astro": "catalog:" |
|||
}, |
|||
"peerDependencies": {}, |
|||
"optionalDependencies": {} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
--- |
|||
import { IconAd, IconAdOff, IconAdFilled, IconHeartFilled } from '@tabler/icons-astro'; |
|||
import '../styles/index.css'; |
|||
import '../styles/app.css'; |
|||
--- |
|||
|
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="utf-8" /> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
|||
<title>Tabler Icons for Astro — test</title> |
|||
</head> |
|||
<body> |
|||
<div class="App"> |
|||
<button type="button" class="toggle" aria-label="Toggle icon" aria-pressed="false"> |
|||
<IconAd size={48} class="icon-default" /> |
|||
<IconAdOff size={48} class="icon-active" /> |
|||
</button> |
|||
<IconAd size={48} stroke={1} /> |
|||
<IconAdOff size={48} stroke={1.5} /> |
|||
<IconAdFilled size={48} stroke={2} /> |
|||
|
|||
<button type="button" class="color-toggle" aria-label="Change color" title="Change color"> |
|||
<IconHeartFilled size={48} /> |
|||
</button> |
|||
</div> |
|||
|
|||
<script> |
|||
const toggle = document.querySelector('.toggle'); |
|||
toggle?.addEventListener('click', () => { |
|||
const active = toggle.classList.toggle('is-active'); |
|||
toggle.setAttribute('aria-pressed', String(active)); |
|||
}); |
|||
|
|||
// Colored icon — cycle its color on click (icon uses `currentColor`). |
|||
const colors = ['#e64980', '#4dabf7', '#51cf66', '#ffd43b', '#845ef7']; |
|||
const colorToggle = document.querySelector('.color-toggle'); |
|||
let colorIndex = 0; |
|||
colorToggle?.addEventListener('click', () => { |
|||
colorIndex = (colorIndex + 1) % colors.length; |
|||
colorToggle.style.color = colors[colorIndex]; |
|||
}); |
|||
</script> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,31 @@ |
|||
.App { |
|||
max-width: 1280px; |
|||
margin: 0 auto; |
|||
padding: 2rem; |
|||
display: flex; |
|||
gap: 1rem; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.toggle, |
|||
.color-toggle { |
|||
display: inline-flex; |
|||
padding: 0.6em; |
|||
} |
|||
|
|||
.color-toggle { |
|||
color: #e64980; |
|||
transition: color 0.2s; |
|||
} |
|||
|
|||
/* astro renders both toggle icons and swaps via CSS; harmless elsewhere */ |
|||
.toggle .icon-active { |
|||
display: none; |
|||
} |
|||
.toggle.is-active .icon-default { |
|||
display: none; |
|||
} |
|||
.toggle.is-active .icon-active { |
|||
display: inline-flex; |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
:root { |
|||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif; |
|||
font-size: 16px; |
|||
line-height: 24px; |
|||
font-weight: 400; |
|||
|
|||
color-scheme: light dark; |
|||
color: rgba(255, 255, 255, 0.87); |
|||
background-color: #242424; |
|||
|
|||
font-synthesis: none; |
|||
text-rendering: optimizeLegibility; |
|||
-webkit-font-smoothing: antialiased; |
|||
-moz-osx-font-smoothing: grayscale; |
|||
-webkit-text-size-adjust: 100%; |
|||
} |
|||
|
|||
a { |
|||
font-weight: 500; |
|||
color: #646cff; |
|||
text-decoration: inherit; |
|||
} |
|||
a:hover { |
|||
color: #535bf2; |
|||
} |
|||
|
|||
body { |
|||
margin: 0; |
|||
display: flex; |
|||
place-items: center; |
|||
justify-content: center; |
|||
min-width: 320px; |
|||
min-height: 100vh; |
|||
} |
|||
|
|||
h1 { |
|||
font-size: 3.2em; |
|||
line-height: 1.1; |
|||
} |
|||
|
|||
button { |
|||
border-radius: 8px; |
|||
border: 1px solid transparent; |
|||
padding: 0.6em 1.2em; |
|||
font-size: 1em; |
|||
font-weight: 500; |
|||
font-family: inherit; |
|||
background-color: #1a1a1a; |
|||
cursor: pointer; |
|||
transition: border-color 0.25s; |
|||
} |
|||
button:hover { |
|||
border-color: #646cff; |
|||
} |
|||
button:focus, |
|||
button:focus-visible { |
|||
outline: 4px auto -webkit-focus-ring-color; |
|||
} |
|||
|
|||
@media (prefers-color-scheme: light) { |
|||
:root { |
|||
color: #213547; |
|||
background-color: #ffffff; |
|||
} |
|||
a:hover { |
|||
color: #747bff; |
|||
} |
|||
button { |
|||
background-color: #f9f9f9; |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"extends": "astro/tsconfigs/base" |
|||
} |
|||
@ -1,25 +1,31 @@ |
|||
#app { |
|||
.App { |
|||
max-width: 1280px; |
|||
margin: 0 auto; |
|||
padding: 2rem; |
|||
text-align: center; |
|||
display: flex; |
|||
gap: 1rem; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.logo { |
|||
height: 6em; |
|||
padding: 1.5em; |
|||
} |
|||
.logo:hover { |
|||
filter: drop-shadow(0 0 2em #646cffaa); |
|||
} |
|||
.logo.preact:hover { |
|||
filter: drop-shadow(0 0 2em #673ab8aa); |
|||
.toggle, |
|||
.color-toggle { |
|||
display: inline-flex; |
|||
padding: 0.6em; |
|||
} |
|||
|
|||
.card { |
|||
padding: 2em; |
|||
.color-toggle { |
|||
color: #e64980; |
|||
transition: color 0.2s; |
|||
} |
|||
|
|||
.read-the-docs { |
|||
color: #888; |
|||
/* astro renders both toggle icons and swaps via CSS; harmless elsewhere */ |
|||
.toggle .icon-active { |
|||
display: none; |
|||
} |
|||
.toggle.is-active .icon-default { |
|||
display: none; |
|||
} |
|||
.toggle.is-active .icon-active { |
|||
display: inline-flex; |
|||
} |
|||
|
|||
@ -1,20 +1,25 @@ |
|||
import { useState } from 'preact/hooks' |
|||
import './app.css' |
|||
|
|||
import { IconAd, IconAdOff, IconAdFilled } from '@tabler/icons-preact'; |
|||
import { IconAd, IconAdOff, IconAdFilled, IconHeartFilled } from '@tabler/icons-preact'; |
|||
|
|||
const colors = ['#e64980', '#4dabf7', '#51cf66', '#ffd43b', '#845ef7'] |
|||
|
|||
export function App() { |
|||
const [active, setActive] = useState(false) |
|||
const [colorIndex, setColorIndex] = useState(0) |
|||
|
|||
return ( |
|||
<div className="App"> |
|||
<a onClick={() => setActive(!active)}> |
|||
<button type="button" className="toggle" aria-label="Toggle icon" aria-pressed={active} onClick={() => setActive(!active)}> |
|||
{active ? <IconAdOff size={48} /> : <IconAd size={48} />} |
|||
</a> |
|||
</button> |
|||
<IconAd size={48} stroke={1} /> |
|||
<IconAdOff size={48} stroke={1.5} /> |
|||
<IconAdFilled size={48} stroke={2} /> |
|||
<button type="button" className="color-toggle" aria-label="Change color" onClick={() => setColorIndex((colorIndex + 1) % colors.length)}> |
|||
<IconHeartFilled size={48} color={colors[colorIndex]} /> |
|||
</button> |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
@ -1,7 +1,31 @@ |
|||
#root { |
|||
.App { |
|||
max-width: 1280px; |
|||
margin: 0 auto; |
|||
padding: 2rem; |
|||
text-align: center; |
|||
display: flex; |
|||
gap: 1rem; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.toggle, |
|||
.color-toggle { |
|||
display: inline-flex; |
|||
padding: 0.6em; |
|||
} |
|||
|
|||
.color-toggle { |
|||
color: #e64980; |
|||
transition: color 0.2s; |
|||
} |
|||
|
|||
/* astro renders both toggle icons and swaps via CSS; harmless elsewhere */ |
|||
.toggle .icon-active { |
|||
display: none; |
|||
} |
|||
.toggle.is-active .icon-default { |
|||
display: none; |
|||
} |
|||
.toggle.is-active .icon-active { |
|||
display: inline-flex; |
|||
} |
|||
|
|||
@ -1,7 +1,31 @@ |
|||
#root { |
|||
.App { |
|||
max-width: 1280px; |
|||
margin: 0 auto; |
|||
padding: 2rem; |
|||
text-align: center; |
|||
display: flex; |
|||
gap: 1rem; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.toggle, |
|||
.color-toggle { |
|||
display: inline-flex; |
|||
padding: 0.6em; |
|||
} |
|||
|
|||
.color-toggle { |
|||
color: #e64980; |
|||
transition: color 0.2s; |
|||
} |
|||
|
|||
/* astro renders both toggle icons and swaps via CSS; harmless elsewhere */ |
|||
.toggle .icon-active { |
|||
display: none; |
|||
} |
|||
.toggle.is-active .icon-default { |
|||
display: none; |
|||
} |
|||
.toggle.is-active .icon-active { |
|||
display: inline-flex; |
|||
} |
|||
|
|||
@ -1,17 +1,25 @@ |
|||
<script setup lang="ts"> |
|||
import { ref } from "vue" |
|||
import { IconAd, IconAdOff, IconAdFilled } from '@tabler/icons-vue' |
|||
import { IconAd, IconAdOff, IconAdFilled, IconHeartFilled } from '@tabler/icons-vue' |
|||
|
|||
const active = ref(false) |
|||
|
|||
const colors = ['#e64980', '#4dabf7', '#51cf66', '#ffd43b', '#845ef7'] |
|||
const colorIndex = ref(0) |
|||
</script> |
|||
|
|||
<template> |
|||
<a @click="active = !active"> |
|||
<IconAdOff v-if="active" size="48" /> |
|||
<IconAd v-else size="48" /> |
|||
</a> |
|||
<IconAd size="48" stroke="1" /> |
|||
<IconAdOff size="48" stroke="1.5" /> |
|||
<IconAdFilled size="48" stroke="2" /> |
|||
<div class="App"> |
|||
<button type="button" class="toggle" aria-label="Toggle icon" :aria-pressed="active" @click="active = !active"> |
|||
<IconAdOff v-if="active" size="48" /> |
|||
<IconAd v-else size="48" /> |
|||
</button> |
|||
<IconAd size="48" stroke="1" /> |
|||
<IconAdOff size="48" stroke="1.5" /> |
|||
<IconAdFilled size="48" stroke="2" /> |
|||
<button type="button" class="color-toggle" aria-label="Change color" @click="colorIndex = (colorIndex + 1) % colors.length"> |
|||
<IconHeartFilled size="48" :color="colors[colorIndex]" /> |
|||
</button> |
|||
</div> |
|||
</template> |
|||
|
|||
|
|||
Loading…
Reference in new issue