@brickflow/ui 0.0.40 → 0.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +3 -0
  3. package/dist/runtime/components/Button/icons.demo.vue +9 -9
  4. package/dist/runtime/components/Button/index.vue +83 -57
  5. package/dist/runtime/components/Button/size.demo.vue +35 -66
  6. package/dist/runtime/components/Button/slots.demo.vue +10 -8
  7. package/dist/runtime/components/Button/state.demo.vue +58 -8
  8. package/dist/runtime/components/Button/variants.demo.vue +18 -20
  9. package/dist/runtime/components/Icon/basic.demo.vue +13 -10
  10. package/dist/runtime/components/Icon/gallery.demo.vue +142 -0
  11. package/dist/runtime/components/Icon/index.vue +15 -9
  12. package/dist/runtime/components/Link/basic.demo.vue +7 -7
  13. package/dist/runtime/components/Link/index.vue +81 -50
  14. package/dist/runtime/icon-font.d.ts +1 -0
  15. package/dist/runtime/pages/ui.vue +514 -347
  16. package/package.json +5 -5
  17. package/dist/runtime/components/Button/icons.demo.d.vue.ts +0 -8
  18. package/dist/runtime/components/Button/icons.demo.vue.d.ts +0 -8
  19. package/dist/runtime/components/Button/index.d.vue.ts +0 -51
  20. package/dist/runtime/components/Button/index.vue.d.ts +0 -51
  21. package/dist/runtime/components/Button/size.demo.d.vue.ts +0 -7
  22. package/dist/runtime/components/Button/size.demo.vue.d.ts +0 -7
  23. package/dist/runtime/components/Button/slots.demo.d.vue.ts +0 -7
  24. package/dist/runtime/components/Button/slots.demo.vue.d.ts +0 -7
  25. package/dist/runtime/components/Button/state.demo.d.vue.ts +0 -7
  26. package/dist/runtime/components/Button/state.demo.vue.d.ts +0 -7
  27. package/dist/runtime/components/Button/variants.demo.d.vue.ts +0 -7
  28. package/dist/runtime/components/Button/variants.demo.vue.d.ts +0 -7
  29. package/dist/runtime/components/Icon/basic.demo.d.vue.ts +0 -8
  30. package/dist/runtime/components/Icon/basic.demo.vue.d.ts +0 -8
  31. package/dist/runtime/components/Icon/index.d.vue.ts +0 -7
  32. package/dist/runtime/components/Icon/index.vue.d.ts +0 -7
  33. package/dist/runtime/components/Link/basic.demo.d.vue.ts +0 -8
  34. package/dist/runtime/components/Link/basic.demo.vue.d.ts +0 -8
  35. package/dist/runtime/components/Link/index.d.vue.ts +0 -51
  36. package/dist/runtime/components/Link/index.vue.d.ts +0 -51
  37. package/dist/runtime/pages/ui.d.vue.ts +0 -4
  38. package/dist/runtime/pages/ui.vue.d.ts +0 -4
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  },
5
5
  "configKey": "brickflowUi",
6
6
  "name": "@brickflow/ui",
