picocrank 1.22.2 → 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 +26 -17
- package/vite.config.js +1 -1
- package/vue/components/Table.vue +448 -104
- package/vue/components/TableColumnFilterPopover.vue +421 -0
- package/vue/composables/tableFilters.js +251 -0
- package/vue/composables/useLongPress.js +76 -0
- package/vue/{views → examples}/App.vue +1 -0
- package/vue/{views → examples}/ButtonsExample.vue +54 -1
- package/vue/examples/TableExample.vue +46 -0
- package/vue/examples/TableRemoteExample.vue +79 -0
- package/vue/{views → examples}/Welcome.vue +5 -2
- package/vue/views/TableExample.vue +0 -46
- /package/vue/{views → examples}/Admin.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}/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/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.
|
|
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,16 +57,25 @@ 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,
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
|
+
{
|
|
67
|
+
name: 'TableRemoteExample',
|
|
68
|
+
path: '/table-remote-example',
|
|
69
|
+
component: () => import('./vue/examples/TableRemoteExample.vue'),
|
|
70
|
+
meta: {
|
|
71
|
+
title: 'Remote table',
|
|
72
|
+
icon: TableIcon,
|
|
73
|
+
}
|
|
74
|
+
},
|
|
66
75
|
{
|
|
67
76
|
name: 'ViewItem',
|
|
68
77
|
path: '/view-item/:id',
|
|
69
|
-
component: () => import('./vue/
|
|
78
|
+
component: () => import('./vue/examples/ViewItem.vue'),
|
|
70
79
|
props: true,
|
|
71
80
|
meta: {
|
|
72
81
|
title: 'View item',
|
|
@@ -76,7 +85,7 @@ const routes = [
|
|
|
76
85
|
{
|
|
77
86
|
name: 'Admin',
|
|
78
87
|
path: '/admin',
|
|
79
|
-
component: () => import('./vue/
|
|
88
|
+
component: () => import('./vue/examples/Admin.vue'),
|
|
80
89
|
meta: {
|
|
81
90
|
title: 'Admin',
|
|
82
91
|
icon: SecurityValidationIcon,
|
|
@@ -85,7 +94,7 @@ const routes = [
|
|
|
85
94
|
{
|
|
86
95
|
name: 'CalendarExample',
|
|
87
96
|
path: '/calendar-example',
|
|
88
|
-
component: () => import('./vue/
|
|
97
|
+
component: () => import('./vue/examples/CalendarExample.vue'),
|
|
89
98
|
props: true,
|
|
90
99
|
meta: {
|
|
91
100
|
title: 'Calendar',
|
|
@@ -95,7 +104,7 @@ const routes = [
|
|
|
95
104
|
{
|
|
96
105
|
name: 'Login',
|
|
97
106
|
path: '/login',
|
|
98
|
-
component: () => import('./vue/
|
|
107
|
+
component: () => import('./vue/examples/Login.vue'),
|
|
99
108
|
meta: {
|
|
100
109
|
title: 'Login',
|
|
101
110
|
icon: SecurityValidationIcon,
|
|
@@ -104,7 +113,7 @@ const routes = [
|
|
|
104
113
|
{
|
|
105
114
|
name: 'ButtonsExample',
|
|
106
115
|
path: '/buttons-example',
|
|
107
|
-
component: () => import('./vue/
|
|
116
|
+
component: () => import('./vue/examples/ButtonsExample.vue'),
|
|
108
117
|
meta: {
|
|
109
118
|
title: 'Buttons',
|
|
110
119
|
icon: CursorPointer01Icon,
|
|
@@ -113,7 +122,7 @@ const routes = [
|
|
|
113
122
|
{
|
|
114
123
|
name: 'FormExample',
|
|
115
124
|
path: '/form-example',
|
|
116
|
-
component: () => import('./vue/
|
|
125
|
+
component: () => import('./vue/examples/FormExample.vue'),
|
|
117
126
|
meta: {
|
|
118
127
|
title: 'Forms',
|
|
119
128
|
icon: EditIcon,
|
|
@@ -122,7 +131,7 @@ const routes = [
|
|
|
122
131
|
{
|
|
123
132
|
name: 'ReadOnlyTextAreaExample',
|
|
124
133
|
path: '/readonly-textarea-example',
|
|
125
|
-
component: () => import('./vue/
|
|
134
|
+
component: () => import('./vue/examples/ReadOnlyTextAreaExample.vue'),
|
|
126
135
|
meta: {
|
|
127
136
|
title: 'Read-only output',
|
|
128
137
|
icon: ClipboardCopyIcon,
|
|
@@ -131,7 +140,7 @@ const routes = [
|
|
|
131
140
|
{
|
|
132
141
|
name: 'StatusExample',
|
|
133
142
|
path: '/status-example',
|
|
134
|
-
component: () => import('./vue/
|
|
143
|
+
component: () => import('./vue/examples/StatusExample.vue'),
|
|
135
144
|
meta: {
|
|
136
145
|
title: 'Status & notifications',
|
|
137
146
|
icon: StatusIcon,
|
|
@@ -140,7 +149,7 @@ const routes = [
|
|
|
140
149
|
{
|
|
141
150
|
name: 'DialogExample',
|
|
142
151
|
path: '/dialog-example',
|
|
143
|
-
component: () => import('./vue/
|
|
152
|
+
component: () => import('./vue/examples/DialogExample.vue'),
|
|
144
153
|
meta: {
|
|
145
154
|
title: 'Dialog',
|
|
146
155
|
icon: InformationCircleIcon,
|
|
@@ -149,7 +158,7 @@ const routes = [
|
|
|
149
158
|
{
|
|
150
159
|
name: 'TabsExample',
|
|
151
160
|
path: '/tabs-example',
|
|
152
|
-
component: () => import('./vue/
|
|
161
|
+
component: () => import('./vue/examples/TabsExample.vue'),
|
|
153
162
|
meta: {
|
|
154
163
|
title: 'Tabs',
|
|
155
164
|
icon: Menu01Icon,
|
|
@@ -158,7 +167,7 @@ const routes = [
|
|
|
158
167
|
{
|
|
159
168
|
name: 'NavigationGridExample',
|
|
160
169
|
path: '/navigation-grid',
|
|
161
|
-
component: () => import('./vue/
|
|
170
|
+
component: () => import('./vue/examples/NavigationGridExample.vue'),
|
|
162
171
|
meta: {
|
|
163
172
|
title: 'Navigation Grid',
|
|
164
173
|
icon: Navigation01Icon,
|
|
@@ -167,7 +176,7 @@ const routes = [
|
|
|
167
176
|
{
|
|
168
177
|
name: 'LoginExample',
|
|
169
178
|
path: '/login-example',
|
|
170
|
-
component: () => import('./vue/
|
|
179
|
+
component: () => import('./vue/examples/LoginExample.vue'),
|
|
171
180
|
meta: {
|
|
172
181
|
title: 'Login',
|
|
173
182
|
icon: SecurityValidationIcon,
|