mirror of
https://github.com/lucide-icons/lucide.git
synced 2026-11-11 13:28:19 +00:00
Compare commits
62
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f53e5bfff0 | ||
|
|
f06ac67e33 | ||
|
|
b0b1a4f8ab | ||
|
|
3a37bfa5cd | ||
|
|
16fb55f52c | ||
|
|
951813ce76 | ||
|
|
ba6751ac45 | ||
|
|
99c1037550 | ||
|
|
e32a4a2c10 | ||
|
|
5d9cc1580a | ||
|
|
d9f72b2525 | ||
|
|
0d0408c43b | ||
|
|
55d30bda04 | ||
|
|
f90f10a24a | ||
|
|
f3e36fb0d7 | ||
|
|
4faa47c0b0 | ||
|
|
e593a44ad2 | ||
|
|
dba8fb7932 | ||
|
|
a87cecebe3 | ||
|
|
3b9ea6d087 | ||
|
|
076b52527f | ||
|
|
58ef6830c6 | ||
|
|
f032bed22e | ||
|
|
266fd1c717 | ||
|
|
357a50175e | ||
|
|
a50c54cfff | ||
|
|
bd95db1af8 | ||
|
|
6b474e16d4 | ||
|
|
566032a95d | ||
|
|
042bf77119 | ||
|
|
5b8975c9a3 | ||
|
|
7eb8afe2f8 | ||
|
|
08b2fd2455 | ||
|
|
a79b2d131d | ||
|
|
b998e2892b | ||
|
|
5da3ee357d | ||
|
|
6bbe5ddb07 | ||
|
|
4a26fa58e0 | ||
|
|
5d592a96aa | ||
|
|
bdf795a8e3 | ||
|
|
e4be16acd5 | ||
|
|
ea1242d384 | ||
|
|
7cf805a408 | ||
|
|
c2c28580b3 | ||
|
|
858b662775 | ||
|
|
c17f928577 | ||
|
|
d32ef89ef9 | ||
|
|
5fb5967884 | ||
|
|
1cb69f6f9c | ||
|
|
6d4501f4d3 | ||
|
|
ba642ebf42 | ||
|
|
2f7d975022 | ||
|
|
b115c04286 | ||
|
|
5d0f64034b | ||
|
|
8a225b9fa7 | ||
|
|
95989f4535 | ||
|
|
073ffe0448 | ||
|
|
30f2eda422 | ||
|
|
a68cc8d52f | ||
|
|
249fe3a120 | ||
|
|
07ed920096 | ||
|
|
cef2020c7a |
@@ -10,3 +10,4 @@ touchpad
|
||||
ungroup
|
||||
toc
|
||||
sunlounger
|
||||
monstera
|
||||
|
||||
@@ -64,6 +64,7 @@ jobs:
|
||||
contents: write # Required to create the release and tag
|
||||
outputs:
|
||||
VERSION: ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||
RELEASE_URL: ${{ steps.create-release.outputs.url }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
@@ -102,6 +103,7 @@ jobs:
|
||||
echo '${{ steps.change-log.outputs.CHANGE_LOG }}'
|
||||
|
||||
- name: Create Release
|
||||
id: create-release
|
||||
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
|
||||
with:
|
||||
tag_name: ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||
@@ -129,3 +131,30 @@ jobs:
|
||||
with:
|
||||
version: ${{ inputs.version }}
|
||||
tag: ${{ inputs.tag }}
|
||||
|
||||
post-release:
|
||||
name: Trigger post-release automation
|
||||
if: >-
|
||||
always() &&
|
||||
github.repository == 'lucide-icons/lucide' &&
|
||||
(needs.start-release.result == 'success' || needs.dispatch-release.result == 'success')
|
||||
needs: [create-release, start-release, dispatch-release]
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Dispatch post_release event
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.POST_RELEASE_TOKEN }}
|
||||
RELEASE_TAG: ${{ needs.create-release.outputs.VERSION || inputs.version }}
|
||||
RELEASE_URL: ${{ needs.create-release.outputs.RELEASE_URL }}
|
||||
run: |
|
||||
if [ -z "$RELEASE_URL" ]; then
|
||||
RELEASE_URL="https://github.com/lucide-icons/lucide/releases/tag/$RELEASE_TAG"
|
||||
fi
|
||||
|
||||
gh api \
|
||||
--method POST \
|
||||
repos/lucide-icons/post-release/dispatches \
|
||||
--field event_type=post_release \
|
||||
--field "client_payload[release_tag]=$RELEASE_TAG" \
|
||||
--field "client_payload[release_url]=$RELEASE_URL"
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Docs Checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- docs/**
|
||||
- .github/workflows/docs.yml
|
||||
- package.json
|
||||
- pnpm-workspace.yaml
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
# Release metadata is generated by diffing every release tag
|
||||
fetch-depth: 0
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build docs
|
||||
run: pnpm --filter @lucide/docs build
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
# Release metadata is generated by diffing every release tag
|
||||
fetch-depth: 0
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Generate docs data
|
||||
run: pnpm --filter @lucide/docs run "/^prebuild:.*/"
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm --filter @lucide/docs typecheck
|
||||
@@ -46,3 +46,19 @@ jobs:
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter @lucide/astro test
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version-file: 'package.json'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm --filter @lucide/astro typecheck
|
||||
|
||||
@@ -37,3 +37,19 @@ jobs:
|
||||
with:
|
||||
name: lucide-font
|
||||
path: lucide-font
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm --filter build-font typecheck
|
||||
|
||||
@@ -29,3 +29,19 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter @lucide/lab build
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm --filter @lucide/lab typecheck
|
||||
|
||||
@@ -31,3 +31,19 @@ jobs:
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-preact test
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm --filter lucide-preact typecheck
|
||||
|
||||
@@ -47,3 +47,19 @@ jobs:
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-react-native test
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm --filter lucide-react-native typecheck
|
||||
|
||||
@@ -70,3 +70,19 @@ jobs:
|
||||
|
||||
- name: Run integration checks
|
||||
run: pnpm test:integrations:react
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm --filter lucide-react typecheck
|
||||
|
||||
@@ -27,4 +27,20 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-react test
|
||||
run: pnpm --filter @lucide/shared test
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm --filter @lucide/shared typecheck
|
||||
|
||||
@@ -47,3 +47,19 @@ jobs:
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-solid test
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm --filter lucide-solid typecheck
|
||||
|
||||
@@ -26,3 +26,19 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-static build
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm --filter lucide-static typecheck
|
||||
|
||||
@@ -61,5 +61,5 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Test
|
||||
- name: Typecheck
|
||||
run: pnpm --filter @lucide/svelte typecheck
|
||||
|
||||
@@ -47,3 +47,19 @@ jobs:
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter @lucide/vue test
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter @lucide/vue typecheck
|
||||
|
||||
@@ -46,3 +46,19 @@ jobs:
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide test
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm --filter lucide typecheck
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.DS_Store
|
||||
.idea
|
||||
.next
|
||||
.astro
|
||||
.obsidian
|
||||
.now
|
||||
.cache
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { eventHandler, setResponseHeader } from 'h3';
|
||||
import iconNodes from '../../data/iconNodes/index.ts';
|
||||
import { IconNodeWithKeys } from '../../theme/types';
|
||||
import iconMetaData from '../../data/iconMetaData';
|
||||
import releaseMeta from '../../data/releaseMetaData.json';
|
||||
import categories from '../../data/categoriesData.json';
|
||||
|
||||
const dataResponse = {
|
||||
icons: Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
|
||||
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
|
||||
const newIconNode = iconNode.map(([name, { key, ...attrs }]) => {
|
||||
return [name, attrs];
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { eventHandler, getQuery, setResponseHeader } from 'h3';
|
||||
import iconNodes from '../../data/iconNodes';
|
||||
import { IconNodeWithKeys } from '../../theme/types';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const query = getQuery(event);
|
||||
@@ -19,7 +18,7 @@ export default eventHandler((event) => {
|
||||
return [name, iconNode];
|
||||
}
|
||||
|
||||
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
|
||||
const newIconNode = iconNode.map(([name, { key, ...attrs }]) => {
|
||||
return [name, attrs];
|
||||
});
|
||||
|
||||
|
||||
@@ -97,7 +97,8 @@ export default defineConfig({
|
||||
{
|
||||
text: 'Resources',
|
||||
items: [
|
||||
...resourcesSidebar[0].items,
|
||||
...resourcesSidebar[0].items.map(({ text, link }) => ({ text, link })),
|
||||
{ text: 'How to use icons', link: '/how-to/' },
|
||||
{ text: 'Contributing icons', link: '/contribute/icons/' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { SVGProps } from 'react';
|
||||
import { getCommands } from './utils';
|
||||
|
||||
export type Point = { x: number; y: number };
|
||||
@@ -15,8 +14,8 @@ export type Path = {
|
||||
};
|
||||
|
||||
export type PathProps<
|
||||
RequiredProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
|
||||
NeverProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
|
||||
RequiredProps extends keyof React.SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
|
||||
NeverProps extends keyof React.SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
|
||||
> = Required<Pick<React.SVGProps<SVGElement & SVGRectElement & SVGCircleElement>, RequiredProps>> &
|
||||
Omit<
|
||||
React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>,
|
||||
|
||||
@@ -53,10 +53,7 @@ export const getCommands = (src: string) =>
|
||||
getNodes(src)
|
||||
.map(convertToPathNode)
|
||||
.flatMap(({ d, name }, idx) =>
|
||||
new SVGPathData(d)
|
||||
.toAbs()
|
||||
// @ts-expect-error `commands` is typed as a union of command types svgson does not narrow
|
||||
.commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })),
|
||||
new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })),
|
||||
);
|
||||
|
||||
const getPaths = (src: string) => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Category, IconEntity } from '../theme/types';
|
||||
|
||||
const directory = path.join(process.cwd(), '../categories');
|
||||
|
||||
export function getAllCategoryFiles(): Category[] {
|
||||
export function getAllCategoryFiles(): Omit<Category, 'iconCount'>[] {
|
||||
const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json');
|
||||
|
||||
return fileNames.map((fileName) => {
|
||||
@@ -21,9 +21,9 @@ export function getAllCategoryFiles(): Category[] {
|
||||
}
|
||||
|
||||
export function mapCategoryIconCount(
|
||||
categories: Category[],
|
||||
categories: Omit<Category, 'iconCount'>[],
|
||||
icons: { categories: IconEntity['categories'] }[],
|
||||
) {
|
||||
): Category[] {
|
||||
return categories.map((category) => ({
|
||||
...category,
|
||||
iconCount: icons.reduce(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createLucideIcon } from 'lucide-react/src/lucide-react';
|
||||
import React from 'react';
|
||||
import createLucideIcon from 'lucide-react/src/createLucideIcon';
|
||||
import { type LucideProps, type IconNode } from 'lucide-react/src/types';
|
||||
import { IconEntity } from '../theme/types';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
|
||||
@@ -20,6 +20,8 @@ export async function getData(name: string) {
|
||||
const iconNode = iconNodes[name];
|
||||
|
||||
const releaseData = releaseMeta?.[name] ?? {
|
||||
fromFork: false,
|
||||
awaitingRelease: false,
|
||||
createdRelease: {
|
||||
version: '0.0.0',
|
||||
date: DATE_OF_FORK,
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, isAbsolute, resolve } from 'node:path';
|
||||
import type MarkdownIt from 'markdown-it';
|
||||
import type { RenderRule } from 'markdown-it/lib/renderer.mjs';
|
||||
import type { MarkdownIt, RenderRule } from './types';
|
||||
import container from 'markdown-it-container';
|
||||
|
||||
type ContainerArgs = [typeof container, string, { render: RenderRule }];
|
||||
type GuidanceType = 'do' | 'dont' | 'caution';
|
||||
|
||||
const isGuidanceOpenToken = (type: string) =>
|
||||
type === 'container_do_open' ||
|
||||
type === 'container_dont_open' ||
|
||||
type === 'container_caution_open';
|
||||
|
||||
const isGuidanceCloseToken = (type: string) =>
|
||||
type === 'container_do_close' ||
|
||||
type === 'container_dont_close' ||
|
||||
type === 'container_caution_close';
|
||||
|
||||
const parseInfo = (info: string, marker: string) => info.trim().slice(marker.length).trim();
|
||||
|
||||
@@ -81,12 +91,12 @@ const hasTopLevelContent = (tokens: Parameters<RenderRule>[0], index: number) =>
|
||||
return false;
|
||||
}
|
||||
|
||||
if (token.type === 'container_do_open' || token.type === 'container_dont_open') {
|
||||
if (isGuidanceOpenToken(token.type)) {
|
||||
guidanceDepth += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token.type === 'container_do_close' || token.type === 'container_dont_close') {
|
||||
if (isGuidanceCloseToken(token.type)) {
|
||||
guidanceDepth -= 1;
|
||||
continue;
|
||||
}
|
||||
@@ -107,7 +117,7 @@ const hasGuidance = (tokens: Parameters<RenderRule>[0], index: number) => {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (token.type === 'container_do_open' || token.type === 'container_dont_open') {
|
||||
if (isGuidanceOpenToken(token.type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -123,7 +133,7 @@ const isFirstGuidance = (tokens: Parameters<RenderRule>[0], index: number) => {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (token.type === 'container_do_open' || token.type === 'container_dont_open') {
|
||||
if (isGuidanceOpenToken(token.type)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -141,6 +151,12 @@ const getExampleOpenIndex = (tokens: Parameters<RenderRule>[0], index: number) =
|
||||
return index;
|
||||
};
|
||||
|
||||
const labels: Record<GuidanceType, string> = {
|
||||
do: `<svg 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" class="lucide lucide-check-icon lucide-check"><path d="M20 6 9 17l-5-5"/></svg> Do`,
|
||||
dont: `<svg 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" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg> Don't`,
|
||||
caution: `<svg 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" class="lucide lucide-exclamation-point"><path d="M12 20 L12 20" /><path d="M12 4 L12 15" /></svg> Caution`,
|
||||
};
|
||||
|
||||
export default function examplePlugin(md: MarkdownIt) {
|
||||
const escapeHtml = md.utils.escapeHtml;
|
||||
|
||||
@@ -170,7 +186,7 @@ export default function examplePlugin(md: MarkdownIt) {
|
||||
},
|
||||
];
|
||||
|
||||
const createGuidanceContainer = (type: 'do' | 'dont'): ContainerArgs => [
|
||||
const createGuidanceContainer = (type: GuidanceType): ContainerArgs => [
|
||||
container,
|
||||
type,
|
||||
{
|
||||
@@ -185,10 +201,7 @@ export default function examplePlugin(md: MarkdownIt) {
|
||||
hasTopLevelContent(tokens, exampleOpenIndex) && isFirstGuidance(tokens, idx)
|
||||
? '</div>\n'
|
||||
: '';
|
||||
const label =
|
||||
type === 'do'
|
||||
? `<svg 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" class="lucide lucide-check-icon lucide-check"><path d="M20 6 9 17l-5-5"/></svg> Do`
|
||||
: `<svg 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" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg> Don't`;
|
||||
const label = labels[type];
|
||||
|
||||
return `${contentClose}<figure class="example-guidance example-guidance--${type}">${media}<figcaption class="example-guidance__caption"><div class="example-guidance__label">${label}</div>\n`;
|
||||
}
|
||||
@@ -201,4 +214,5 @@ export default function examplePlugin(md: MarkdownIt) {
|
||||
md.use(...createExampleContainer());
|
||||
md.use(...createGuidanceContainer('do'));
|
||||
md.use(...createGuidanceContainer('dont'));
|
||||
md.use(...createGuidanceContainer('caution'));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import type MarkdownIt from 'markdown-it';
|
||||
import type { RenderRule } from 'markdown-it/lib/renderer.mjs';
|
||||
import type Token from 'markdown-it/lib/token.mjs';
|
||||
import type { MarkdownIt, RenderRule, Token } from './types';
|
||||
import container from 'markdown-it-container';
|
||||
import sandpackTheme from '../theme/sandpackTheme.json';
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* />
|
||||
*/
|
||||
|
||||
import type MarkdownIt from 'markdown-it';
|
||||
import type { MarkdownIt } from './types';
|
||||
|
||||
type SnackParams = Record<string, string>;
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { MarkdownRenderer } from 'vitepress';
|
||||
|
||||
// VitePress 1.x renders with markdown-it 14, so derive the plugin types from its renderer
|
||||
// instead of the markdown-it 15 package installed in docs.
|
||||
export type MarkdownIt = MarkdownRenderer;
|
||||
export type RenderRule = NonNullable<MarkdownIt['renderer']['rules'][string]>;
|
||||
export type Token = Parameters<RenderRule>[0][number];
|
||||
@@ -94,7 +94,7 @@ export const angularSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -69,7 +69,7 @@ export const astroSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -36,6 +36,7 @@ const sidebar: Sidebar = {
|
||||
'/guide/react-native/': reactNativeSidebar,
|
||||
'/guide/angular/': angularSidebar,
|
||||
'/guide/static/': lucideStaticSidebar,
|
||||
'/how-to': resourcesSidebar,
|
||||
'/community': resourcesSidebar,
|
||||
'/contribute': resourcesSidebar,
|
||||
'/license': resourcesSidebar,
|
||||
|
||||
@@ -79,7 +79,7 @@ export const lucideSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -89,7 +89,7 @@ export const preactSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -100,7 +100,7 @@ export const reactSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -29,6 +29,25 @@ export const resourcesSidebar = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'How to use Lucide icons',
|
||||
items: [
|
||||
{
|
||||
text: 'Getting started',
|
||||
link: '/how-to/',
|
||||
},
|
||||
{
|
||||
text: 'Accessibility guide',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Learn how to make icons readable, understandable, and usable with assistive technology.',
|
||||
},
|
||||
{
|
||||
text: 'How to pick the right icon',
|
||||
link: '/how-to/icon-guide',
|
||||
desc: 'Learn how to decide when icons belong in an interface and how to pick symbols people are likely to recognize.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Contributing icons',
|
||||
items: [
|
||||
|
||||
@@ -90,7 +90,7 @@ export const solidSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -75,7 +75,7 @@ export const lucideStaticSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -79,7 +79,7 @@ export const svelteSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -85,7 +85,7 @@ export const vueSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useLocalStorage } from '@vueuse/core';
|
||||
import IconButton from './base/IconButton.vue';
|
||||
import Button from './base/Button.vue';
|
||||
import { x } from '../../data/iconNodes';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
@@ -65,16 +65,17 @@ function hidePopup() {
|
||||
}"
|
||||
class="ask-overlay"
|
||||
>
|
||||
<IconButton
|
||||
<Button
|
||||
@click="hidePopup"
|
||||
class="hide-button"
|
||||
>
|
||||
<Icon
|
||||
:iconNode="x"
|
||||
name="x"
|
||||
:size="20"
|
||||
absoluteStrokeWidth
|
||||
/>
|
||||
</IconButton>
|
||||
</Button>
|
||||
|
||||
<h2 class="title">
|
||||
We value your feedback!
|
||||
|
||||
+43
-3
@@ -1,7 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
href?: string;
|
||||
}>()
|
||||
|
||||
const isExternal = computed(() => {
|
||||
return props.href?.startsWith('http') ?? false
|
||||
})
|
||||
|
||||
const component = computed(() => {
|
||||
return props.href ? 'a' : 'button'
|
||||
})
|
||||
|
||||
const target = computed(() => {
|
||||
return isExternal.value ? '_blank' : undefined
|
||||
})
|
||||
|
||||
const rel = computed(() => {
|
||||
return isExternal.value ? 'noreferrer noopener' : undefined
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button v-bind="$attrs" class="icon-button">
|
||||
<component
|
||||
:is="component"
|
||||
v-bind="$attrs"
|
||||
:href="href"
|
||||
:target="target"
|
||||
:rel="rel"
|
||||
class="icon-button"
|
||||
>
|
||||
<slot />
|
||||
</button>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -16,12 +47,21 @@
|
||||
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
|
||||
border-radius: 6px;
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
/* width: 56px;
|
||||
height: 56px; */
|
||||
font-size: 24px;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
transition: color 0.1s, border-color 0.1s, background-color 0.1s;
|
||||
}
|
||||
|
||||
.icon-button span {
|
||||
padding: 0 4px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.icon-button:hover {
|
||||
border-color: var(--vp-button-alt-hover-border);
|
||||
color: var(--vp-button-alt-hover-text);
|
||||
@@ -14,7 +14,7 @@ export interface InputProps {
|
||||
import { ref, onMounted, nextTick, watch } from 'vue';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import { x } from '../../../data/iconNodes';
|
||||
import IconButton from './IconButton.vue';
|
||||
import Button from './Button.vue';
|
||||
|
||||
const props = withDefaults(defineProps<InputProps>(), {
|
||||
type: 'text',
|
||||
@@ -68,9 +68,9 @@ defineExpose({
|
||||
ref="input"
|
||||
:value="modelValue"
|
||||
v-bind="$attrs"
|
||||
@input="$emit('update:modelValue', $event.target.value)"
|
||||
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
<IconButton
|
||||
<Button
|
||||
@click="onClear"
|
||||
v-if="type === 'search' && modelValue"
|
||||
class="clear-button"
|
||||
@@ -79,9 +79,10 @@ defineExpose({
|
||||
>
|
||||
<Icon
|
||||
:iconNode="x"
|
||||
name="x"
|
||||
:size="20"
|
||||
/>
|
||||
</IconButton>
|
||||
</Button>
|
||||
<kbd
|
||||
v-if="shortcut"
|
||||
class="shortcut"
|
||||
|
||||
@@ -46,6 +46,7 @@ const value = computed({
|
||||
<template #icon>
|
||||
<Icon
|
||||
:iconNode="search"
|
||||
name="search"
|
||||
class="search-icon"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -9,7 +9,8 @@ import RocketDuotoneIcon from '../home/RocketDuotoneIcon.ts';
|
||||
<p>
|
||||
<Icon
|
||||
:iconNode="RocketDuotoneIcon"
|
||||
size="18"
|
||||
name="rocket-duotone"
|
||||
:size="18"
|
||||
class="rocket-icon"
|
||||
/>
|
||||
<VPLink
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue'
|
||||
|
||||
const props = defineProps<{
|
||||
tabs: string[];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TabGroup>
|
||||
<TabList class="tab-list">
|
||||
<template v-for="(tab, index) in props.tabs" :key="index">
|
||||
<Tab :id="`tab-${index}`" class="tab">{{ tab }}</Tab>
|
||||
</template>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<template v-for="(_, name) in $slots">
|
||||
<TabPanel>
|
||||
<slot :name="name" />
|
||||
</TabPanel>
|
||||
</template>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.tab-list {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 32px;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tab {
|
||||
display: block;
|
||||
line-height: 32px;
|
||||
font-size: 16px;
|
||||
padding: 10px 12px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: color 0.5s;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.tab[aria-selected="true"] {
|
||||
color: var(--vp-c-text-1);
|
||||
transition: color 0.25s;
|
||||
border-bottom: 2px solid var(--vp-c-brand);
|
||||
}
|
||||
|
||||
@media (min-width: 860px) {
|
||||
.tab-list {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.tab {
|
||||
font-size: 18px;
|
||||
padding: 10px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.tab {
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1152px) {
|
||||
.tab {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -34,7 +34,7 @@ const percentage = computed<string>(() => `${((Number(props.modelValue) - props.
|
||||
type="range"
|
||||
v-bind="$attrs"
|
||||
v-bind:value="modelValue"
|
||||
v-on:input="$emit('update:modelValue', Number($event.target.value))"
|
||||
v-on:input="$emit('update:modelValue', Number(($event.target as HTMLInputElement).value))"
|
||||
:min="min"
|
||||
:max="max"
|
||||
:step="step"
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { rotateCw } from '../../../data/iconNodes';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import IconButton from './IconButton.vue';
|
||||
import Button from './Button.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<IconButton class="reset-button">
|
||||
<Button class="reset-button">
|
||||
<Icon
|
||||
:size="20"
|
||||
:iconNode="rotateCw"
|
||||
name="rotate-cw"
|
||||
/>
|
||||
</IconButton>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -27,6 +27,7 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
|
||||
<slot name="end-icon">
|
||||
<Icon
|
||||
:iconNode="chevronsUpDown"
|
||||
name="chevrons-up-down"
|
||||
class="chevron-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
@@ -57,6 +58,7 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
|
||||
>
|
||||
<Icon
|
||||
:iconNode="check"
|
||||
name="check"
|
||||
class="check"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { type FunctionalComponent } from 'vue';
|
||||
import { type LucideProps } from '@lucide/vue';
|
||||
import { type LucideProps } from '@lucide/vue/src/types';
|
||||
import { Sandpack } from 'sandpack-vue3';
|
||||
|
||||
defineProps<FunctionalComponent<LucideProps>>();
|
||||
|
||||
@@ -10,7 +10,14 @@ import { useLocalStorage } from '@vueuse/core';
|
||||
const { page } = useData();
|
||||
const router = useRouter();
|
||||
|
||||
const frameworks = [
|
||||
interface Framework {
|
||||
name: string;
|
||||
icon: string;
|
||||
iconDark?: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const frameworks: Framework[] = [
|
||||
{ name: 'Vanilla', icon: '/framework-logos/js.svg', value: '/guide/lucide/' },
|
||||
{ name: 'React', icon: '/framework-logos/react.svg', value: '/guide/react/' },
|
||||
{ name: 'Vue', icon: '/framework-logos/vue.svg', value: '/guide/vue/' },
|
||||
@@ -48,7 +55,7 @@ const selected = computed(() => {
|
||||
return current || (fallbackFramework.value?.value ? fallbackFramework.value : frameworks[0]);
|
||||
});
|
||||
|
||||
function onSelectFramework(item: { name: string; icon: string; iconDark?: string; value: string }) {
|
||||
function onSelectFramework(item: Framework) {
|
||||
fallbackFramework.value = item;
|
||||
if (item.value !== router.route.path) {
|
||||
const likeRoute = router.route.path.replace(selected.value.value, item.value);
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
import { ref, shallowRef } from 'vue';
|
||||
import { data } from './HomeHeroIconsCard.data';
|
||||
import { useRouter } from 'vitepress';
|
||||
import { motion, Variants, useScroll, useTransform } from 'motion-v';
|
||||
import { motion, useScroll, useTransform, type Options as MotionOptions } from 'motion-v';
|
||||
import LucideIcon from '../base/LucideIcon.vue';
|
||||
|
||||
// motion-v re-exports framer-motion's `Variants`, which doesn't match the `variants` prop of its components
|
||||
type Variants = MotionOptions['variants'];
|
||||
|
||||
const emit = defineEmits(['animation-complete']);
|
||||
|
||||
const MotionLucideIcon = motion.create(LucideIcon);
|
||||
@@ -129,7 +132,7 @@ const onAnimationComplete = (item) => {
|
||||
|
||||
const randomIndex = ref(Math.floor(Math.random() * 64));
|
||||
|
||||
const iconAnimationVariants = {
|
||||
const iconAnimationVariants: Variants = {
|
||||
initial: {
|
||||
animationName: 'end',
|
||||
opacity: 0,
|
||||
@@ -149,7 +152,7 @@ const iconAnimationVariants = {
|
||||
|
||||
const shrinkIconAnimation = ref('initial');
|
||||
|
||||
const shrinkIconVariants = {
|
||||
const shrinkIconVariants: Variants = {
|
||||
initial: { strokeWidth: 2 },
|
||||
shrinkIcons: (index) => ({
|
||||
animationName: 'shrinkIcons',
|
||||
|
||||
@@ -18,7 +18,7 @@ const pickIcons = [
|
||||
'clapperboard',
|
||||
'tool-case',
|
||||
'printer',
|
||||
'album',
|
||||
'square-bookmark',
|
||||
'pencil-ruler',
|
||||
'calendar',
|
||||
'brain',
|
||||
@@ -69,7 +69,7 @@ const pickIcons = [
|
||||
'stethoscope',
|
||||
];
|
||||
|
||||
export default {
|
||||
const loader = {
|
||||
async load() {
|
||||
const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode }));
|
||||
|
||||
@@ -96,3 +96,8 @@ export default {
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
declare const data: Awaited<ReturnType<typeof loader.load>>;
|
||||
|
||||
export { data };
|
||||
export default loader;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default {
|
||||
const loader = {
|
||||
async load() {
|
||||
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest')
|
||||
.then((res) => {
|
||||
@@ -16,3 +16,8 @@ export default {
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
declare const data: Awaited<ReturnType<typeof loader.load>>;
|
||||
|
||||
export { data };
|
||||
export default loader;
|
||||
|
||||
@@ -15,10 +15,11 @@ import RocketDuotoneIcon from './RocketDuotoneIcon.ts';
|
||||
class="badge-special confetti-button animate"
|
||||
href="/guide/version-1"
|
||||
>
|
||||
<Icon :iconNode="RocketDuotoneIcon" />
|
||||
<Icon :iconNode="RocketDuotoneIcon" name="rocket-duotone" />
|
||||
Version 1
|
||||
<Icon
|
||||
:iconNode="moveRight"
|
||||
name="move-right"
|
||||
class=""
|
||||
/>
|
||||
</Badge>
|
||||
|
||||
@@ -41,8 +41,8 @@ const sizeCssVar = useCssVar(
|
||||
)
|
||||
|
||||
syncRef(color, colorCssVar)
|
||||
syncRef(strokeWidth, strokeWidthCssVar)
|
||||
syncRef(size, sizeCssVar)
|
||||
syncRef(strokeWidth, strokeWidthCssVar, { transform: { ltr: String, rtl: Number } })
|
||||
syncRef(size, sizeCssVar, { transform: { ltr: String, rtl: Number } })
|
||||
|
||||
function resetStyle () {
|
||||
color.value = 'currentColor'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default {
|
||||
const loader = {
|
||||
async load() {
|
||||
return {
|
||||
packages: [
|
||||
@@ -61,3 +61,8 @@ export default {
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
declare const data: Awaited<ReturnType<typeof loader.load>>;
|
||||
|
||||
export { data };
|
||||
export default loader;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
headingLevel: 1 | 2 | 3 | 4 | 5 | 6,
|
||||
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6,
|
||||
}>()
|
||||
|
||||
const headingElement = computed(() => `h${props.headingLevel ?? 2}`)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
const RocketDuotoneIcon = [
|
||||
import type { LucideIconNode } from '@lucide/vue/src/types';
|
||||
|
||||
const RocketDuotoneIcon: LucideIconNode[] = [
|
||||
[
|
||||
'path',
|
||||
{
|
||||
@@ -16,6 +18,6 @@ const RocketDuotoneIcon = [
|
||||
'clip-rule': 'evenodd',
|
||||
},
|
||||
],
|
||||
] as const;
|
||||
];
|
||||
|
||||
export default RocketDuotoneIcon;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useData } from 'vitepress';
|
||||
import { useSessionStorage } from '@vueuse/core';
|
||||
import IconButton from '../base/IconButton.vue';
|
||||
import Button from '../base/Button.vue';
|
||||
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue';
|
||||
import { x } from '../../../data/iconNodes';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
@@ -40,16 +40,17 @@ onMounted(() => {
|
||||
class="floating-ad"
|
||||
v-if="theme.carbonAds"
|
||||
>
|
||||
<IconButton
|
||||
<Button
|
||||
@click="hideAd"
|
||||
class="hide-button"
|
||||
>
|
||||
<Icon
|
||||
:iconNode="x"
|
||||
name="x"
|
||||
:size="20"
|
||||
absoluteStrokeWidth
|
||||
/>
|
||||
</IconButton>
|
||||
</Button>
|
||||
<VPDocAsideCarbonAds :carbon-ads="theme.carbonAds" overlay />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
|
||||
import iconsMetaData from '../../../data/iconMetaData';
|
||||
|
||||
export default {
|
||||
const loader = {
|
||||
async load() {
|
||||
let categories = getAllCategoryFiles();
|
||||
|
||||
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData));
|
||||
const categories = mapCategoryIconCount(getAllCategoryFiles(), Object.values(iconsMetaData));
|
||||
|
||||
return {
|
||||
categories,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
declare const data: Awaited<ReturnType<typeof loader.load>>;
|
||||
|
||||
export { data };
|
||||
export default loader;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useData } from 'vitepress'
|
||||
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
|
||||
import { isActive } from 'vitepress/dist/client/shared'
|
||||
import { isActive } from 'vitepress/dist/client/shared.js'
|
||||
import { useActiveAnchor } from '../../composables/useActiveAnchor'
|
||||
import { data } from './CategoryList.data'
|
||||
import CategoryListItem from './CategoryListItem.vue'
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { pencilSparkles } from '../../../data/iconNodes';
|
||||
import { IconEntity } from '../../types';
|
||||
import getStudioLink from '../../utils/getStudioLink';
|
||||
import Button from '../base/Button.vue';
|
||||
import Icon from '@lucide/vue/src/Icon.ts';
|
||||
|
||||
defineProps<{
|
||||
icon: IconEntity
|
||||
}>()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button
|
||||
:href="getStudioLink(icon.name, icon.iconNode, 'icon-info')"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Icon :iconNode="pencilSparkles" name="pencil-sparkles" :size="24" />
|
||||
<span class="text">Edit in studio</span>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.text {
|
||||
margin: 0 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -30,10 +30,15 @@ const props = defineProps<{
|
||||
.contributors {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
align-items: start;
|
||||
/* justify-content: flex-end; */
|
||||
gap: 16px;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.contributors .label {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.avatar-group {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -16,19 +16,17 @@ const tags = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- <PageContainer class="overview"> -->
|
||||
<IconPreview
|
||||
:name="$params.name"
|
||||
:iconNode="$params.iconNode"
|
||||
class="preview"
|
||||
customizable
|
||||
/>
|
||||
<div class="details">
|
||||
<h1 class="title">
|
||||
{{ $params.name }}
|
||||
</h1>
|
||||
</div>
|
||||
<!-- </PageContainer> -->
|
||||
<IconPreview
|
||||
:name="$params.name"
|
||||
:iconNode="$params.iconNode"
|
||||
class="preview"
|
||||
customizable
|
||||
/>
|
||||
<div class="details">
|
||||
<h1 class="title">
|
||||
{{ $params.name }}
|
||||
</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -6,7 +6,11 @@ import useConfetti from '../../composables/useConfetti';
|
||||
const { animate, confetti } = useConfetti();
|
||||
const slots = useSlots();
|
||||
|
||||
const copiedText = computed(() => slots.default?.()[0].children);
|
||||
const copiedText = computed(() => {
|
||||
const children = slots.default?.()[0].children;
|
||||
|
||||
return typeof children === 'string' ? children : '';
|
||||
});
|
||||
|
||||
function copyText() {
|
||||
navigator.clipboard.writeText(copiedText.value);
|
||||
@@ -25,6 +29,7 @@ function copyText() {
|
||||
<slot />
|
||||
<Icon
|
||||
:iconNode="copy"
|
||||
name="copy"
|
||||
:size="20"
|
||||
class="copy-icon"
|
||||
/>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { IconEntity } from '../../types';
|
||||
import { computed } from 'vue';
|
||||
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
|
||||
import IconButton from '../base/IconButton.vue';
|
||||
import Button from '../base/Button.vue';
|
||||
import IconContributors from './IconContributors.vue';
|
||||
import IconPreview from './IconPreview.vue';
|
||||
import { x, expand } from '../../../data/iconNodes';
|
||||
@@ -12,6 +12,7 @@ import Badge from '../base/Badge.vue';
|
||||
import { computedAsync } from '@vueuse/core';
|
||||
import { satisfies } from 'semver';
|
||||
import { useExternalLibs } from '../../composables/useExternalLibs';
|
||||
import EditInStudioButton from './EditInStudioButton.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
iconName: string | null;
|
||||
@@ -70,12 +71,19 @@ const Expand = createLucideIcon('Expand', expand);
|
||||
<div class="overlay-panel">
|
||||
<nav class="overlay-menu">
|
||||
<Badge
|
||||
v-if="icon.createdRelease"
|
||||
v-if="icon.awaitingRelease"
|
||||
class="version awaiting-release"
|
||||
>Unreleased</Badge
|
||||
>
|
||||
<Badge
|
||||
v-else-if="icon.createdRelease"
|
||||
class="version"
|
||||
:href="releaseTagLink(icon.createdRelease.version)"
|
||||
>v{{ icon.createdRelease.version }}</Badge
|
||||
>
|
||||
<IconButton
|
||||
{{ icon.createdRelease.version }}
|
||||
</Badge>
|
||||
<EditInStudioButton :icon="icon"/>
|
||||
<Button
|
||||
@click="
|
||||
go(
|
||||
icon.externalLibrary
|
||||
@@ -85,10 +93,10 @@ const Expand = createLucideIcon('Expand', expand);
|
||||
"
|
||||
>
|
||||
<component :is="Expand" />
|
||||
</IconButton>
|
||||
<IconButton @click="onClose">
|
||||
</Button>
|
||||
<Button @click="onClose">
|
||||
<component :is="CloseIcon" />
|
||||
</IconButton>
|
||||
</Button>
|
||||
</nav>
|
||||
<IconPreview
|
||||
id="previewer"
|
||||
@@ -200,7 +208,14 @@ const Expand = createLucideIcon('Expand', expand);
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.version.awaiting-release {
|
||||
color: var(--vp-c-danger-3);
|
||||
}
|
||||
|
||||
.contributors {
|
||||
justify-content: flex-end;
|
||||
position: absolute;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,30 +10,36 @@ import { computed } from 'vue';
|
||||
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
|
||||
import { diamond } from '../../../data/iconNodes';
|
||||
import deprecationReasonTemplate from '../../../../../tools/build-icons/utils/deprecationReasonTemplate.ts';
|
||||
import type { IconDeprecationReason } from '../../../../../tools/build-icons/types.ts';
|
||||
import IconTags from "./IconTags.vue"
|
||||
import getStudioLink from '../../utils/getStudioLink';
|
||||
|
||||
const props = defineProps<{
|
||||
icon: IconEntity;
|
||||
popoverPosition?: 'top' | 'bottom';
|
||||
showEditButton?: boolean;
|
||||
}>();
|
||||
|
||||
const { go } = useRouter();
|
||||
const { page } = useData();
|
||||
|
||||
const tags = computed(() => {
|
||||
if (!props.icon || !props?.icon?.tags) return [];
|
||||
return props.icon.tags.join(' • ');
|
||||
});
|
||||
|
||||
const DiamondIcon = createLucideIcon('Diamond', diamond);
|
||||
|
||||
const deprecatedTitle = computed(() => {
|
||||
if (!props.icon.deprecationReason) return '';
|
||||
return deprecationReasonTemplate(props.icon.deprecationReason, {
|
||||
return deprecationReasonTemplate(props.icon.deprecationReason as IconDeprecationReason, {
|
||||
componentName: props.icon.name,
|
||||
iconName: props.icon.name,
|
||||
toBeRemovedInVersion: props.icon.toBeRemovedInVersion,
|
||||
});
|
||||
});
|
||||
|
||||
const iconPath = computed(() => {
|
||||
if (props.icon.externalLibrary) {
|
||||
return `icons/${props.icon.externalLibrary}/${props.icon.name}`;
|
||||
}
|
||||
|
||||
return `icons/${props.icon.name}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -60,14 +66,7 @@ const deprecatedTitle = computed(() => {
|
||||
Deprecated
|
||||
</Badge>
|
||||
</div>
|
||||
<div
|
||||
class="tags-scroller"
|
||||
v-if="tags.length"
|
||||
>
|
||||
<p class="icon-tags horizontal-scroller">
|
||||
{{ tags }}
|
||||
</p>
|
||||
</div>
|
||||
<IconTags :tags="props.icon.tags" />
|
||||
<div class="group">
|
||||
<Badge
|
||||
v-for="category in icon.categories"
|
||||
@@ -80,26 +79,10 @@ const deprecatedTitle = computed(() => {
|
||||
|
||||
<div class="group buttons">
|
||||
<VPButton
|
||||
v-if="
|
||||
!page?.relativePath?.startsWith?.(
|
||||
icon.externalLibrary
|
||||
? `icons/${icon.externalLibrary}/${icon.name}`
|
||||
: `icons/${icon.name}`,
|
||||
)
|
||||
"
|
||||
:href="
|
||||
icon.externalLibrary
|
||||
? `/icons/${icon.externalLibrary}/${icon.name}`
|
||||
: `/icons/${icon.name}`
|
||||
"
|
||||
v-if="!page?.relativePath?.startsWith?.(iconPath)"
|
||||
:href="iconPath"
|
||||
text="See in action"
|
||||
@click="
|
||||
go(
|
||||
icon.externalLibrary
|
||||
? `/icons/${icon.externalLibrary}/${icon.name}`
|
||||
: `/icons/${icon.name}`,
|
||||
)
|
||||
"
|
||||
@click="go(iconPath)"
|
||||
/>
|
||||
<CopySVGButton
|
||||
:name="icon.name"
|
||||
@@ -115,6 +98,38 @@ const deprecatedTitle = computed(() => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.studio-edit-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 0 14px;
|
||||
min-height: 40px;
|
||||
border: 1px solid var(--vp-button-alt-border);
|
||||
border-radius: 999px;
|
||||
background-color: var(--vp-button-alt-bg);
|
||||
color: var(--vp-button-alt-text);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
border-color 0.2s,
|
||||
color 0.2s,
|
||||
background-color 0.2s,
|
||||
transform 0.2s;
|
||||
margin-left: 0;
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
.studio-edit-button:hover,
|
||||
.studio-edit-button:focus-visible {
|
||||
border-color: var(--vp-button-alt-hover-border);
|
||||
color: var(--vp-button-alt-hover-text);
|
||||
background-color: var(--vp-button-alt-hover-bg);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -156,66 +171,6 @@ const deprecatedTitle = computed(() => {
|
||||
background-color: var(--vp-c-brand-2);
|
||||
}
|
||||
|
||||
.icon-tags {
|
||||
font-size: 16px;
|
||||
color: var(--vp-c-text-2);
|
||||
font-weight: 500;
|
||||
line-height: 28px;
|
||||
white-space: nowrap;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tags-scroller {
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
height: 28px;
|
||||
padding: 8px 0 16px;
|
||||
margin-bottom: 16px;
|
||||
margin-top: 8px;
|
||||
align-items: center;
|
||||
|
||||
--gradient-background: var(--tags-gradient-background, var(--vp-c-bg-elv));
|
||||
}
|
||||
.horizontal-scroller {
|
||||
overflow-x: scroll;
|
||||
/* Hide Scrollbar */
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
scrollbar-width: thin; /* can also be normal, or none, to not render scrollbar */
|
||||
scrollbar-color: var(--vp-c-text-4) transparent; /* foreground background */
|
||||
}
|
||||
.horizontal-scroller::-webkit-scrollbar {
|
||||
width: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.horizontal-scroller::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.horizontal-scroller::-webkit-scrollbar-thumb {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.tags-scroller::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 32px;
|
||||
height: 100%;
|
||||
/* Background Gradient left to right */
|
||||
background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, var(--gradient-background) 100%);
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { IconEntity } from '../../types';
|
||||
import { computed, ref } from 'vue';
|
||||
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
|
||||
import { useIconStyleContext } from '../../composables/useIconStyle';
|
||||
import { squarePen } from '../../../data/iconNodes';
|
||||
|
||||
const props = defineProps<{
|
||||
name: IconEntity['name'];
|
||||
@@ -11,6 +10,8 @@ const props = defineProps<{
|
||||
customizable?: boolean;
|
||||
}>();
|
||||
|
||||
const activeSize = ref('1000%');
|
||||
|
||||
const { size, color, strokeWidth, absoluteStrokeWidth } = useIconStyleContext();
|
||||
const previewIcon = ref();
|
||||
|
||||
@@ -20,31 +21,6 @@ const iconComponent = computed(() => {
|
||||
if (!props.name || !props.iconNode) return null;
|
||||
return createLucideIcon(props.name, props.iconNode);
|
||||
});
|
||||
|
||||
const SquarePenIcon = createLucideIcon('SquarePen', squarePen);
|
||||
|
||||
const innerSvg = computed(() => {
|
||||
if (!props.iconNode) return '';
|
||||
|
||||
const string = props.iconNode
|
||||
.map(([tag, attrs]) => {
|
||||
const attributes = Object.entries(attrs)
|
||||
.map(([key, value]) => `${key}="${value}"`)
|
||||
.join(' ');
|
||||
return `<${tag} ${attributes}/>`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
return string
|
||||
.replace(/>[\r\n ]+</g, '><')
|
||||
.replace(/(<.*?>)|\s+/g, (m, $1) => $1 || ' ')
|
||||
.trim();
|
||||
});
|
||||
|
||||
const base64InnerSvg = computed(() => {
|
||||
if (typeof window === 'undefined') return '';
|
||||
return btoa(innerSvg.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -55,6 +31,8 @@ const base64InnerSvg = computed(() => {
|
||||
:size="size"
|
||||
:color="color"
|
||||
:strokeWidth="absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth"
|
||||
class="preview-icon"
|
||||
:data-size="activeSize"
|
||||
/>
|
||||
<svg
|
||||
class="icon-grid"
|
||||
@@ -62,6 +40,7 @@ const base64InnerSvg = computed(() => {
|
||||
fill="none"
|
||||
stroke-width="0.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
:class="{ show: activeSize === '1000%' }"
|
||||
>
|
||||
<g
|
||||
:key="`grid-${i}`"
|
||||
@@ -83,16 +62,17 @@ const base64InnerSvg = computed(() => {
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<a
|
||||
:href="`https://studio.lucide.dev/edit?value=${encodeURIComponent(base64InnerSvg)}&name=${name}&utm_source=lucide.dev&utm_medium=icon-detail-preview`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="studio-button"
|
||||
title="Open in Lucide Studio"
|
||||
aria-label="Open in Lucide Studio"
|
||||
>
|
||||
<SquarePenIcon :size="14" />
|
||||
</a>
|
||||
<div class="size-buttons" :class="{ show: activeSize !== '1000%' }">
|
||||
<button class="size-button" @click="activeSize = '1000%'" :class="{ active: activeSize === '1000%' }">
|
||||
1000%
|
||||
</button>
|
||||
<button class="size-button" @click="activeSize = '200%'" :class="{ active: activeSize === '200%' }">
|
||||
200%
|
||||
</button>
|
||||
<button class="size-button" @click="activeSize = '100%'" :class="{ active: activeSize === '100%' }">
|
||||
100%
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -104,56 +84,87 @@ const base64InnerSvg = computed(() => {
|
||||
top: 0;
|
||||
left: 0;
|
||||
stroke: var(--vp-c-divider);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.icon-grid.show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
height: 100%;
|
||||
aspect-ratio: 1/1;
|
||||
position: relative;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.icon-container > :deep(svg:not(.icon-grid)) {
|
||||
|
||||
.icon-container:hover .size-buttons {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.icon-container > :deep(svg.preview-icon) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
color: var(--vp-c-neutral);
|
||||
opacity: 0.8;
|
||||
transition: all 0.3s;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.studio-button {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--vp-c-text-2);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s, color 0.2s, background-color 0.2s;
|
||||
z-index: 2;
|
||||
.icon-container > :deep(svg.preview-icon[data-size='1000%']) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.icon-container:hover .studio-button,
|
||||
.studio-button:focus-visible {
|
||||
opacity: 1;
|
||||
.icon-container > :deep(svg.preview-icon[data-size='200%']) {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.studio-button:hover,
|
||||
.studio-button:focus-visible {
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-1);
|
||||
.icon-container > :deep(svg.preview-icon[data-size='100%']) {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.icon-component.customizable {
|
||||
will-change: width, height, stroke-width, stroke;
|
||||
/* color: var(--customize-color, currentColor);
|
||||
stroke-width: var(--customize-strokeWidth, 2); */
|
||||
}
|
||||
|
||||
.size-buttons {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
left: 50%;
|
||||
padding: 4px;
|
||||
border-radius: 12px;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
z-index: 99;
|
||||
background: var(--vp-code-tab-bg);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.size-buttons.show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.size-button {
|
||||
color: var(--vp-code-tab-text-color);
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
.size-button.active {
|
||||
background: var(--vp-c-neutral-inverse);
|
||||
box-shadow: var(--vp-shadow-1);
|
||||
border-radius: 4px;
|
||||
color: var(--vp-code-tab-active-text-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -70,7 +70,6 @@ const prettyName = props.name
|
||||
class="showcase"
|
||||
v-if="iconComponent"
|
||||
>
|
||||
<h2 class="title">See this icon in action</h2>
|
||||
<div class="showcase-grid">
|
||||
<div class="showcase-item column">
|
||||
<div class="placeholder"></div>
|
||||
@@ -236,18 +235,17 @@ const prettyName = props.name
|
||||
.showcase-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 48px;
|
||||
margin-inline-start: 24px;
|
||||
margin-block-start: 48px;
|
||||
gap: 32px;
|
||||
background: var(--vp-code-block-bg);
|
||||
padding: 32px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.showcase-item {
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
background-color: var(--vp-c-bg);
|
||||
box-shadow:
|
||||
var(--vp-shadow-4),
|
||||
-24px -24px 0 var(--vp-c-bg-soft);
|
||||
box-shadow: var(--vp-shadow-2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
const MAX_VISIBLE_TAGS = 5;
|
||||
|
||||
const props = defineProps<{
|
||||
tags?: string[];
|
||||
}>();
|
||||
|
||||
const tags = computed(() => props.tags ?? []);
|
||||
const expanded = ref(false);
|
||||
const hiddenCount = computed(() => Math.max(tags.value.length - MAX_VISIBLE_TAGS, 0));
|
||||
const showAll = computed(() => expanded.value || hiddenCount.value === 0);
|
||||
|
||||
// The overlay reuses this component when switching icons, so collapse again
|
||||
watch(tags, () => {
|
||||
expanded.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul
|
||||
v-if="tags.length"
|
||||
class="icon-tags"
|
||||
>
|
||||
<!-- v-show keeps hidden tags in the rendered HTML so crawlers can index them -->
|
||||
<li
|
||||
v-for="(tag, index) in tags"
|
||||
v-show="showAll || index < MAX_VISIBLE_TAGS"
|
||||
:key="tag"
|
||||
class="tag"
|
||||
>
|
||||
{{ tag }}
|
||||
</li>
|
||||
<li
|
||||
v-if="!showAll"
|
||||
class="more"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="more-button"
|
||||
:aria-label="`Show ${hiddenCount} more tags`"
|
||||
@click="expanded = true"
|
||||
>
|
||||
+{{ hiddenCount }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.icon-tags {
|
||||
font-size: 16px;
|
||||
color: var(--vp-c-text-2);
|
||||
font-weight: 500;
|
||||
line-height: 28px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
list-style-type: disc;
|
||||
gap: 4px 8px;
|
||||
/* Clip bullets hanging before tags that start a wrapped line, padding leaves room for the focus ring */
|
||||
overflow: hidden;
|
||||
padding: 4px;
|
||||
margin: 4px -4px 12px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
margin-top: 0;
|
||||
padding-left: 0px;
|
||||
padding-right: 12px;
|
||||
list-style-position: outside;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tag:first-child {
|
||||
list-style: none;
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.more {
|
||||
margin-top: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.more-button {
|
||||
font: inherit;
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 12px;
|
||||
padding: 0 8px;
|
||||
margin-left: -4px;
|
||||
border-radius: 16px;
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
transition:
|
||||
color 0.25s,
|
||||
background-color 0.25s;
|
||||
}
|
||||
|
||||
.more-button:hover,
|
||||
.more-button:focus-visible {
|
||||
color: var(--vp-c-text-1);
|
||||
background-color: var(--vp-c-default-soft);
|
||||
}
|
||||
|
||||
.more-button:focus-visible {
|
||||
outline: 2px solid var(--vp-c-brand-1);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
@@ -163,7 +163,7 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
|
||||
function handleCloseDrawer() {
|
||||
setActiveIconName('');
|
||||
|
||||
const url = new URL(window.location);
|
||||
const url = new URL(window.location.href);
|
||||
url.pathname = '/icons/categories';
|
||||
|
||||
if (searchQueryDebounced.value) {
|
||||
|
||||
@@ -35,7 +35,6 @@ const SORTING = [
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
const initialGridItems = computed(() => {
|
||||
if (containerWidth.value === 0) return 120;
|
||||
|
||||
@@ -72,6 +71,8 @@ const sortedIcons = computed(() => {
|
||||
return [...props.icons].sort((a, b) => (b.popularity || 0) - (a.popularity || 0));
|
||||
case 'release-date':
|
||||
return [...props.icons].sort((a, b) => {
|
||||
if (a.awaitingRelease !== b.awaitingRelease) return a.awaitingRelease ? -1 : 1;
|
||||
|
||||
const aDate = a.createdRelease?.date ? new Date(a.createdRelease.date).getTime() : 0;
|
||||
const bDate = b.createdRelease?.date ? new Date(b.createdRelease.date).getTime() : 0;
|
||||
return bDate - aDate;
|
||||
@@ -172,7 +173,7 @@ watch(searchQueryDebounced, () => {
|
||||
function handleCloseDrawer() {
|
||||
setActiveIconName('');
|
||||
|
||||
const url = new URL(window.location);
|
||||
const url = new URL(window.location.href);
|
||||
url.pathname = '/icons/';
|
||||
|
||||
if (searchQueryDebounced.value) {
|
||||
@@ -207,6 +208,7 @@ function handleCloseDrawer() {
|
||||
<template #start-icon>
|
||||
<Icon
|
||||
:iconNode="listSortDescending"
|
||||
name="list-sort-descending"
|
||||
class="chevron-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
@@ -39,7 +39,7 @@ interface Placeholder {
|
||||
finePrint?: string;
|
||||
}
|
||||
|
||||
const brandPlaceholders: Placeholder[] = shallowReadonly([
|
||||
const brandPlaceholders: readonly Placeholder[] = shallowReadonly([
|
||||
{
|
||||
title: 'Boooo! What a scary brand logo!',
|
||||
message:
|
||||
@@ -103,7 +103,7 @@ const brandPlaceholders: Placeholder[] = shallowReadonly([
|
||||
},
|
||||
]);
|
||||
|
||||
const notFoundPlaceholders: Omit<Placeholder, 'title'>[] = shallowReadonly([
|
||||
const notFoundPlaceholders: readonly Omit<Placeholder, 'title'>[] = shallowReadonly([
|
||||
{
|
||||
message: 'We’ve looked for this icon for a bird’s eye view, but could not find it.',
|
||||
icon: markRaw(bird),
|
||||
@@ -130,7 +130,7 @@ const notFoundPlaceholders: Omit<Placeholder, 'title'>[] = shallowReadonly([
|
||||
},
|
||||
]);
|
||||
|
||||
function randomItem<T>(arr: T[]): T {
|
||||
function randomItem<T>(arr: readonly T[]): T {
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ defineProps<{
|
||||
|
||||
<template>
|
||||
<section class="related-icons">
|
||||
<h2 class="title">More icons like this</h2>
|
||||
<IconGrid :icons="icons" />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -34,8 +34,8 @@ const sizeCssVar = useCssVar('--customize-size', props.rootEl?.value ?? document
|
||||
});
|
||||
|
||||
syncRef(color, colorCssVar, { direction: 'ltr' });
|
||||
syncRef(strokeWidth, strokeWidthCssVar, { direction: 'ltr' });
|
||||
syncRef(size, sizeCssVar, { direction: 'ltr' });
|
||||
syncRef(strokeWidth, strokeWidthCssVar, { direction: 'ltr', transform: { ltr: String } });
|
||||
syncRef(size, sizeCssVar, { direction: 'ltr', transform: { ltr: String } });
|
||||
|
||||
function resetStyle() {
|
||||
color.value = STYLE_DEFAULTS.color;
|
||||
|
||||
@@ -61,6 +61,50 @@
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-button) {
|
||||
padding: 11px 14px;
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
color: var(--vp-c-text-2);
|
||||
flex-shrink: 0;
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-button):hover,
|
||||
.search-bar :deep(.select-button):focus {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-options) {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-option) {
|
||||
padding-right: 36px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-icon) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-button) .select-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.search-bar :deep(.select-button) {
|
||||
padding: 11px 16px 11px 16px;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-button) .select-text {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.search-bar {
|
||||
padding-top: 32px;
|
||||
@@ -68,4 +112,7 @@
|
||||
top: calc(var(--vp-layout-top-height, 0px) + 64px);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import VPHero from 'vitepress/dist/client/theme-default/components/VPHero.vue'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data.js'
|
||||
import FakeInput from '../base/FakeInput.vue';
|
||||
import { useRouter } from 'vitepress';
|
||||
import { data } from '../home/HomeHeroIconsCard.data'
|
||||
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
sparkles,
|
||||
} from '../../../data/iconNodes';
|
||||
import createElement from 'lucide/src/createElement';
|
||||
import { IconNode } from 'lucide';
|
||||
import { IconNode } from 'lucide/src/types';
|
||||
|
||||
const icons: Component[] = [
|
||||
heart, star, zap, code, feather, cloud, sun, moon, camera, music,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useData } from 'vitepress';
|
||||
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar';
|
||||
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar.js';
|
||||
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue';
|
||||
import { computed } from 'vue';
|
||||
import SurveyAskOverlay from '../SurveyAskOverlay.vue';
|
||||
|
||||
@@ -2,7 +2,7 @@ import packageDataList from '../../../data/packageData.json';
|
||||
import thirdPartyPackages from '../../../data/packageData.thirdParty.json';
|
||||
import fetchPackages from '../../../lib/fetchPackages';
|
||||
|
||||
export default {
|
||||
const loader = {
|
||||
async load() {
|
||||
const packageJsonList = await fetchPackages();
|
||||
|
||||
@@ -29,3 +29,8 @@ export default {
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
declare const data: Awaited<ReturnType<typeof loader.load>>;
|
||||
|
||||
export { data };
|
||||
export default loader;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { onMounted, onUpdated, onUnmounted } from 'vue';
|
||||
import { throttleAndDebounce } from 'vitepress/dist/client/theme-default/support/utils';
|
||||
import { throttleAndDebounce } from 'vitepress/dist/client/theme-default/support/utils.js';
|
||||
|
||||
/*
|
||||
* This file is compied and adjusted from vitepress/dist/client/theme-default/composables/useActiveAnchor.ts
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Fuse from 'fuse.js';
|
||||
import Fuse, { type FuseOptionKeyObject } from 'fuse.js';
|
||||
import { shallowRef, computed, Ref } from 'vue';
|
||||
|
||||
const useSearch = <T>(
|
||||
query: Ref<string>,
|
||||
collection: Ref<T[]>,
|
||||
keys: Fuse.FuseOptionKeyObject<T>[] = [],
|
||||
keys: FuseOptionKeyObject<T>[] = [],
|
||||
) => {
|
||||
const index = shallowRef(
|
||||
new Fuse(collection.value, {
|
||||
|
||||
@@ -152,6 +152,10 @@ html {
|
||||
.VPHomeHero .container {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.VPDoc:has(.vp-doc[class*=' _icons_']) {
|
||||
padding: 32px 32px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.VPNavBarHamburger .container > span {
|
||||
@@ -286,6 +290,10 @@ html {
|
||||
--example-guidance-label-bg: var(--vp-c-danger-1);
|
||||
}
|
||||
|
||||
.example-guidance--caution {
|
||||
--example-guidance-label-bg: var(--vp-c-warning-1);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.example-block {
|
||||
display: grid;
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
export type IconNode = [elementName: string, attrs: Record<string, string>][];
|
||||
export type IconNodeWithKeys = [elementName: string, attrs: Record<string, string>, key: string][];
|
||||
|
||||
export interface Alias {
|
||||
name: string;
|
||||
deprecationReason: string;
|
||||
deprecated: boolean;
|
||||
}
|
||||
|
||||
export interface IconMetaData {
|
||||
tags: string[];
|
||||
categories: string[];
|
||||
contributors: string[];
|
||||
aliases?: string[];
|
||||
aliases?: Alias[];
|
||||
deprecated?: boolean;
|
||||
deprecationReason?: string;
|
||||
toBeRemovedInVersion?: string;
|
||||
@@ -18,6 +24,8 @@ export interface IconEntity extends IconMetaData {
|
||||
externalLibrary?: ExternalLibs;
|
||||
createdRelease?: Release;
|
||||
changedRelease?: Release;
|
||||
fromFork?: boolean;
|
||||
awaitingRelease?: boolean;
|
||||
popularity?: number;
|
||||
}
|
||||
|
||||
@@ -43,7 +51,8 @@ export interface PackageItem {
|
||||
packageDirname?: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
iconDark: string;
|
||||
iconDark?: string;
|
||||
iconClass?: string;
|
||||
shields: Shield[];
|
||||
source: string;
|
||||
documentation: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const chunkArray = <ArrayType>(stream: ArrayType, size: number) => {
|
||||
return stream.reduce<ArrayType[][]>(
|
||||
const chunkArray = <ItemType>(stream: ItemType[], size: number) => {
|
||||
return stream.reduce<ItemType[][]>(
|
||||
(chunks, item, idx, arr) =>
|
||||
idx % size == 0 ? [...chunks, arr.slice(idx, idx + size)] : chunks,
|
||||
[],
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { IconNode } from '../types';
|
||||
|
||||
function getInnerSvg(iconNode: IconNode) {
|
||||
return iconNode
|
||||
.map(([tag, attrs]) => {
|
||||
const attributes = Object.entries(attrs)
|
||||
.map(([key, value]) => `${key}="${value}"`)
|
||||
.join(' ');
|
||||
return `<${tag} ${attributes}/>`;
|
||||
})
|
||||
.join('')
|
||||
.replace(/>[\r\n ]+</g, '><')
|
||||
.replace(/(<.*?>)|\s+/g, (m, $1) => $1 || ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
export default function getStudioLink(
|
||||
name: string,
|
||||
iconNode: IconNode,
|
||||
utmMedium = 'icon-detail-preview',
|
||||
) {
|
||||
const base64InnerSvg = btoa(getInnerSvg(iconNode ?? []));
|
||||
|
||||
return `https://studio.lucide.dev/edit?value=${encodeURIComponent(base64InnerSvg)}&name=${name}&utm_source=lucide.dev&utm_medium=${utmMedium}`;
|
||||
}
|
||||
Vendored
+5
@@ -1,3 +1,4 @@
|
||||
/// <reference types="vitepress/client" />
|
||||
import { type IconNode } from '@lucide/vue/src/types';
|
||||
import Vue from 'vue';
|
||||
|
||||
@@ -31,7 +32,11 @@ declare module '*.node.json' {
|
||||
}
|
||||
|
||||
declare global {
|
||||
// Defined by VitePress at build time, used by its VPDocAsideCarbonAds component
|
||||
const __CARBON__: boolean;
|
||||
|
||||
interface Window {
|
||||
_carbonads?: unknown;
|
||||
ExpoSnack?: {
|
||||
/**
|
||||
* Initialize all snack players on the page
|
||||
|
||||
@@ -17,7 +17,7 @@ This guide explains how to design and prepare icons for Lucide.
|
||||
## What this guide covers
|
||||
|
||||
<OverviewLinkGrid>
|
||||
<OverviewLink v-for="item in resourcesSidebar[2].items.slice(1, -1)" :key="item.link" :href="item.link" :title="item.text" :desc="item.desc"/>
|
||||
<OverviewLink v-for="item in resourcesSidebar[3].items.slice(1, -1)" :key="item.link" :href="item.link" :title="item.text" :desc="item.desc"/>
|
||||
</OverviewLinkGrid>
|
||||
|
||||
## Use your preferred design tool
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
---
|
||||
title: Accessibility
|
||||
description: Learn how to make your icons accessible to all users, including those with disabilities.
|
||||
---
|
||||
|
||||
# Accessibility in depth
|
||||
|
||||
Icons are pictures that show what something means without using words. They can be very helpful
|
||||
because they can quickly give information.
|
||||
|
||||
However, not everyone can understand them easily. When using icons it is very important to consider
|
||||
the following aspects of accessibility.
|
||||
|
||||
::: info
|
||||
By default, we hide icons from screen readers using `aria-hidden="true"`.
|
||||
You can make them accessible yourself by following the guidelines below.
|
||||
:::
|
||||
|
||||
## Provide visible labels
|
||||
|
||||
Icons are a helpful tool to improve perception, but they aren't a replacement for text.
|
||||
|
||||
In most cases, it is probably a good idea to also provide a textual representation of your icon's
|
||||
function.
|
||||
|
||||
<!--@include: ../../docs/images/a11y/visible-labels.svg -->
|
||||
|
||||
## Contrast
|
||||
|
||||
Ensure there's enough contrast between the icon and its background so that it's visible to people
|
||||
with low vision or color vision deficiencies.
|
||||
|
||||
We recommend
|
||||
following [WCAG 2.1 SC 1.4.3](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html).
|
||||
|
||||
<!--@include: ../../docs/images/a11y/contrast.svg -->
|
||||
|
||||
## Use of color
|
||||
|
||||
Avoid relying solely on color to convey meaning in icons, as some users may have color blindness.
|
||||
Instead, use additional visual cues like shape, shading or text.
|
||||
|
||||
<!--@include: ../../docs/images/a11y/use-of-color.svg -->
|
||||
|
||||
## Interactivity
|
||||
|
||||
Ensure that interactive icons are accessible via keyboard navigation and provide clear feedback when
|
||||
activated.
|
||||
|
||||
<!--@include: ../../docs/images/a11y/interactivity.svg -->
|
||||
|
||||
In most cases this is easily done by wrapping them in icon buttons.
|
||||
|
||||
## Minimum target size
|
||||
|
||||
Small targets can be difficult to click or touch, if your icon is interactive, we recommend that it
|
||||
should have a minimum target size of 44×44 pixels.
|
||||
|
||||
<!--@include: ../../docs/images/a11y/target-size.svg -->
|
||||
|
||||
In practice, this doesn't necessarily mean that the icon itself should be this large, only its
|
||||
interactive wrapper element.
|
||||
|
||||
## Meaningfulness
|
||||
|
||||
Icons should represent concepts or actions in a universally understandable way. Avoid using abstract
|
||||
or ambiguous, or culture-specific symbols that might confuse some users.
|
||||
|
||||
<!--@include: ../../docs/images/a11y/meaningfulness.svg -->
|
||||
|
||||
## Consistency
|
||||
|
||||
Maintain consistency in icon design and usage across your interface to help users learn and
|
||||
understand their meanings more easily.
|
||||
|
||||
<!--@include: ../../docs/images/a11y/consistency.svg -->
|
||||
|
||||
## Text Alternatives
|
||||
|
||||
You may have to provide text labels or alternative text descriptions for icons, especially for
|
||||
screen readers used by people with visual impairments.
|
||||
|
||||
However: descriptions should only be provided to standalone icons that aren't purely decorative, as
|
||||
providing accessible names to non-functional elements only increases clutter when using screen
|
||||
readers.
|
||||
|
||||
### On standalone icons
|
||||
|
||||
Icons are usually very unlikely to stand on their own with no semantically meaningful wrapper
|
||||
element. In most cases they will be part of a badge, button (including icon buttons), navigation
|
||||
item or other interactive UI element.
|
||||
|
||||
::: warning
|
||||
In case some of your icons stand alone, and they serve a non-decorative function, make sure to
|
||||
provide the appropriate accessible label for them.
|
||||
:::
|
||||
|
||||
<!--@include: ../../docs/images/a11y/alttext-standalone.svg -->
|
||||
|
||||
In general try to avoid using functional icons with no interactivity, we recommend that:
|
||||
|
||||
1) you either add a visible description next to them, or
|
||||
2) place them in badges, labels or on buttons, and at least add a tooltip to them.
|
||||
|
||||
In any such case, it is preferred that the accessible name be provided for these interactive
|
||||
elements (badges, buttons, nav items etc.) only, _not_ the icons themselves.
|
||||
|
||||
### On buttons
|
||||
|
||||
Do not provide an accessible label to icons when used on a button, as this label will be read out by
|
||||
screen readers, leading to nonsensical text.
|
||||
|
||||
<!--@include: ../../docs/images/a11y/alttext-buttons.svg -->
|
||||
|
||||
|
||||
::: details Code examples
|
||||
|
||||
```tsx
|
||||
// Don't do this
|
||||
<button>
|
||||
<Plus aria-label="Plus icon"/>
|
||||
Add document
|
||||
</button>
|
||||
|
||||
// Do this, just leave it
|
||||
<button>
|
||||
<Plus/>
|
||||
Add document
|
||||
</button>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### On icon buttons
|
||||
|
||||
Icon buttons are buttons that do not contain any visible text apart from the icon itself (think of
|
||||
the close button of a dialog for example).
|
||||
|
||||
As previously stated, you should provide your accessible label on the icon button itself, not the
|
||||
contained icon.
|
||||
|
||||
<!--@include: ../../docs/images/a11y/alttext-iconbuttons.svg -->
|
||||
|
||||
::: details Code examples
|
||||
|
||||
```tsx
|
||||
// Don't do this
|
||||
<button class="btn-icon">
|
||||
<House/>
|
||||
</button>
|
||||
|
||||
// Don't do this either
|
||||
<button class="btn-icon">
|
||||
<House aria-label="Home icon"/>
|
||||
</button>
|
||||
|
||||
// This works but might not be the best solution, see below
|
||||
<button aria-label="Go to home" class="btn-icon">
|
||||
<House/>
|
||||
</button>
|
||||
|
||||
// Do this instead
|
||||
<button class="btn-icon">
|
||||
<House/>
|
||||
<span class="visually-hidden">Go to home</span>
|
||||
</button>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## A note on `aria-label`
|
||||
|
||||
Although you could provide accessible labels to your elements via the `aria-label` attribute, we
|
||||
generally recommend avoiding this and instead suggest that you use your chosen CSS framework's "
|
||||
visually hidden" utility whenever possible. You can
|
||||
[read more about why `aria-label` might not be the best solution](https://gomakethings.com/revisting-aria-label-versus-a-visually-hidden-class/).
|
||||
|
||||
### Example - Radix UI
|
||||
|
||||
Use [Radix UI's built-in accessible icon utility component](https://www.radix-ui.com/primitives/docs/utilities/accessible-icon).
|
||||
|
||||
```tsx
|
||||
import { ArrowRightIcon } from 'lucide-react';
|
||||
import { AccessibleIcon } from '@radix-ui/react-accessible-icon';
|
||||
|
||||
<AccessibleIcon label="Next item">
|
||||
<ArrowRightIcon />
|
||||
</AccessibleIcon>
|
||||
```
|
||||
|
||||
### Example - Bootstrap
|
||||
|
||||
```html
|
||||
|
||||
<div>
|
||||
<i data-lucide="phone" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">Phone number</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Example - Tailwind CSS
|
||||
|
||||
```html
|
||||
|
||||
<div>
|
||||
<i data-lucide="phone" aria-hidden="true"></i>
|
||||
<span class="sr-only">Phone number</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
If you're not sure, you may consider learning more
|
||||
about [how to hide content.](https://www.a11yproject.com/posts/how-to-hide-content/)
|
||||
|
||||
## Further resources
|
||||
|
||||
We also recommend checking out the following resources about accessibility:
|
||||
|
||||
- [Web Content Accessibility Guidelines (WCAG) 2.1](https://www.w3.org/TR/WCAG21/)
|
||||
- [Web Accessibility Initiative (WAI)](https://www.w3.org/WAI/)
|
||||
- [Learn accessibility on web.dev](https://web.dev/learn/accessibility)
|
||||
- [Inclusive Components](https://inclusive-components.design/)
|
||||
- [A11yTalks](https://www.a11ytalks.com/)
|
||||
- [A11y automation tracker](https://a11y-automation.dev/)
|
||||
- [The A11Y Project](https://www.a11yproject.com/)
|
||||
|
||||
<style>
|
||||
svg.a11y-example {
|
||||
max-width: calc(100% + 48px);
|
||||
margin: 0 -24px;
|
||||
}
|
||||
@media (min-width: 480px) {
|
||||
svg.a11y-example {
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -17,7 +17,7 @@ Most of the time, icons are used purely for decoration or visual reinforcement.
|
||||
|
||||
For a broader explanation of this, and other best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility:
|
||||
|
||||
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
|
||||
<OverviewLink href="/how-to/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
|
||||
|
||||
Only if an icon **conveys essential meaning on its own** should it be made accessible. The sections below explain how to do that in Angular.
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ Most of the time, icons are used purely for decoration or visual reinforcement.
|
||||
|
||||
For a broader explanation of this, and other best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility:
|
||||
|
||||
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
|
||||
<OverviewLink href="/how-to/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
|
||||
|
||||
Only if an icon **conveys essential meaning on its own** should it be made accessible. The sections below explain how to do that in Astro.
|
||||
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ In addition to design, code is also important. Assets like icons can significant
|
||||
|
||||
Icons are pictures that show what something means without using words. They can be very helpful because they can quickly give information.
|
||||
|
||||
However, not everyone can understand them easily. Read more about [how to use Lucide in an accessible way](./accessibility.md).
|
||||
However, not everyone can understand them easily. Read more about [how to use Lucide in an accessible way](../how-to/accessibility.md).
|
||||
|
||||
## Official Packages
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Most of the time, icons are used purely for decoration or visual reinforcement.
|
||||
|
||||
For a broader explanation of this, and other best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility:
|
||||
|
||||
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
|
||||
<OverviewLink href="/how-to/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
|
||||
|
||||
Only if an icon **conveys essential meaning on its own** should it be made accessible. The sections below explain how to do that in your app.
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ Most of the time, icons are used purely for decoration or visual reinforcement.
|
||||
|
||||
For a broader explanation of this, and other best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility:
|
||||
|
||||
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
|
||||
<OverviewLink href="/how-to/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
|
||||
|
||||
Only if an icon **conveys essential meaning on its own** should it be made accessible. The sections below explain how to do that in Preact.
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Most of the time, icons are used purely for decoration or visual reinforcement.
|
||||
|
||||
For a broader explanation of this, and other best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility:
|
||||
|
||||
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
|
||||
<OverviewLink href="/how-to/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
|
||||
|
||||
Only if an icon **conveys essential meaning on its own** should it be made accessible. The sections below explain how to do that in React.
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Most of the time, icons are used purely for decoration or visual reinforcement.
|
||||
|
||||
For a broader explanation of this, and other best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility:
|
||||
|
||||
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
|
||||
<OverviewLink href="/how-to/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
|
||||
|
||||
Only if an icon **conveys essential meaning on its own** should it be made accessible. The sections below explain how to do that in Solid.
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ Most of the time, icons are used purely for decoration or visual reinforcement.
|
||||
|
||||
For a broader explanation of this, and other best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility:
|
||||
|
||||
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
|
||||
<OverviewLink href="/how-to/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
|
||||
|
||||
Only if an icon **conveys essential meaning on its own** should it be made accessible. The sections below explain how to do that in Svelte.
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ Also, a `llms.txt` is now available for LLMs to use.
|
||||
|
||||
We have improved the accessibility of our icons by setting `aria-hidden` to `true` by default. This change ensures that screen readers will ignore icons that are purely decorative, improving the overall accessibility of your applications. If you need to make an icon accessible, you can provide an appropriate `aria-label` or add a `title` attribute to the icon element.
|
||||
|
||||
See our [accessibility in-depth guide](/guide/accessibility) for more details and best practices on making your icons accessible.
|
||||
See our [accessibility in-depth guide](/how-to/accessibility) for more details and best practices on making your icons accessible.
|
||||
|
||||
## Removed UMD build, only ESM and CJS now
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ Most of the time, icons are used purely for decoration or visual reinforcement.
|
||||
|
||||
For a broader explanation of this, and other best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility:
|
||||
|
||||
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
|
||||
<OverviewLink href="/how-to/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>
|
||||
|
||||
Only if an icon **conveys essential meaning on its own** should it be made accessible. The sections below explain how to do that in Vue.
|
||||
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
---
|
||||
title: Accessibility
|
||||
description: Learn how to make your icons accessible to all users, including those with disabilities.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
menu,
|
||||
pawPrint,
|
||||
shirt,
|
||||
umbrella,
|
||||
userRoundCog,
|
||||
} from '~/.vitepress/data/iconNodes';
|
||||
import LucideIcon from '~/.vitepress/theme/components/base/LucideIcon.vue';
|
||||
</script>
|
||||
|
||||
# Accessibility in depth
|
||||
|
||||
Icons are pictures that convey meaning without using words.
|
||||
|
||||
They can help people scan an interface, recognize actions, and understand status quickly.
|
||||
|
||||
Not everyone understands icons easily. Use the following accessibility rules when you add icons to an interface.
|
||||
|
||||
::: tip
|
||||
Lucide icons are hidden from screen readers by default with `aria-hidden="true"`.
|
||||
|
||||
Add accessible labels when icons carry meaning or act as controls.
|
||||
:::
|
||||
|
||||
## Provide visible labels
|
||||
|
||||
Icons can improve scanning, but they aren't a replacement for text.
|
||||
|
||||
Text helps people understand actions and navigation more reliably than icons alone.
|
||||
|
||||
:::: example
|
||||
::: do <span aria-hidden="true">On this page <LucideIcon name="menu" :iconNode="menu" /></span>
|
||||
Provide a written description of your interactive elements.
|
||||
:::
|
||||
::: dont <LucideIcon name="menu" :iconNode="menu" />
|
||||
Don't rely on icons alone to communicate what elements do.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Provide enough contrast
|
||||
|
||||
Use enough contrast between an icon and its background. Low contrast makes icons harder to see for people with low vision or color vision deficiencies.
|
||||
|
||||
Follow [WCAG 2.1 SC 1.4.3](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html) when contrast requirements apply.
|
||||
|
||||
:::: example
|
||||
::: do ../images/a11y/contrast_do.svg?raw=true
|
||||
Use a contrast ratio of at least 4.5:1.
|
||||
:::
|
||||
::: dont ../images/a11y/contrast_dont.svg?raw=true
|
||||
Don't use low contrast.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Do not rely on color alone
|
||||
|
||||
Use non-color cues like shape, shading, or text to mark state, status, and validity. Color can support meaning, but it must not be the only cue.
|
||||
|
||||
:::: example
|
||||
::: do ../images/a11y/state_do.svg?raw=true
|
||||
Mark state with non-color visual cues too.
|
||||
:::
|
||||
::: dont ../images/a11y/state_dont.svg?raw=true
|
||||
Don't mark state only with color.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Make interactive icons focusable
|
||||
|
||||
Wrap interactive icons in semantic elements. Icons that perform actions need keyboard access and clear feedback when activated.
|
||||
|
||||
:::: example
|
||||
::: do ../images/a11y/interactive_icons_do.svg?raw=true
|
||||
Wrap interactive icons in semantic elements, such as buttons, so they are focusable.
|
||||
:::
|
||||
::: dont ../images/a11y/interactive_icons_dont.svg?raw=true
|
||||
Don't leave interactive icons unable to be focused.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Use a large enough target size
|
||||
|
||||
Use a large enough target size for interactive icon controls. Small targets can be difficult to click or tap.
|
||||
|
||||
:::: example
|
||||
::: do ../images/a11y/target_size_do.svg?raw=true
|
||||
Keep the target size at a minimum of 44 by 44 pixels.
|
||||
:::
|
||||
::: dont ../images/a11y/target_size_dont.svg?raw=true
|
||||
Don't make the target size smaller than 44 by 44 pixels.
|
||||
:::
|
||||
::::
|
||||
|
||||
The icon itself does not need to be 44 by 44 pixels. The interactive wrapper needs to meet the target size.
|
||||
|
||||
## Choose universal symbols when possible
|
||||
|
||||
Choose icons that are widely recognized for your use case. Avoid icons that only make sense to a narrow audience.
|
||||
|
||||
:::: example
|
||||
::: do <span aria-hidden="true"><LucideIcon name="shirt" :iconNode="shirt" /> Clothing</span>
|
||||
Pick icons that are universal symbols for your use case.
|
||||
:::
|
||||
::: dont <span aria-hidden="true"><LucideIcon name="umbrella" :iconNode="umbrella" /> Clothing</span>
|
||||
Don't pick icons that are not universally recognizable for your use case.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Avoid cultural or linguistic shortcuts
|
||||
|
||||
Choose icons with a clear connection to the concept. Puns, idioms, and culture-specific references can be difficult to understand or translate.
|
||||
|
||||
:::: example
|
||||
::: do <span aria-hidden="true"><LucideIcon name="user-round-cog" :iconNode="userRoundCog" /> Role bearers</span>
|
||||
Pick icons with a clear connection to the concept you want to represent.
|
||||
:::
|
||||
::: dont <span aria-hidden="true"><LucideIcon name="paw-print" :iconNode="pawPrint" /> Role bearers</span>
|
||||
Don't pick icons based on puns or other cultural or linguistic cues.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Use different icons for different meanings
|
||||
|
||||
Use different icons for different meanings. Reusing one icon for unrelated concepts makes the interface harder to learn.
|
||||
|
||||
:::: example
|
||||
::: do ../images/a11y/distinct_use_case_do.svg?raw=true
|
||||
Use different icons for distinct purposes or meanings.
|
||||
:::
|
||||
::: dont ../images/a11y/distinct_use_case_dont.svg?raw=true
|
||||
Don't use the same icon for multiple distinct purposes or meanings, especially in the same context.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Use the same icon for the same meaning
|
||||
|
||||
Use the same icon for the same meaning. Consistent icon use helps people recognize familiar actions across an interface.
|
||||
|
||||
:::: example
|
||||
::: do ../images/a11y/same_use_case_do.svg?raw=true
|
||||
Use the same icon for the same purpose or function.
|
||||
:::
|
||||
::: dont ../images/a11y/same_use_case_dont.svg?raw=true
|
||||
Don't use different icons for the same purpose or function.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Hide decorative icons from assistive technology
|
||||
|
||||
Keep decorative icons hidden from screen readers. Decorative icons repeat nearby text or add visual styling without adding meaning.
|
||||
|
||||
:::: example
|
||||
::: do ../images/a11y/decorative_icons_do.svg?raw=true
|
||||
Omit `aria-label` on decorative icons.
|
||||
:::
|
||||
::: dont ../images/a11y/decorative_icons_dont.svg?raw=true
|
||||
Don't provide accessible labels to decorative icons.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Label functional standalone icons carefully
|
||||
|
||||
Avoid standalone functional icons when possible. If an icon carries meaning without nearby text or an interactive wrapper, give that icon an accessible label.
|
||||
|
||||
:::: example
|
||||
::: do ../images/a11y/functional_icons_do.svg?raw=true
|
||||
Provide an accessible label for functional icons, but avoid standalone functional icons when possible.
|
||||
:::
|
||||
::: dont ../images/a11y/functional_icons_dont.svg?raw=true
|
||||
Don't provide an accessible label for purely decorative icons.
|
||||
:::
|
||||
::::
|
||||
|
||||
In many cases, add visible text, place the icon inside a badge or button, or add a tooltip to the meaningful wrapper instead.
|
||||
|
||||
## Label icon buttons on the button
|
||||
|
||||
Give icon buttons an accessible name. Put the name on the button or in visually hidden text inside the button, not on the icon.
|
||||
|
||||
:::: example
|
||||
::: do ../images/a11y/icon_button_do.svg?raw=true
|
||||
Provide the accessible label for icon buttons themselves.
|
||||
:::
|
||||
::: dont ../images/a11y/icon_button_dont.svg?raw=true
|
||||
Don't provide the accessible label for icons on icon buttons.
|
||||
:::
|
||||
::::
|
||||
|
||||
::: details Code examples
|
||||
|
||||
```tsx
|
||||
// Don't do this
|
||||
<button className="btn-icon">
|
||||
<House />
|
||||
</button>
|
||||
|
||||
// Don't do this either
|
||||
<button className="btn-icon">
|
||||
<House aria-label="Home icon" />
|
||||
</button>
|
||||
|
||||
// This works, but visually hidden text is usually more reliable
|
||||
<button aria-label="Go to home" className="btn-icon">
|
||||
<House />
|
||||
</button>
|
||||
|
||||
// Prefer this
|
||||
<button className="btn-icon">
|
||||
<House />
|
||||
<span className="visually-hidden">Go to home</span>
|
||||
</button>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## A note on `aria-label`
|
||||
|
||||
You can provide accessible labels with `aria-label`, but visually hidden text is often more reliable.
|
||||
|
||||
Read [why `aria-label` might not be the best solution](https://gomakethings.com/revisting-aria-label-versus-a-visually-hidden-class/) for more context.
|
||||
|
||||
### Radix UI
|
||||
|
||||
Use [Radix UI's built-in accessible icon utility component](https://www.radix-ui.com/primitives/docs/utilities/accessible-icon).
|
||||
|
||||
```tsx
|
||||
import { ArrowRightIcon } from 'lucide-react';
|
||||
import { AccessibleIcon } from '@radix-ui/react-accessible-icon';
|
||||
|
||||
<AccessibleIcon label="Next item">
|
||||
<ArrowRightIcon />
|
||||
</AccessibleIcon>;
|
||||
```
|
||||
|
||||
### Bootstrap
|
||||
|
||||
```html
|
||||
<div>
|
||||
<i data-lucide="phone" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">Phone number</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Tailwind CSS
|
||||
|
||||
```html
|
||||
<div>
|
||||
<i data-lucide="phone" aria-hidden="true"></i>
|
||||
<span class="sr-only">Phone number</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
If you are not sure which hiding technique to use, read [how to hide content](https://www.a11yproject.com/posts/how-to-hide-content/).
|
||||
|
||||
## Further resources
|
||||
|
||||
Use these accessibility resources:
|
||||
|
||||
- [Web Content Accessibility Guidelines (WCAG) 2.1](https://www.w3.org/TR/WCAG21/)
|
||||
- [Web Accessibility Initiative (WAI)](https://www.w3.org/WAI/)
|
||||
- [Learn accessibility on web.dev](https://web.dev/learn/accessibility)
|
||||
- [Inclusive Components](https://inclusive-components.design/)
|
||||
- [A11yTalks](https://www.a11ytalks.com/)
|
||||
- [A11y automation tracker](https://a11y-automation.dev/)
|
||||
- [The A11Y Project](https://www.a11yproject.com/)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user