7
- "version": "0.0.40",
7
+ "version": "0.0.43",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.3",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -750,6 +750,9 @@ const module$1 = defineNuxtModule({
750
750
  getContents: async () => {
751
751
  const iconNames = iconFont ? await getIconFontNames(iconFont.inputDir) : [];
752
752
  return [
753
+ "import { uiConfig } from '#brickflow-ui-config'",
754
+ "",
755
+ "export const buttonConfig = uiConfig.button",
753
756
  `export const iconNames = ${JSON.stringify(iconNames)}`,
754
757
  `export const firstIconName = ${JSON.stringify(iconNames[0] ?? "")}`,
755
758
  ""
@@ -1,13 +1,14 @@
1
- <script>
2
- export const uiDemo = {
3
- description: "Set leadingIcon and trailingIcon to an icon name. Slots take precedence when both are provided.",
4
- title: "Icons"
5
- };
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ description: 'Set leadingIcon and trailingIcon to an icon name. Slots take precedence when both are provided.',
4
+ title: 'Icons',
5
+ }
6
6
  </script>
7
7
 
8
- <script setup>
9
- import { firstIconName } from "#brickflow-ui-icons";
10
- import Button from "./index.vue";
8
+ <script lang="ts" setup>
9
+ import { firstIconName } from '#brickflow-ui-icons'
10
+
11
+ import Button from './index.vue'
11
12
  </script>
12
13
 
13
14
  <template>
@@ -17,7 +18,6 @@ import Button from "./index.vue";
17
18
  <Button
18
19
  :icon="firstIconName"
19
20
  :trailingIcon="firstIconName"
20
- variant="soft"
21
21
  >
22
22
  View profile
23
23
  </Button>
@@ -1,53 +1,79 @@
1
- <script>
2
- const UI_CONFIG = defineUiConfig();
1
+ <script lang="ts">
2
+ const UI_CONFIG = defineUiConfig<{
3
+ colorClasses: Record<string, Record<string, string>>
4
+ colorDefault: string
5
+ loadingIconName: string
6
+ sizeClasses: Record<string, string>
7
+ sizeDefault: string
8
+ sizeIconClasses: Record<string, string>
9
+ variantDefault: string
10
+ }>()
11
+
12
+ export type ButtonConfig = typeof UI_CONFIG
3
13
  </script>
4
14
 
5
- <script setup>
6
- import { computed, shallowRef } from "vue";
7
- import Icon from "../Icon/index.vue";
8
- import BaseLink from "../Link/index.vue";
9
- const props = defineProps({
10
- block: { type: Boolean, required: false },
11
- color: { type: String, required: false, default: UI_CONFIG.colorDefault },
12
- disabled: { type: Boolean, required: false },
13
- icon: { type: String, required: false, default: void 0 },
14
- loading: { type: Boolean, required: false },
15
- rounded: { type: Boolean, required: false, default: false },
16
- size: { type: String, required: false, default: UI_CONFIG.sizeDefault },
17
- square: { type: Boolean, required: false },
18
- to: { type: null, required: false, default: void 0 },
19
- trailingIcon: { type: String, required: false, default: void 0 },
20
- type: { type: String, required: false, default: "button" },
21
- variant: { type: String, required: false, default: UI_CONFIG.variantDefault }
22
- });
23
- const colorClasses = UI_CONFIG.colorClasses;
24
- const sizeClasses = UI_CONFIG.sizeClasses;
25
- const sizeIconClasses = {
26
- lg: UI_STYLE.size.lgIcon,
27
- md: UI_STYLE.size.mdIcon,
28
- sm: UI_STYLE.size.smIcon,
29
- xl: UI_STYLE.size.xlIcon,
30
- xs: UI_STYLE.size.xsIcon
31
- };
32
- const component = computed(() => props.to === void 0 ? "button" : BaseLink);
33
- const disabled = computed(() => props.disabled || props.loading);
34
- const isButton = computed(() => props.to === void 0);
35
- const classes = computed(() => [
36
- UI_STYLE.base,
37
- sizeClasses[props.size],
38
- colorClasses[props.color][props.variant],
39
- props.block && UI_STYLE.state.block,
40
- props.square && UI_STYLE.state.square,
41
- disabled.value && UI_STYLE.state.disabled
42
- ]);
43
- const isAnimating = shallowRef(false);
44
- const animationTapClass = UI_STYLE.animationTap;
45
- function handleTap() {
46
- isAnimating.value = false;
47
- requestAnimationFrame(() => {
48
- isAnimating.value = true;
49
- });
50
- }
15
+ <script setup lang="ts">
16
+ import type { RouteLocationRaw } from 'vue-router'
17
+
18
+ import { computed, shallowRef } from 'vue'
19
+
20
+ import Icon from '../Icon/index.vue'
21
+ import BaseLink from '../Link/index.vue'
22
+ const props = withDefaults(
23
+ defineProps<{
24
+ block?: boolean
25
+ color?: ButtonColor
26
+ disabled?: boolean
27
+ icon?: string
28
+ loading?: boolean
29
+ rounded?: boolean
30
+ size?: ButtonSize
31
+ square?: boolean
32
+ to?: RouteLocationRaw
33
+ trailingIcon?: string
34
+ type?: 'button' | 'reset' | 'submit'
35
+ variant?: ButtonVariant
36
+ }>(),
37
+ {
38
+ color: UI_CONFIG.colorDefault,
39
+ icon: undefined,
40
+ rounded: false,
41
+ size: UI_CONFIG.sizeDefault,
42
+ to: undefined,
43
+ trailingIcon: undefined,
44
+ type: 'button',
45
+ variant: UI_CONFIG.variantDefault,
46
+ },
47
+ )
48
+
49
+ const colorClasses = UI_CONFIG.colorClasses
50
+ const sizeClasses = UI_CONFIG.sizeClasses
51
+ const sizeIconClasses = UI_CONFIG.sizeIconClasses
52
+ type ButtonColor = Extract<keyof typeof colorClasses, string>
53
+ type ButtonSize = Extract<keyof typeof sizeClasses, string>
54
+ type ButtonVariant = Extract<keyof (typeof colorClasses)[ButtonColor], string>
55
+
56
+ const component = computed(() => (props.to === undefined ? 'button' : BaseLink))
57
+ const disabled = computed(() => props.disabled || props.loading)
58
+ const isButton = computed(() => props.to === undefined)
59
+ const classes = computed(() => [
60
+ UI_STYLE.base,
61
+ sizeClasses[props.size],
62
+ colorClasses[props.color][props.variant],
63
+ props.block && UI_STYLE.state.block,
64
+ props.square && UI_STYLE.state.square,
65
+ disabled.value && UI_STYLE.state.disabled,
66
+ ])
67
+
68
+ const isAnimating = shallowRef(false)
69
+ const animationTapClass = UI_STYLE.animationTap
70
+ function handleTap(): void {
71
+ isAnimating.value = false
72
+
73
+ requestAnimationFrame(() => {
74
+ isAnimating.value = true
75
+ })
76
+ }
51
77
  </script>
52
78
 
53
79
  <template>
@@ -55,16 +81,16 @@ function handleTap() {
55
81
  :is="component"
56
82
  v-bind="animationTapClass ? { onPointerupPassive: handleTap } : {}"
57
83
  :to="props.to"
58
- :type="isButton ? props.type : void 0"
59
- :disabled="isButton ? disabled : void 0"
60
- :aria-busy="props.loading || void 0"
61
- :aria-disabled="disabled || void 0"
62
- :tabindex="disabled && !isButton ? -1 : void 0"
84
+ :type="isButton ? props.type : undefined"
85
+ :disabled="isButton ? disabled : undefined"
86
+ :aria-busy="props.loading || undefined"
87
+ :aria-disabled="disabled || undefined"
88
+ :tabindex="disabled && !isButton ? -1 : undefined"
63
89
  :class="[classes, isAnimating && animationTapClass]"
64
90
  data-testid="ui-button"
65
91
  :style="{
66
- borderRadius: props.rounded && '99999px'
67
- }"
92
+ borderRadius: props.rounded && '99999px',
93
+ }"
68
94
  @contextmenu.prevent
