Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a92b9ba26 | ||
|
|
66d8f9fc39 | ||
|
|
e042fec3b5 | ||
|
|
c870b3c54a | ||
|
|
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 |
@@ -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,7 @@ 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,7 +1,6 @@
|
||||
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 }];
|
||||
|
||||
@@ -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];
|
||||
@@ -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!
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -12,6 +12,7 @@ import { computed } from 'vue'
|
||||
import { useData } from 'vitepress'
|
||||
import IconPreview from '~/.vitepress/theme/components/icons/IconPreview.vue'
|
||||
import IconPreviewSmall from '~/.vitepress/theme/components/icons/IconPreviewSmall.vue'
|
||||
import EditInStudioButton from '~/.vitepress/theme/components/icons/EditInStudioButton.vue'
|
||||
import IconInfo from '~/.vitepress/theme/components/icons/IconInfo.vue'
|
||||
import IconContributors from '~/.vitepress/theme/components/icons/IconContributors.vue'
|
||||
import IconShowcase from '~/.vitepress/theme/components/icons/IconShowcase.vue'
|
||||
@@ -19,9 +20,11 @@ import RelatedIcons from '~/.vitepress/theme/components/icons/RelatedIcons.vue'
|
||||
import CodeGroup from '~/.vitepress/theme/components/base/CodeGroup.vue'
|
||||
import Badge from '~/.vitepress/theme/components/base/Badge.vue'
|
||||
import Label from '~/.vitepress/theme/components/base/Label.vue'
|
||||
import PageTabs from '~/.vitepress/theme/components/base/PageTabs.vue'
|
||||
import { data } from './codeExamples.data'
|
||||
import { toCamelCase, toPascalCase } from '@lucide/shared'
|
||||
import { satisfies } from 'semver'
|
||||
import CarbonAdOverlay from '~/.vitepress/theme/components/icons/CarbonAdOverlay.vue';
|
||||
|
||||
const { params } = useData()
|
||||
|
||||
@@ -49,75 +52,92 @@ function releaseTagLink(version) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<div :class="$style.layout">
|
||||
<div :class="$style.iconPreviews">
|
||||
<div class="layout">
|
||||
<div class="actions">
|
||||
<EditInStudioButton :icon="params" />
|
||||
</div>
|
||||
<div class="iconPreviews">
|
||||
<IconPreview
|
||||
id="previewer"
|
||||
:name="params.name"
|
||||
:iconNode="params.iconNode"
|
||||
:class="$style.preview"
|
||||
/>
|
||||
<IconPreviewSmall
|
||||
:name="params.name"
|
||||
:iconNode="params.iconNode"
|
||||
:class="$style.smallPreview"
|
||||
class="preview"
|
||||
/>
|
||||
</div>
|
||||
<div >
|
||||
<div :class="$style.info">
|
||||
|
||||
<div>
|
||||
<div class="info">
|
||||
<IconInfo :icon="params" />
|
||||
<div :class="$style.meta">
|
||||
<div class="meta">
|
||||
<div
|
||||
v-if="params.createdRelease?.version"
|
||||
:class="$style.version"
|
||||
v-if="params.awaitingRelease"
|
||||
class="version"
|
||||
>
|
||||
<Label>Created:</Label>
|
||||
<Badge class="awaiting-release">Unreleased</Badge>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="params.createdRelease?.version"
|
||||
class="version"
|
||||
>
|
||||
<Label>Created:</Label>
|
||||
<Badge
|
||||
:href="releaseTagLink(params.createdRelease.version)"
|
||||
>
|
||||
v{{params.createdRelease.version}}
|
||||
{{params.createdRelease.version}}
|
||||
</Badge>
|
||||
</div>
|
||||
<div
|
||||
v-if="params.changedRelease?.version"
|
||||
:class="$style.version"
|
||||
v-if="!params.awaitingRelease && params.changedRelease?.version"
|
||||
class="version"
|
||||
>
|
||||
<Label>Last changed:</Label>
|
||||
<Badge
|
||||
:href="releaseTagLink(params.changedRelease.version)"
|
||||
>
|
||||
v{{params.changedRelease.version}}
|
||||
{{params.changedRelease.version}}
|
||||
</Badge>
|
||||
</div>
|
||||
<IconContributors :icon="params" :class="$style.contributors"/>
|
||||
<IconContributors :icon="params" class="contributors"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PageTabs
|
||||
:tabs="['More like this', 'Code examples', 'See in action']"
|
||||
>
|
||||
<template #tab-0>
|
||||
<RelatedIcons
|
||||
v-if="params.relatedIcons"
|
||||
:icons="params.relatedIcons"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #tab-1>
|
||||
<CodeGroup
|
||||
:groups="tabs"
|
||||
groupName="icon-code-example"
|
||||
:class="$style.code"
|
||||
class="code"
|
||||
>
|
||||
<div
|
||||
class="blocks"
|
||||
v-html="codeExample"
|
||||
/>
|
||||
</CodeGroup>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="icon-page-sections">
|
||||
<IconShowcase
|
||||
:name="params.name"
|
||||
:iconNode="params.iconNode"
|
||||
/>
|
||||
<template #tab-2>
|
||||
<IconShowcase
|
||||
:name="params.name"
|
||||
:iconNode="params.iconNode"
|
||||
/>
|
||||
</template>
|
||||
</PageTabs>
|
||||
|
||||
<RelatedIcons
|
||||
v-if="params.relatedIcons"
|
||||
:icons="params.relatedIcons"
|
||||
/>
|
||||
</div>
|
||||
<CarbonAdOverlay :drawerOpen="false" />
|
||||
|
||||
<style module>
|
||||
<style scoped>
|
||||
.preview {
|
||||
grid-area: preview;
|
||||
margin-bottom: 24px;
|
||||
@@ -130,31 +150,52 @@ function releaseTagLink(version) {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.actions {
|
||||
text-align: right;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin: -12px -12px 24px 0;
|
||||
}
|
||||
|
||||
.meta {
|
||||
margin-left: auto;
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.info {
|
||||
--tags-gradient-background: var(--vp-c-bg);
|
||||
|
||||
}
|
||||
|
||||
.icon-info {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.version .label {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.version, .contributors {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 0px;
|
||||
justify-content: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.version:first-child {
|
||||
margin-bottom: 8px;
|
||||
.version .awaiting-release {
|
||||
color: var(--vp-c-danger-3);
|
||||
}
|
||||
|
||||
.iconPreviews {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
justify-content: center;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
@@ -178,6 +219,13 @@ function releaseTagLink(version) {
|
||||
|
||||
.iconPreviews {
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.actions {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.smallPreview {
|
||||
@@ -185,61 +233,6 @@ function releaseTagLink(version) {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 860px) {
|
||||
.info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.meta {
|
||||
border-left: 1px solid var(--vp-c-divider);
|
||||
padding-left: 16px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.version, .contributors {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.info {
|
||||
display: block;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.meta {
|
||||
border-left: none;
|
||||
padding-left: 0;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.version, .contributors {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1152px) {
|
||||
.info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.meta {
|
||||
border-left: 1px solid var(--vp-c-divider);
|
||||
padding-left: 16px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.version, .contributors {
|
||||
flex-direction: row;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
@@ -249,4 +242,19 @@ function releaseTagLink(version) {
|
||||
margin-block-end: 64px;
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
.tab-list {
|
||||
margin: 24px -24px 24px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.tab-list {
|
||||
margin: 24px 0 24px;
|
||||
}
|
||||
|
||||
.iconPreviews {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,17 +3,28 @@ import * as iconDetails from '../.vitepress/data/iconDetails';
|
||||
export default {
|
||||
async load() {
|
||||
return {
|
||||
icons: Object.entries(iconDetails).map(
|
||||
([, { name, iconNode, popularity, createdRelease, aliases = [] }]) => ({
|
||||
name,
|
||||
iconNode,
|
||||
popularity: popularity?.count ?? 0,
|
||||
aliases: aliases
|
||||
.map((alias) => (typeof alias === 'string' ? alias : alias?.name))
|
||||
.filter((alias): alias is string => Boolean(alias)),
|
||||
createdRelease,
|
||||
}),
|
||||
),
|
||||
icons: Object.entries(iconDetails)
|
||||
.map(
|
||||
([
|
||||
,
|
||||
{ name, iconNode, popularity, createdRelease, awaitingRelease, aliases = [], fromFork },
|
||||
]) => ({
|
||||
name,
|
||||
iconNode,
|
||||
popularity: popularity?.count ?? 0,
|
||||
aliases: aliases
|
||||
.map((alias) => (typeof alias === 'string' ? alias : alias?.name))
|
||||
.filter((alias): alias is string => Boolean(alias)),
|
||||
createdRelease,
|
||||
awaitingRelease,
|
||||
fromFork,
|
||||
}),
|
||||
// We hide icons that are not released yet, we make exceptions for preview environments
|
||||
)
|
||||
.filter(
|
||||
({ awaitingRelease }) =>
|
||||
!awaitingRelease || process.env.VITE_SHOW_AWAITING_RELEASE === 'true',
|
||||
),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import iconNodes from '../../.vitepress/data/lab/iconNodes';
|
||||
import iconPopularity from '../../.vitepress/data/lab/iconPopularity';
|
||||
import iconMetaData from '../../.vitepress/data/lab/iconMetaData';
|
||||
import { type IconEntity } from '../../.vitepress/theme/types';
|
||||
|
||||
export default {
|
||||
paths: async () => {
|
||||
return (Object.entries(iconNodes) as unknown as IconEntity[]).map(([name, iconNode]) => {
|
||||
return Object.entries(iconNodes).map(([name, iconNode]) => {
|
||||
const iconDetails = iconMetaData[name];
|
||||
|
||||
delete iconDetails['$schema'];
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"prebuild:categoriesJson": "node ./scripts/writeCategoriesMetadata.mts",
|
||||
"prebuild:relatedIcons": "node ./scripts/writeIconRelatedIcons.mts",
|
||||
"prebuild:iconDetails": "node ./scripts/writeIconDetails.mts",
|
||||
"prebuild:iconPopularity": "node ./scripts/writeiconPopularity.mts && node ./scripts/writeiconPopularityIndex.mts",
|
||||
"prebuild:iconPopularity": "node --env-file-if-exists=.env ./scripts/writeiconPopularity.mts && node ./scripts/writeiconPopularityIndex.mts",
|
||||
"prebuild:brandStopwords": "node ./scripts/writeBrandStopwords.mts",
|
||||
"prebuild:writeLabIconNodes": "node ./scripts/writeLabIconNodes.mts",
|
||||
"prebuild:writeLabIconMetaIndex": "node ./scripts/writeLabIconMetaIndex.mts",
|
||||
@@ -24,7 +24,8 @@
|
||||
"prebuild:api": "nitropack prepare",
|
||||
"build:api": "NODE_OPTIONS=--max-old-space-size=8192 nitropack build",
|
||||
"build": "pnpm run /^prebuild:.*/ && pnpm run build:api && pnpm run build:docs && pnpm postbuild:vercelJson",
|
||||
"preview": "node .output/server/index.mjs"
|
||||
"preview": "node .output/server/index.mjs",
|
||||
"typecheck": "vue-tsc --noEmit"
|
||||
},
|
||||
"author": "Eric Fennis",
|
||||
"license": "ISC",
|
||||
@@ -47,9 +48,11 @@
|
||||
"svg-path-commander": "^2.3.3",
|
||||
"svg-pathdata": "^9.0.0",
|
||||
"svgson": "^5.3.1",
|
||||
"typescript": "^6.0.3",
|
||||
"vitepress": "^1.6.4",
|
||||
"vitepress-plugin-group-icons": "^1.7.6",
|
||||
"vitepress-plugin-llms": "^1.13.5"
|
||||
"vitepress-plugin-llms": "^1.13.5",
|
||||
"vue-tsc": "^3.3.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/vue": "^2.0.1",
|
||||
@@ -68,6 +71,6 @@
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"sandpack-vue3": "3.1.12",
|
||||
"vue": "^3.5.42"
|
||||
"vue": "^3.5.43"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,12 +27,13 @@ import iconNode from '../iconNodes/${iconName}.node.json' with { type: 'json' };
|
||||
import metaData from '../../../../icons/${iconName}.json' with { type: 'json' };
|
||||
import releaseData from '../releaseMetadata/${iconName}.json' with { type: 'json' };
|
||||
import popularity from '../iconPopularity/${iconName}.json' with { type: 'json' };
|
||||
import { IconMetaData, IconNode } from '~/.vitepress/theme/types';
|
||||
|
||||
const { tags, categories, contributors, aliases, deprecated, deprecationReason, toBeRemovedInVersion } = metaData;
|
||||
const { tags, categories, contributors, aliases, deprecated, deprecationReason, toBeRemovedInVersion }: IconMetaData = metaData;
|
||||
|
||||
const iconDetails = {
|
||||
name: '${iconName}',
|
||||
iconNode,
|
||||
iconNode: iconNode as IconNode,
|
||||
contributors,
|
||||
tags,
|
||||
categories,
|
||||
|
||||
@@ -20,6 +20,7 @@ if (!fs.existsSync(iconNodesDirectory)) {
|
||||
|
||||
const iconIndexFile = path.resolve(iconNodesDirectory, `index.ts`);
|
||||
const iconIndexFileImports = [];
|
||||
const iconIndexFileNodes = [];
|
||||
const iconIndexFileExports = [];
|
||||
const iconIndexFileDefaultExports = [];
|
||||
|
||||
@@ -32,7 +33,10 @@ const writeIconFiles = Object.entries(icons).map(async ([iconName, { children }]
|
||||
await fs.promises.writeFile(location, output, 'utf-8');
|
||||
|
||||
iconIndexFileImports.push(
|
||||
`import ${toCamelCase(iconName)}Node from './${iconName}.node.json' with { type: "json" };`,
|
||||
`import ${toCamelCase(iconName)}Json from './${iconName}.node.json' with { type: "json" };`,
|
||||
);
|
||||
iconIndexFileNodes.push(
|
||||
`const ${toCamelCase(iconName)}Node = ${toCamelCase(iconName)}Json as IconNode;`,
|
||||
);
|
||||
iconIndexFileExports.push(` ${toCamelCase(iconName)}Node as ${toCamelCase(iconName)},`);
|
||||
iconIndexFileDefaultExports.push(` '${iconName}': ${toCamelCase(iconName)}Node,`);
|
||||
@@ -43,16 +47,19 @@ try {
|
||||
await fs.promises.writeFile(
|
||||
iconIndexFile,
|
||||
`\
|
||||
import type { IconNode } from '../../theme/types';
|
||||
${iconIndexFileImports.join('\n')}
|
||||
|
||||
${iconIndexFileNodes.join('\n')}
|
||||
|
||||
export {
|
||||
${iconIndexFileExports.join('\n')}
|
||||
}
|
||||
|
||||
export default {
|
||||
${iconIndexFileDefaultExports.join('\n')}
|
||||
}
|
||||
`,
|
||||
} satisfies Record<string, IconNode>;
|
||||
`,
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ if (!fs.existsSync(iconNodesDirectory)) {
|
||||
|
||||
const iconIndexFile = path.resolve(iconNodesDirectory, `index.ts`);
|
||||
const iconIndexFileImports: string[] = [];
|
||||
const iconIndexFileNodes: string[] = [];
|
||||
const iconIndexFileExports: string[] = [];
|
||||
const iconIndexFileDefaultExports: string[] = [];
|
||||
|
||||
@@ -32,7 +33,10 @@ const writeIconFiles = Object.entries(icons).map(async ([iconName, { children }]
|
||||
await fs.promises.writeFile(location, output, 'utf-8');
|
||||
|
||||
iconIndexFileImports.push(
|
||||
`import ${toCamelCase(iconName)}Node from './${iconName}.node.json' with { type: "json" };`,
|
||||
`import ${toCamelCase(iconName)}Json from './${iconName}.node.json' with { type: "json" };`,
|
||||
);
|
||||
iconIndexFileNodes.push(
|
||||
`const ${toCamelCase(iconName)}Node = ${toCamelCase(iconName)}Json as IconNode;`,
|
||||
);
|
||||
iconIndexFileExports.push(` ${toCamelCase(iconName)}Node as ${toCamelCase(iconName)},`);
|
||||
iconIndexFileDefaultExports.push(` '${iconName}': ${toCamelCase(iconName)}Node,`);
|
||||
@@ -43,16 +47,19 @@ try {
|
||||
await fs.promises.writeFile(
|
||||
iconIndexFile,
|
||||
`\
|
||||
import type { IconNode } from '../../../theme/types';
|
||||
${iconIndexFileImports.join('\n')}
|
||||
|
||||
${iconIndexFileNodes.join('\n')}
|
||||
|
||||
export {
|
||||
${iconIndexFileExports.join('\n')}
|
||||
}
|
||||
|
||||
export default {
|
||||
${iconIndexFileDefaultExports.join('\n')}
|
||||
}
|
||||
`,
|
||||
} satisfies Record<string, IconNode>;
|
||||
`,
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
|
||||
@@ -40,7 +40,21 @@ const fetchAllReleases = async () => {
|
||||
);
|
||||
};
|
||||
|
||||
const getIconNamesAtTag = async (tag: string) => {
|
||||
// --full-tree resolves `icons/` from the repo root instead of the cwd (docs/)
|
||||
const files = await git.raw(['ls-tree', '--full-tree', '--name-only', tag, 'icons/']);
|
||||
|
||||
return new Set(
|
||||
files
|
||||
.split('\n')
|
||||
.filter((file) => file.endsWith('.svg'))
|
||||
.map((file) => path.basename(file, '.svg')),
|
||||
);
|
||||
};
|
||||
|
||||
const tags = await fetchAllReleases();
|
||||
const forkIconNames = await getIconNamesAtTag(tags[0]);
|
||||
const releasedIconNames = await getIconNamesAtTag(tags[tags.length - 1]);
|
||||
|
||||
const comparisonsPromises = tags.map(async (tag, index) => {
|
||||
const previousTag = tags[index - 1];
|
||||
@@ -150,19 +164,26 @@ try {
|
||||
const iconName = path.basename(iconJsonFile, '.json');
|
||||
const metaDir = path.resolve(releaseMetaDataDirectory, `${iconName}.json`);
|
||||
|
||||
if (!(iconName in newReleaseMetaData)) {
|
||||
const metaData = await fs.promises.readFile(path.join(ICONS_DIR, iconJsonFile), 'utf-8');
|
||||
const iconMetaData = JSON.parse(metaData);
|
||||
const aliases = iconMetaData.aliases ?? [];
|
||||
|
||||
const fromFork = [iconName, ...aliases.map((alias) => alias.name)].some((name) =>
|
||||
forkIconNames.has(name),
|
||||
);
|
||||
const awaitingRelease = !releasedIconNames.has(iconName);
|
||||
|
||||
if (!(iconName in newReleaseMetaData) && !fromFork && !awaitingRelease) {
|
||||
console.error(`Could not find release metadata for icon '${iconName}'.`);
|
||||
}
|
||||
|
||||
const contents = {
|
||||
fromFork,
|
||||
awaitingRelease,
|
||||
...defaultReleaseMetaData,
|
||||
...(newReleaseMetaData[iconName] ?? {}),
|
||||
};
|
||||
|
||||
const metaData = await fs.promises.readFile(path.join(ICONS_DIR, iconJsonFile), 'utf-8');
|
||||
const iconMetaData = JSON.parse(metaData);
|
||||
const aliases = iconMetaData.aliases ?? [];
|
||||
|
||||
if (aliases.length) {
|
||||
aliases.forEach((alias) => {
|
||||
if (!(alias.name in newReleaseMetaData)) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"allowImportingTsExtensions": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"~/.vitepress/*": ["./.vitepress/*"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"TanvirMahin24",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [
|
||||
"Showing product prices in BDT",
|
||||
"Selecting BDT in multi-currency interfaces",
|
||||
"Showing BDT exchange rates in currency converters",
|
||||
"Indicating BDT amounts in invoices and payments"
|
||||
],
|
||||
"tags": [
|
||||
"currency",
|
||||
"money",
|
||||
"payment",
|
||||
"bdt",
|
||||
"৳",
|
||||
"currencysymbol",
|
||||
"cash",
|
||||
"banknote",
|
||||
"price",
|
||||
"remittance",
|
||||
"ecommerce"
|
||||
],
|
||||
"categories": [
|
||||
"finance",
|
||||
"shopping",
|
||||
"travel"
|
||||
]
|
||||
}
|
||||
@@ -9,8 +9,6 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
||||
<rect width="20" height="14" x="2" y="7" rx="2" />
|
||||
<path d="M15 14H9" />
|
||||
<path d="M12 11v6" />
|
||||
<path d="M6 5a2 2 0 0 1 4 0v12a4 4 0 0 0 8 0 2 2 0 0 0-4 0" />
|
||||
<path d="M6 9h12" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 363 B After Width: | Height: | Size: 296 B |
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"colebemis",
|
||||
"csandman",
|
||||
"ericfennis",
|
||||
"tylerkade",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [
|
||||
"Indicating you're adding luggage",
|
||||
"adding a new business or company profile",
|
||||
"creating or saving a professional portfolio",
|
||||
"adding a job or role to a user's profile",
|
||||
"indicating add to briefcase or save for later in a product/service listing"
|
||||
],
|
||||
"tags": [
|
||||
"work",
|
||||
"bag",
|
||||
"baggage",
|
||||
"folder",
|
||||
"new",
|
||||
"add",
|
||||
"create",
|
||||
"increase",
|
||||
"briefcase",
|
||||
"portfolio",
|
||||
"business",
|
||||
"career",
|
||||
"employment",
|
||||
"professional",
|
||||
"plus"
|
||||
],
|
||||
"categories": [
|
||||
"transportation",
|
||||
"files",
|
||||
"account"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<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"
|
||||
>
|
||||
<path d="M13.354 20H4a2 2 0 01-2-2V8a2 2 0 012-2h16a2 2 0 012 2v3.354" />
|
||||
<path d="M16 11.354V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v16" />
|
||||
<path d="M16 17h6" />
|
||||
<path d="M19 14v6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 392 B |
@@ -3,7 +3,8 @@
|
||||
"contributors": [
|
||||
"rrod497",
|
||||
"jamiemlaw",
|
||||
"karsa-mistmere"
|
||||
"karsa-mistmere",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [],
|
||||
"tags": [
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M10 13a3 3 0 0 1-2.121-5.121" />
|
||||
<path d="M15.606 14.204c-3.5 1.5-5.899 4.503-8.899 7.503A1 1 0 0 1 6 22c-2 0-4-2-4-4a1 1 0 0 1 .293-.707c1.911-1.911 3.823-3.578 5.347-5.441" />
|
||||
<path d="M16.573 14.737A4 4 0 0 1 14 11" />
|
||||
<path d="M7.14 10.907a4 4 0 1 1 2.756-7.43A4 4 0 0 1 16.7 4.48a2 2 0 0 1 2.82 2.82 4 4 0 0 1 1.002 6.805A4 4 0 1 1 13 16" />
|
||||
<path d="M10 13a3 3 0 01-2.121-5.121" />
|
||||
<path d="M15.606 14.204c-3.5 1.5-5.899 4.503-8.899 7.503A1 1 0 016 22c-2 0-4-2-4-4a1 1 0 01.293-.707c1.911-1.911 3.823-3.578 5.347-5.441" />
|
||||
<path d="M16.573 14.737A4 4 0 0114 11" />
|
||||
<path d="M7.14 10.907a4 4 0 112.756-7.43A4 4 0 0116.7 4.48a2 2 0 012.82 2.82 4 4 0 011.002 6.805 4 4 0 11-7.51 1.59" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 572 B After Width: | Height: | Size: 560 B |
@@ -12,7 +12,7 @@
|
||||
<path d="m13 21 3-3-3-3" />
|
||||
<path d="M16 2v3" />
|
||||
<path d="m19 21 3-3-3-3" />
|
||||
<path d="M21 11.5V5.05a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2L9 21" />
|
||||
<path d="M21 11.5V5a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h4" />
|
||||
<path d="M3 9h18" />
|
||||
<path d="M8 2v3" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 413 B After Width: | Height: | Size: 407 B |
@@ -12,5 +12,5 @@
|
||||
<path d="M12 14v4" />
|
||||
<path d="M14.172 2a2 2 0 0 1 1.414.586l3.828 3.828A2 2 0 0 1 20 7.828V20a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2z" />
|
||||
<path d="M8 14h8" />
|
||||
<rect x="8" y="10" width="8" height="8" rx="1" />
|
||||
<rect x="8" y="10" width="8" height="8" rx="2" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 433 B After Width: | Height: | Size: 433 B |
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"AnnaSasDev",
|
||||
"briz123",
|
||||
"ajokt123",
|
||||
"rrod497",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [
|
||||
"Representing cupcakes in bakery product catalogs",
|
||||
"Filtering dessert options in food ordering apps",
|
||||
"Marking treats for parties or celebrations"
|
||||
],
|
||||
"tags": [
|
||||
"cake",
|
||||
"dessert",
|
||||
"food",
|
||||
"celebration",
|
||||
"sweet",
|
||||
"baking",
|
||||
"cooking"
|
||||
],
|
||||
"categories": [
|
||||
"food-beverage"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<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"
|
||||
>
|
||||
<path d="M12 22v-9" />
|
||||
<path d="M14 4h1a3 3 0 013 3l-.004.125A4 4 0 0121 11v2" />
|
||||
<path d="m15.5 22 1.5-9" />
|
||||
<path d="M21 13a1 1 0 01.919 1.394l-2.74 6.394A2 2 0 0117.34 22H6.659a2 2 0 01-1.838-1.212l-2.74-6.394A1 1 0 013 13z" />
|
||||
<path d="M3 13v-2a4 4 0 013.003-3.875L6 7a3 3 0 013-3h1" />
|
||||
<path d="M8.5 22 7 13" />
|
||||
<circle cx="12" cy="4" r="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 571 B |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [
|
||||
"Indicating package delivery to a home or residence",
|
||||
"Representing packages left at a recipient's door",
|
||||
"Tracking doorstep or unattended deliveries",
|
||||
"Selecting a delivery location or drop-off point"
|
||||
],
|
||||
"tags": [
|
||||
"delivery",
|
||||
"parcel",
|
||||
"doorstep",
|
||||
"shipping",
|
||||
"drop-off",
|
||||
"courier"
|
||||
],
|
||||
"categories": [
|
||||
"home",
|
||||
"food-beverage",
|
||||
"transportation"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<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"
|
||||
>
|
||||
<path d="M18 13v3" />
|
||||
<path d="M19 9V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16" />
|
||||
<path d="M2 21h8" />
|
||||
<path d="M9 12h.01" />
|
||||
<rect x="14" y="13" width="8" height="8" rx="1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 387 B |
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"danielbayley",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [
|
||||
"Indicating water sources on maps",
|
||||
"Controlling faucets in smart home apps",
|
||||
"Representing kitchen or bathroom plumbing fixtures",
|
||||
"Showing drinking water amenities in travel apps"
|
||||
],
|
||||
"tags": [
|
||||
"tap",
|
||||
"spigot",
|
||||
"water",
|
||||
"drinking",
|
||||
"fresh",
|
||||
"source",
|
||||
"pipes",
|
||||
"plumbing",
|
||||
"plumber",
|
||||
"sink",
|
||||
"basin",
|
||||
"kitchen",
|
||||
"bathroom",
|
||||
"washroom",
|
||||
"fixture",
|
||||
"valve",
|
||||
"flow",
|
||||
"drinkable",
|
||||
"potable",
|
||||
"amenities",
|
||||
"utilities",
|
||||
"hydration"
|
||||
],
|
||||
"categories": [
|
||||
"food-beverage",
|
||||
"home",
|
||||
"navigation",
|
||||
"travel"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<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"
|
||||
>
|
||||
<path d="M10.083 5.428 5.57 4.083a2 2 0 10.001 3.834l4.512-1.345" />
|
||||
<path d="M12 8v3" />
|
||||
<path d="m13.917 5.428 4.511-1.345a2 2 0 110 3.834l-4.51-1.345" />
|
||||
<path d="M18 17v-4.006" />
|
||||
<path d="M22 11v8" />
|
||||
<path d="M22 12h-3a1 1 0 00-1 .994h-2.539a4 4 0 00-6.915-.012L7 13a5 5 0 00-5 5v1a1 1 0 001 1h2a1 1 0 001-1v-1a1 1 0 01.995-1l1.552.018a4 4 0 006.907 0L18 17a1 1 0 001 1h3" />
|
||||
<circle cx="12" cy="6" r="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 636 B |
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"jguddas",
|
||||
"karsa-mistmere",
|
||||
"danielbayley",
|
||||
"UsamaKhan",
|
||||
"ajaxjiang96"
|
||||
],
|
||||
"use-cases": [
|
||||
"Opening household settings in smart home applications",
|
||||
"Managing property configuration in real estate dashboards",
|
||||
"Configuring home automation rules in IoT device management interfaces",
|
||||
"Accessing home security settings in residential alarm systems"
|
||||
],
|
||||
"tags": [
|
||||
"home",
|
||||
"building",
|
||||
"residence",
|
||||
"settings",
|
||||
"gear",
|
||||
"configuration",
|
||||
"property",
|
||||
"automation"
|
||||
],
|
||||
"categories": [
|
||||
"buildings",
|
||||
"home"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<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"
|
||||
>
|
||||
<path d="M10.584 21H5a2 2 0 01-2-2v-9a2 2 0 01.709-1.527l7-6a2 2 0 012.582 0l7 6A2 2 0 0121 10.001v.583" />
|
||||
<path d="M14 12H10a1 1 0 00-1 1v8" />
|
||||
<path d="m14.305 19.53.923-.382" />
|
||||
<path d="m15.229 16.852-.924-.383" />
|
||||
<path d="m16.852 15.228-.383-.923" />
|
||||
<path d="m16.852 20.773-.383.924" />
|
||||
<path d="m19.148 15.228.383-.923" />
|
||||
<path d="m19.53 21.697-.382-.924" />
|
||||
<path d="m20.773 16.852.922-.383" />
|
||||
<path d="m20.773 19.148.922.383" />
|
||||
<circle cx="18" cy="18" r="3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 705 B |
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"karsa-mistmere",
|
||||
"kaleidosium",
|
||||
"UbaidUllah9962"
|
||||
],
|
||||
"use-cases": [
|
||||
"Representing anonymous functions in code editors",
|
||||
"Representing serverless functions in cloud architecture diagrams",
|
||||
"Representing lambda notation in math and science"
|
||||
],
|
||||
"tags": [
|
||||
"greek",
|
||||
"symbol",
|
||||
"math",
|
||||
"function",
|
||||
"programming",
|
||||
"serverless",
|
||||
"anonymous",
|
||||
"functional",
|
||||
"cloud",
|
||||
"wavelength",
|
||||
"calculus",
|
||||
"linear algebra"
|
||||
],
|
||||
"categories": [
|
||||
"development",
|
||||
"math",
|
||||
"science",
|
||||
"text"
|
||||
]
|
||||
}
|
||||