picocrank 1.23.0 → 1.23.1
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.
- package/AGENTS.md +11 -0
- package/docs/buttons-with-icons.md +97 -0
- package/index.html +1 -1
- package/package.json +2 -2
- package/router.js +18 -18
- package/vite.config.js +1 -1
- package/vue/{views → examples}/ButtonsExample.vue +54 -1
- /package/vue/{views → examples}/Admin.vue +0 -0
- /package/vue/{views → examples}/App.vue +0 -0
- /package/vue/{views → examples}/CalendarExample.vue +0 -0
- /package/vue/{views → examples}/DialogExample.vue +0 -0
- /package/vue/{views → examples}/FormExample.vue +0 -0
- /package/vue/{views → examples}/Login.vue +0 -0
- /package/vue/{views → examples}/LoginExample.vue +0 -0
- /package/vue/{views → examples}/NavigationGridExample.vue +0 -0
- /package/vue/{views → examples}/PageStructure.vue +0 -0
- /package/vue/{views → examples}/ReadOnlyTextAreaExample.vue +0 -0
- /package/vue/{views → examples}/StatusExample.vue +0 -0
- /package/vue/{views → examples}/TableExample.vue +0 -0
- /package/vue/{views → examples}/TableRemoteExample.vue +0 -0
- /package/vue/{views → examples}/TabsExample.vue +0 -0
- /package/vue/{views → examples}/ThemePreview.vue +0 -0
- /package/vue/{views → examples}/UserDetails.vue +0 -0
- /package/vue/{views → examples}/ViewItem.vue +0 -0
- /package/vue/{views → examples}/Welcome.vue +0 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# PicoCrank — agent notes
|
|
2
|
+
|
|
3
|
+
PicoCrank is a Vue component library demo built on the [Femtocrank](https://github.com/jamesread/femtocrank) global CSS theme. Reusable components live under `vue/components/`; example pages under `vue/examples/`.
|
|
4
|
+
|
|
5
|
+
## UI patterns
|
|
6
|
+
|
|
7
|
+
- [Buttons with icons](docs/buttons-with-icons.md) — Femtocrank `.inline-icon`, Hugeicons, karma variants (icon + text and icon-only).
|
|
8
|
+
|
|
9
|
+
## Examples app
|
|
10
|
+
|
|
11
|
+
Run the Vite dev server and browse example routes (e.g. `/buttons-example`) for live reference implementations.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Buttons with icons (PicoCrank / Femtocrank)
|
|
2
|
+
|
|
3
|
+
Brief reference for reproducing icon buttons as shown in `vue/examples/ButtonsExample.vue`.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- **Femtocrank** `^2.5.3` or newer (provides `.inline-icon` and button/karma styles).
|
|
8
|
+
- **Hugeicons**: `@hugeicons/vue` and `@hugeicons/core-free-icons`.
|
|
9
|
+
- There is **no** `Button` Vue component — use plain HTML `<button>` (or `<a class="button">` for links).
|
|
10
|
+
|
|
11
|
+
## Pattern
|
|
12
|
+
|
|
13
|
+
1. Put **`inline-icon`** on the `<button>` (Femtocrank flex layout: icon + label, aligned with gap).
|
|
14
|
+
2. Add optional **karma intent** classes on the same element: `neutral`, `good`, `bad`, `warning`.
|
|
15
|
+
3. Render **`HugeiconsIcon`** at `1em` × `1em` with **`strokeWidth` `2.5`** for button-sized icons.
|
|
16
|
+
4. Set **`aria-hidden="true"`** on the icon when visible text is present.
|
|
17
|
+
5. For **icon-only** buttons, omit the label and set **`aria-label`** on the button instead.
|
|
18
|
+
|
|
19
|
+
## Icon and text
|
|
20
|
+
|
|
21
|
+
```vue
|
|
22
|
+
<script setup>
|
|
23
|
+
import { HugeiconsIcon } from '@hugeicons/vue'
|
|
24
|
+
import { Add01Icon } from '@hugeicons/core-free-icons'
|
|
25
|
+
|
|
26
|
+
const iconStrokeWidth = 2.5
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<button type="button" class="inline-icon good">
|
|
31
|
+
<HugeiconsIcon
|
|
32
|
+
:icon="Add01Icon"
|
|
33
|
+
width="1em"
|
|
34
|
+
height="1em"
|
|
35
|
+
:strokeWidth="iconStrokeWidth"
|
|
36
|
+
aria-hidden="true"
|
|
37
|
+
/>
|
|
38
|
+
<span>Add item</span>
|
|
39
|
+
</button>
|
|
40
|
+
</template>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Icon only
|
|
44
|
+
|
|
45
|
+
```vue
|
|
46
|
+
<button type="button" class="inline-icon neutral" aria-label="Edit">
|
|
47
|
+
<HugeiconsIcon
|
|
48
|
+
:icon="EditIcon"
|
|
49
|
+
width="1em"
|
|
50
|
+
height="1em"
|
|
51
|
+
:strokeWidth="iconStrokeWidth"
|
|
52
|
+
aria-hidden="true"
|
|
53
|
+
/>
|
|
54
|
+
</button>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Karma variants
|
|
58
|
+
|
|
59
|
+
Apply on the same `<button>` as `inline-icon`:
|
|
60
|
+
|
|
61
|
+
| Class | Use for |
|
|
62
|
+
|-----------|----------------------|
|
|
63
|
+
| (none) | Default |
|
|
64
|
+
| `neutral` | Secondary / toolbar |
|
|
65
|
+
| `good` | Primary / confirm |
|
|
66
|
+
| `bad` | Destructive |
|
|
67
|
+
| `warning` | Caution |
|
|
68
|
+
|
|
69
|
+
Example: `<button type="button" class="inline-icon bad" aria-label="Delete">…</button>`
|
|
70
|
+
|
|
71
|
+
## Toolbars
|
|
72
|
+
|
|
73
|
+
Group buttons in a flex row (example-only layout, not from Femtocrank):
|
|
74
|
+
|
|
75
|
+
```vue
|
|
76
|
+
<div role="toolbar" class="buttons-row">
|
|
77
|
+
<!-- buttons here -->
|
|
78
|
+
</div>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```css
|
|
82
|
+
.buttons-row {
|
|
83
|
+
display: flex;
|
|
84
|
+
flex-wrap: wrap;
|
|
85
|
+
gap: 0.5rem;
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Do not
|
|
90
|
+
|
|
91
|
+
- Do not create a wrapper `Button` component unless the project already has one.
|
|
92
|
+
- Do not omit `aria-label` on icon-only buttons.
|
|
93
|
+
- Do not set `aria-hidden` on the button itself — only on the icon when a visible label exists.
|
|
94
|
+
|
|
95
|
+
## Live example
|
|
96
|
+
|
|
97
|
+
Route: `/buttons-example` — sections **Icon and text** and **Icon only** in `vue/examples/ButtonsExample.vue`.
|
package/index.html
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "picocrank",
|
|
3
|
-
"version": "1.23.
|
|
3
|
+
"version": "1.23.1",
|
|
4
4
|
"description": "A set of zero dependency Vue components built on the Femtocrank global CSS theme.",
|
|
5
5
|
"main": "vite.config.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"@hugeicons/core-free-icons": "^4.2.3",
|
|
22
22
|
"@hugeicons/vue": "^1.0.7",
|
|
23
23
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
24
|
-
"femtocrank": "^2.5.
|
|
24
|
+
"femtocrank": "^2.5.3",
|
|
25
25
|
"unplugin-vue-components": "^32.1.0",
|
|
26
26
|
"vite": "^8.2.1",
|
|
27
27
|
"vue": "^3.5.41",
|
package/router.js
CHANGED
|
@@ -20,7 +20,7 @@ const routes = [
|
|
|
20
20
|
{
|
|
21
21
|
name: 'Welcome',
|
|
22
22
|
path: '/',
|
|
23
|
-
component: () => import('./vue/
|
|
23
|
+
component: () => import('./vue/examples/Welcome.vue'),
|
|
24
24
|
meta: {
|
|
25
25
|
title: 'Welcome',
|
|
26
26
|
icon: HomeIcon,
|
|
@@ -29,7 +29,7 @@ const routes = [
|
|
|
29
29
|
{
|
|
30
30
|
name: 'PageStructure',
|
|
31
31
|
path: '/page-structure',
|
|
32
|
-
component: () => import('./vue/
|
|
32
|
+
component: () => import('./vue/examples/PageStructure.vue'),
|
|
33
33
|
meta: {
|
|
34
34
|
title: 'Page structure',
|
|
35
35
|
icon: LayoutGridIcon,
|
|
@@ -38,7 +38,7 @@ const routes = [
|
|
|
38
38
|
{
|
|
39
39
|
name: 'ThemePreview',
|
|
40
40
|
path: '/theme-preview',
|
|
41
|
-
component: () => import('./vue/
|
|
41
|
+
component: () => import('./vue/examples/ThemePreview.vue'),
|
|
42
42
|
meta: {
|
|
43
43
|
title: 'Theme Switcher',
|
|
44
44
|
icon: PaintBoardIcon,
|
|
@@ -47,7 +47,7 @@ const routes = [
|
|
|
47
47
|
{
|
|
48
48
|
name: 'UserDetails',
|
|
49
49
|
path: '/user',
|
|
50
|
-
component: () => import('./vue/
|
|
50
|
+
component: () => import('./vue/examples/UserDetails.vue'),
|
|
51
51
|
meta: {
|
|
52
52
|
title: 'User details',
|
|
53
53
|
icon: UserIcon,
|
|
@@ -57,7 +57,7 @@ const routes = [
|
|
|
57
57
|
name: 'TableExample',
|
|
58
58
|
path: '/table-example',
|
|
59
59
|
title: 'Table Example',
|
|
60
|
-
component: () => import('./vue/
|
|
60
|
+
component: () => import('./vue/examples/TableExample.vue'),
|
|
61
61
|
meta: {
|
|
62
62
|
title: 'Table',
|
|
63
63
|
icon: TableIcon,
|
|
@@ -66,7 +66,7 @@ const routes = [
|
|
|
66
66
|
{
|
|
67
67
|
name: 'TableRemoteExample',
|
|
68
68
|
path: '/table-remote-example',
|
|
69
|
-
component: () => import('./vue/
|
|
69
|
+
component: () => import('./vue/examples/TableRemoteExample.vue'),
|
|
70
70
|
meta: {
|
|
71
71
|
title: 'Remote table',
|
|
72
72
|
icon: TableIcon,
|
|
@@ -75,7 +75,7 @@ const routes = [
|
|
|
75
75
|
{
|
|
76
76
|
name: 'ViewItem',
|
|
77
77
|
path: '/view-item/:id',
|
|
78
|
-
component: () => import('./vue/
|
|
78
|
+
component: () => import('./vue/examples/ViewItem.vue'),
|
|
79
79
|
props: true,
|
|
80
80
|
meta: {
|
|
81
81
|
title: 'View item',
|
|
@@ -85,7 +85,7 @@ const routes = [
|
|
|
85
85
|
{
|
|
86
86
|
name: 'Admin',
|
|
87
87
|
path: '/admin',
|
|
88
|
-
component: () => import('./vue/
|
|
88
|
+
component: () => import('./vue/examples/Admin.vue'),
|
|
89
89
|
meta: {
|
|
90
90
|
title: 'Admin',
|
|
91
91
|
icon: SecurityValidationIcon,
|
|
@@ -94,7 +94,7 @@ const routes = [
|
|
|
94
94
|
{
|
|
95
95
|
name: 'CalendarExample',
|
|
96
96
|
path: '/calendar-example',
|
|
97
|
-
component: () => import('./vue/
|
|
97
|
+
component: () => import('./vue/examples/CalendarExample.vue'),
|
|
98
98
|
props: true,
|
|
99
99
|
meta: {
|
|
100
100
|
title: 'Calendar',
|
|
@@ -104,7 +104,7 @@ const routes = [
|
|
|
104
104
|
{
|
|
105
105
|
name: 'Login',
|
|
106
106
|
path: '/login',
|
|
107
|
-
component: () => import('./vue/
|
|
107
|
+
component: () => import('./vue/examples/Login.vue'),
|
|
108
108
|
meta: {
|
|
109
109
|
title: 'Login',
|
|
110
110
|
icon: SecurityValidationIcon,
|
|
@@ -113,7 +113,7 @@ const routes = [
|
|
|
113
113
|
{
|
|
114
114
|
name: 'ButtonsExample',
|
|
115
115
|
path: '/buttons-example',
|
|
116
|
-
component: () => import('./vue/
|
|
116
|
+
component: () => import('./vue/examples/ButtonsExample.vue'),
|
|
117
117
|
meta: {
|
|
118
118
|
title: 'Buttons',
|
|
119
119
|
icon: CursorPointer01Icon,
|
|
@@ -122,7 +122,7 @@ const routes = [
|
|
|
122
122
|
{
|
|
123
123
|
name: 'FormExample',
|
|
124
124
|
path: '/form-example',
|
|
125
|
-
component: () => import('./vue/
|
|
125
|
+
component: () => import('./vue/examples/FormExample.vue'),
|
|
126
126
|
meta: {
|
|
127
127
|
title: 'Forms',
|
|
128
128
|
icon: EditIcon,
|
|
@@ -131,7 +131,7 @@ const routes = [
|
|
|
131
131
|
{
|
|
132
132
|
name: 'ReadOnlyTextAreaExample',
|
|
133
133
|
path: '/readonly-textarea-example',
|
|
134
|
-
component: () => import('./vue/
|
|
134
|
+
component: () => import('./vue/examples/ReadOnlyTextAreaExample.vue'),
|
|
135
135
|
meta: {
|
|
136
136
|
title: 'Read-only output',
|
|
137
137
|
icon: ClipboardCopyIcon,
|
|
@@ -140,7 +140,7 @@ const routes = [
|
|
|
140
140
|
{
|
|
141
141
|
name: 'StatusExample',
|
|
142
142
|
path: '/status-example',
|
|
143
|
-
component: () => import('./vue/
|
|
143
|
+
component: () => import('./vue/examples/StatusExample.vue'),
|
|
144
144
|
meta: {
|
|
145
145
|
title: 'Status & notifications',
|
|
146
146
|
icon: StatusIcon,
|
|
@@ -149,7 +149,7 @@ const routes = [
|
|
|
149
149
|
{
|
|
150
150
|
name: 'DialogExample',
|
|
151
151
|
path: '/dialog-example',
|
|
152
|
-
component: () => import('./vue/
|
|
152
|
+
component: () => import('./vue/examples/DialogExample.vue'),
|
|
153
153
|
meta: {
|
|
154
154
|
title: 'Dialog',
|
|
155
155
|
icon: InformationCircleIcon,
|
|
@@ -158,7 +158,7 @@ const routes = [
|
|
|
158
158
|
{
|
|
159
159
|
name: 'TabsExample',
|
|
160
160
|
path: '/tabs-example',
|
|
161
|
-
component: () => import('./vue/
|
|
161
|
+
component: () => import('./vue/examples/TabsExample.vue'),
|
|
162
162
|
meta: {
|
|
163
163
|
title: 'Tabs',
|
|
164
164
|
icon: Menu01Icon,
|
|
@@ -167,7 +167,7 @@ const routes = [
|
|
|
167
167
|
{
|
|
168
168
|
name: 'NavigationGridExample',
|
|
169
169
|
path: '/navigation-grid',
|
|
170
|
-
component: () => import('./vue/
|
|
170
|
+
component: () => import('./vue/examples/NavigationGridExample.vue'),
|
|
171
171
|
meta: {
|
|
172
172
|
title: 'Navigation Grid',
|
|
173
173
|
icon: Navigation01Icon,
|
|
@@ -176,7 +176,7 @@ const routes = [
|
|
|
176
176
|
{
|
|
177
177
|
name: 'LoginExample',
|
|
178
178
|
path: '/login-example',
|
|
179
|
-
component: () => import('./vue/
|
|
179
|
+
component: () => import('./vue/examples/LoginExample.vue'),
|
|
180
180
|
meta: {
|
|
181
181
|
title: 'Login',
|
|
182
182
|
icon: SecurityValidationIcon,
|
package/vite.config.js
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
>
|
|
6
6
|
<p>
|
|
7
7
|
Buttons use the global Femtocrank theme. Apply karma classes for intent, or
|
|
8
|
-
rely on <code>type</code> attributes for form semantics.
|
|
8
|
+
rely on <code>type</code> attributes for form semantics. Use
|
|
9
|
+
<code>.inline-icon</code> on buttons with icons, and
|
|
10
|
+
<code>aria-label</code> when there is no visible text.
|
|
9
11
|
</p>
|
|
10
12
|
</Section>
|
|
11
13
|
|
|
@@ -19,6 +21,47 @@
|
|
|
19
21
|
</div>
|
|
20
22
|
</Section>
|
|
21
23
|
|
|
24
|
+
<Section title="Icon and text" subtitle="Hugeicons with a visible label">
|
|
25
|
+
<div role="toolbar" class="buttons-row">
|
|
26
|
+
<button type="button" class="inline-icon">
|
|
27
|
+
<HugeiconsIcon :icon="Add01Icon" width="1em" height="1em" :strokeWidth="iconStrokeWidth" aria-hidden="true" />
|
|
28
|
+
<span>Add item</span>
|
|
29
|
+
</button>
|
|
30
|
+
<button type="button" class="inline-icon neutral">
|
|
31
|
+
<HugeiconsIcon :icon="EditIcon" width="1em" height="1em" :strokeWidth="iconStrokeWidth" aria-hidden="true" />
|
|
32
|
+
<span>Edit</span>
|
|
33
|
+
</button>
|
|
34
|
+
<button type="button" class="inline-icon good">
|
|
35
|
+
<HugeiconsIcon :icon="SearchIcon" width="1em" height="1em" :strokeWidth="iconStrokeWidth" aria-hidden="true" />
|
|
36
|
+
<span>Search</span>
|
|
37
|
+
</button>
|
|
38
|
+
<button type="button" class="inline-icon">
|
|
39
|
+
<HugeiconsIcon :icon="Copy01Icon" width="1em" height="1em" :strokeWidth="iconStrokeWidth" aria-hidden="true" />
|
|
40
|
+
<span>Copy</span>
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
</Section>
|
|
44
|
+
|
|
45
|
+
<Section title="Icon only" subtitle="Hugeicons without visible text">
|
|
46
|
+
<div role="toolbar" class="buttons-row">
|
|
47
|
+
<button type="button" class="inline-icon" aria-label="Add item">
|
|
48
|
+
<HugeiconsIcon :icon="Add01Icon" width="1em" height="1em" :strokeWidth="iconStrokeWidth" aria-hidden="true" />
|
|
49
|
+
</button>
|
|
50
|
+
<button type="button" class="inline-icon neutral" aria-label="Edit">
|
|
51
|
+
<HugeiconsIcon :icon="EditIcon" width="1em" height="1em" :strokeWidth="iconStrokeWidth" aria-hidden="true" />
|
|
52
|
+
</button>
|
|
53
|
+
<button type="button" class="inline-icon good" aria-label="Search">
|
|
54
|
+
<HugeiconsIcon :icon="SearchIcon" width="1em" height="1em" :strokeWidth="iconStrokeWidth" aria-hidden="true" />
|
|
55
|
+
</button>
|
|
56
|
+
<button type="button" class="inline-icon bad" aria-label="Delete">
|
|
57
|
+
<HugeiconsIcon :icon="Delete02Icon" width="1em" height="1em" :strokeWidth="iconStrokeWidth" aria-hidden="true" />
|
|
58
|
+
</button>
|
|
59
|
+
<button type="button" class="inline-icon neutral" aria-label="Settings">
|
|
60
|
+
<HugeiconsIcon :icon="Settings01Icon" width="1em" height="1em" :strokeWidth="iconStrokeWidth" aria-hidden="true" />
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
</Section>
|
|
64
|
+
|
|
22
65
|
<Section title="Form actions" subtitle="Semantic button types in a fieldset">
|
|
23
66
|
<FormLayout class="buttons-demo-form" @submit.prevent="onSubmit">
|
|
24
67
|
<template #actions>
|
|
@@ -67,10 +110,20 @@
|
|
|
67
110
|
|
|
68
111
|
<script setup>
|
|
69
112
|
import { ref } from 'vue'
|
|
113
|
+
import { HugeiconsIcon } from '@hugeicons/vue'
|
|
114
|
+
import {
|
|
115
|
+
Add01Icon,
|
|
116
|
+
Copy01Icon,
|
|
117
|
+
Delete02Icon,
|
|
118
|
+
EditIcon,
|
|
119
|
+
SearchIcon,
|
|
120
|
+
Settings01Icon,
|
|
121
|
+
} from '@hugeicons/core-free-icons'
|
|
70
122
|
import Section from '../components/Section.vue'
|
|
71
123
|
import FormLayout from '../components/FormLayout.vue'
|
|
72
124
|
|
|
73
125
|
const lastAction = ref('')
|
|
126
|
+
const iconStrokeWidth = 2.5
|
|
74
127
|
|
|
75
128
|
function onSubmit() {
|
|
76
129
|
lastAction.value = 'Form submitted.'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|