69
95
  >
70
96
  <span
@@ -80,7 +106,7 @@ function handleTap() {
80
106
  />
81
107
  <Icon
82
108
  v-else
83
- :name="props.icon"
109
+ :name="props.icon!"
84
110
  :class="sizeIconClasses[props.size]"
85
111
  aria-hidden="true"
86
112
  />
@@ -100,7 +126,7 @@ function handleTap() {
100
126
  >
101
127
  <slot name="trailing">
102
128
  <Icon
103
- :name="props.trailingIcon"
129
+ :name="props.trailingIcon!"
104
130
  :class="sizeIconClasses[props.size]"
105
131
  aria-hidden="true"
106
132
  />
@@ -1,78 +1,47 @@
1
- <script>
2
- export const uiDemo = {
3
- title: "Size & Rounded"
4
- };
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ title: 'Size & Rounded',
4
+ }
5
5
  </script>
6
6
 
7
- <script setup>
8
- import { firstIconName } from "#brickflow-ui-icons";
9
- import Button from "./index.vue";
7
+ <script lang="ts" setup>
8
+ import { buttonConfig, firstIconName } from '#brickflow-ui-icons'
9
+
10
+ import Button from './index.vue'
10
11
  </script>
11
12
 
12
13
  <template>
13
14
  <div class="flex flex-col gap-3">
14
15
  <div class="flex flex-wrap items-center gap-3">
15
- <Button
16
- :icon="firstIconName"
17
- size="xs"
18
- >
19
- Button
20
- </Button>
21
- <Button
22
- :icon="firstIconName"
23
- size="sm"
24
- >
25
- Button
26
- </Button>
27
- <Button :icon="firstIconName">Button</Button>
28
- <Button
29
- :icon="firstIconName"
30
- size="lg"
31
- >
32
- Button
33
- </Button>
34
- <Button
35
- :icon="firstIconName"
36
- size="xl"
37
- >
38
- Button
39
- </Button>
16
+ <div
17
+ v-for="(_, size) in buttonConfig.sizeClasses"
18
+ :key="size"
19
+ class="flex flex-col items-center gap-1"
20
+ >
21
+ <span class="text-xs text-zinc-500">{{ size }}</span>
22
+ <Button
23
+ :icon="firstIconName"
24
+ :size="size"
25
+ >
26
+ Button
27
+ </Button>
28
+ </div>
40
29
  </div>
41
30
  <div class="flex flex-wrap items-center gap-3">
42
- <Button
43
- :icon="firstIconName"
44
- size="xs"
45
- rounded
46
- >
47
- Button
48
- </Button>
49
- <Button
50
- :icon="firstIconName"
51
- size="sm"
52
- rounded
53
- >
54
- Button
55
- </Button>
56
- <Button
57
- :icon="firstIconName"
58
- rounded
59
- >
60
- Button
61
- </Button>
62
- <Button
63
- :icon="firstIconName"
64
- size="lg"
65
- rounded
66
- >
67
- Button
68
- </Button>
69
- <Button
70
- :icon="firstIconName"
71
- size="xl"
72
- rounded
73
- >
74
- Button
75
- </Button>
31
+ <div
32
+ v-for="(_, size) in buttonConfig.sizeClasses"
33
+ :key="size"
34
+ class="flex flex-col items-center gap-1"
35
+ >
36
+ <span class="text-xs text-zinc-500">{{ size }}</span>
37
+ <Button
38
+ :icon="firstIconName"
39
+ :size="size"
40
+ rounded
41
+ >
42
+ Button
43
+ </Button>
44
+ </div>
76
45
  </div>
77
46
  </div>
78
47
  </template>
@@ -1,13 +1,15 @@
1
- <script>
2
- export const uiDemo = {
3
- title: "Slots"
4
- };
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ title: 'Slots',
4
+ }
5
5
  </script>
6
6
 
7
- <script setup>
8
- import { ref } from "vue";
9
- import Button from "./index.vue";
10
- const clicks = ref(0);
7
+ <script lang="ts" setup>
8
+ import { ref } from 'vue'
9
+
10
+ import Button from './index.vue'
11
+
12
+ const clicks = ref(0)
11
13
  </script>
12
14
 
13
15
  <template>
@@ -1,17 +1,67 @@
1
- <script>
2
- export const uiDemo = {
3
- title: "State"
4
- };
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ title: 'State',
4
+ }
5
5
  </script>
6
6
 
7
- <script setup>
8
- import Button from "./index.vue";
7
+ <script lang="ts" setup>
8
+ import { buttonConfig } from '#brickflow-ui-icons'
9
+
10
+ import Button from './index.vue'
11
+
12
+ const firstColorClasses = Object.values(buttonConfig.colorClasses)[0] ?? {}
9
13
  </script>
10
14
 
11
15
  <template>
12
16
  <div class="flex flex-wrap items-center gap-3">
13
- <Button loading>Loading</Button>
14
- <Button disabled>Disabled</Button>
17
+ <div class="basis-full">
18
+ <div class="relative flex w-fit flex-col items-start gap-3 pl-13">
19
+ <div class="absolute top-0 left-0 z-0 size-full object-cover pt-7 pl-13">
20
+ <img
21
+ src="https://images.unsplash.com/photo-1545346315-f4c47e3e1b55?ixid=M3w4MjcwNjd8MHwxfHNlYXJjaHwyfHxzdHJvbmclMjBtYW58ZW58MHx8fHwxNzg4MjIyNTU1fDA&ixlib=rb-4.1.0&w=1000&h=1000&fit=max&q=80"
22
+ class="top-0 z-0 size-full rounded-xl object-cover opacity-50"
23
+ />
24
+ </div>
25
+ <div class="flex w-full items-center justify-around gap-3 text-xs text-slate-600">
26
+ <span
27
+ v-for="(_, variant) in firstColorClasses"
28
+ :key="variant"
29
+ >
30
+ {{ variant }}
31
+ </span>
32
+ </div>
33
+ <div
34
+ v-for="(variants, color) in buttonConfig.colorClasses"
35
+ :key="color"
36
+ class="relative flex w-full items-center gap-3 p-2"
37
+ >
38
+ <span class="absolute top-1/2 right-full mr-3 -translate-y-1/2 text-xs text-slate-600">
39
+ {{ color }}
40
+ </span>
41
+ <div
42
+ v-for="(_, variant) in variants"
43
+ :key="variant"
44
+ class="flex flex-col items-stretch gap-2"
45
+ >
46
+ <Button
47
+ disabled
48
+ :color="color"
49
+ :variant="variant"
50
+ >
51
+ Disabled
52
+ </Button>
53
+ <Button
54
+ loading
55
+ :color="color"
56
+ :variant="variant"
57
+ >
58
+ Loading
59
+ </Button>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ </div>
64
+
15
65
  <Button to="/">Link</Button>
16
66
  <Button block>Block</Button>
17
67
  </div>
@@ -1,17 +1,15 @@
1
- <script>
2
- export const uiDemo = {
3
- title: "Color & Variants"
4
- };
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ title: 'Color & Variants',
4
+ }
5
5
  </script>
6
6
 
7
- <script setup>
8
- import Button from "./index.vue";
9
- const UI_CONFIG = defineUiConfig();
10
- const colors = Object.entries(UI_CONFIG.colorClasses).map(([name, classes]) => ({
11
- name,
12
- variants: Object.keys(classes)
13
- }));
14
- const variants = colors[0]?.variants ?? [];
7
+ <script lang="ts" setup>
8
+ import { buttonConfig } from '#brickflow-ui-icons'
9
+
10
+ import Button from './index.vue'
11
+
12
+ const firstColorClasses = Object.values(buttonConfig.colorClasses)[0] ?? {}
15
13
  </script>
16
14
 
17
15
  <template>
@@ -22,26 +20,26 @@ const variants = colors[0]?.variants ?? [];
22
20
  class="top-0 z-0 size-full rounded-xl object-cover opacity-50"
23
21
  />
24
22
  </div>
25
- <div class="flex w-full flex-wrap items-center justify-around gap-3 text-xs text-slate-600">
23
+ <div class="flex w-full items-center justify-around gap-3 text-xs text-slate-600">
26
24
  <span
27
- v-for="variant in variants"
25
+ v-for="(_, variant) in firstColorClasses"
28
26
  :key="variant"
29
27
  >
30
28
  {{ variant }}
31
29
  </span>
32
30
  </div>
33
31
  <div
34
- v-for="color in colors"
35
- :key="color.name"
36
- class="relative flex w-full flex-wrap items-center gap-3 p-2"
32
+ v-for="(variants, color) in buttonConfig.colorClasses"
33
+ :key="color"
34
+ class="relative flex w-full items-center gap-3 p-2"
37
35
  >
38
36
  <span class="absolute top-1/2 right-full mr-3 -translate-y-1/2 text-xs text-slate-600">
39
- {{ color.name }}
37
+ {{ color }}
40
38
  </span>
41
39
  <Button
42
- v-for="variant in color.variants"
40
+ v-for="(_, variant) in variants"
43
41
  :key="variant"
44
- :color="color.name"
42
+ :color="color"
45
43
  :variant="variant"
46
44
  >
47
45
  Button
@@ -1,15 +1,18 @@
1
- <script>
2
- export const uiDemo = {
3
- description: "The name matches the SVG filename without its extension.",
4
- title: "Basic usage"
5
- };
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ description: 'The name matches the SVG filename without its extension.',
4
+ title: 'Basic usage',
5
+ }
6
6
  </script>
7
7
 
8
- <script setup>
9
- import { ref } from "vue";
10
- import { firstIconName } from "#brickflow-ui-icons";
11
- import Icon from "./index.vue";
12
- const iconName = ref(firstIconName);
8
+ <script lang="ts" setup>
9
+ import { ref } from 'vue'
10
+
11
+ import { firstIconName } from '#brickflow-ui-icons'
12
+
13
+ import Icon from './index.vue'
14
+
15
+ const iconName = ref(firstIconName)
13
16
  </script>
14
17
 
15
18
  <template>
@@ -0,0 +1,142 @@
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ description: 'Browse every icon available in the project, filter by name, and click to copy its name.',
4
+ title: 'Icon gallery',
5
+ }
6
+ </script>
7
+
8
+ <script lang="ts" setup>
9
+ import { computed, onBeforeUnmount, ref } from 'vue'
10
+
11
+ import { iconNames } from '#brickflow-ui-icons'
12
+
13
+ import Icon from './index.vue'
14
+
15
+ const search = ref('')
16
+ const copiedIconName = ref('')
17
+ const copyError = ref('')
18
+ let copiedTimeout: ReturnType<typeof setTimeout> | undefined
19
+
20
+ const filteredIconNames = computed(() => {
21
+ const query = search.value.trim().toLocaleLowerCase()
22
+
23
+ return query ? iconNames.filter((name) => name.toLocaleLowerCase().includes(query)) : iconNames
24
+ })
25
+
26
+ const copyIconName = async (name: string): Promise<void> => {
27
+ copyError.value = ''
28
+
29
+ try {
30
+ await navigator.clipboard.writeText(name)
31
+ clearTimeout(copiedTimeout)
32
+ copiedIconName.value = name
33
+ copiedTimeout = setTimeout(() => {
34
+ copiedIconName.value = ''
35
+ }, 2000)
36
+ } catch {
37
+ copyError.value = 'Could not copy the icon name. Please try again.'
38
+ }
39
+ }
40
+
41
+ onBeforeUnmount(() => clearTimeout(copiedTimeout))
42
+ </script>
43
+
44
+ <template>
45
+ <div class="space-y-5 text-zinc-100">
46
+ <div class="flex flex-wrap items-center justify-between gap-3">
47
+ <label class="relative block w-full max-w-md">
48
+ <span class="sr-only">Search icons</span>
49
+ <svg
50
+ class="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-zinc-500"
51
+ aria-hidden="true"
52
+ fill="none"
53
+ stroke="currentColor"
54
+ stroke-linecap="round"
55
+ stroke-linejoin="round"
56
+ stroke-width="2"
57
+ viewBox="0 0 24 24"
58
+ >
59
+ <circle
60
+ cx="11"
61
+ cy="11"
62
+ r="8"
63
+ />
64
+ <path d="m21 21-4.3-4.3" />
65
+ </svg>
66
+ <input
67
+ v-model="search"
68
+ class="w-full max-w-60 rounded-lg border border-zinc-700 bg-zinc-950 py-2 pr-3 pl-9 text-sm text-zinc-200 caret-blue-400 outline-none placeholder:text-zinc-500 focus:border-blue-400 focus:ring-2 focus:ring-blue-400/20"
69
+ placeholder="Search icon"
70
+ type="search"
71
+ />
72
+ </label>
73
+
74
+ <p
75
+ class="text-sm text-zinc-400"
76
+ aria-live="polite"
77
+ >
78
+ {{ filteredIconNames.length }} of {{ iconNames.length }} icons
79
+ </p>
80
+ </div>
81
+
82
+ <p
83
+ class="sr-only"
84
+ role="status"
85
+ >
86
+ {{ copiedIconName ? `Copied ${copiedIconName}` : '' }}
87
+ </p>
88
+ <p
89
+ v-if="copyError"
90
+ class="text-sm text-red-400"
91
+ role="alert"
92
+ >
93
+ {{ copyError }}
94
+ </p>
95
+
96
+ <ul
97
+ v-if="filteredIconNames.length"
98
+ class="grid grid-cols-6 gap-3 lp:grid-cols-5 tb:grid-cols-4 mb:grid-cols-3 ms:grid-cols-2"
99
+ >
100
+ <li
101
+ v-for="name in filteredIconNames"
102
+ :key="name"
103
+ class="min-w-0"
104
+ >
105
+ <button
106
+ type="button"
107
+ :aria-label="`Copy ${name}`"
108
+ :class="[
109
+ 'group flex w-full cursor-pointer flex-col items-center gap-3 rounded-xl border px-3 py-5 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-400',
110
+ copiedIconName === name
111
+ ? 'border-green-500/50 bg-green-500/10'
112
+ : 'border-zinc-900 bg-zinc-950/50 hover:border-zinc-700 hover:bg-zinc-900/70',
113
+ ]"
114
+ :title="`Copy ${name}`"
115
+ @click="copyIconName(name)"
116
+ >
117
+ <Icon
118
+ :name="name"
119
+ aria-hidden="true"
120
+ class="text-3xl text-zinc-100"
121
+ />
122
+ <span
123
+ :class="[
124
+ 'w-full truncate text-center font-mono text-xs transition-colors',
125
+ copiedIconName === name ? 'text-green-400' : 'text-zinc-400 group-hover:text-zinc-100',
126
+ ]"
127
+ >
128
+ {{ copiedIconName === name ? 'Copied' : name }}
129
+ </span>
130
+ </button>
131
+ </li>
132
+ </ul>
133
+
134
+ <div
135
+ v-else
136
+ class="rounded-xl border border-dashed border-zinc-700 px-6 py-12 text-center"
137
+ >
138
+ <p class="text-sm font-medium text-zinc-200">No icons found</p>
139
+ <p class="mt-1 text-sm text-zinc-500">Try another name.</p>
140
+ </div>
141
+ </div>
142
+ </template>