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 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
@@ -24,7 +24,7 @@
24
24
 
25
25
  <script type = "module">
26
26
  import { createApp } from 'vue';
27
- import App from './vue/views/App.vue';
27
+ import App from './vue/examples/App.vue';
28
28
  import router from './router.js';
29
29
 
30
30
  const app = createApp(App)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "picocrank",
3
- "version": "1.22.2",
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.2",
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/views/Welcome.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/views/PageStructure.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/views/ThemePreview.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/views/UserDetails.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/views/TableExample.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/views/ViewItem.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/views/Admin.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/views/CalendarExample.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/views/Login.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/views/ButtonsExample.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/views/FormExample.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/views/ReadOnlyTextAreaExample.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/views/StatusExample.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/views/DialogExample.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/views/TabsExample.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/views/NavigationGridExample.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/views/LoginExample.vue'),
179
+ component: () => import('./vue/examples/LoginExample.vue'),
171
180
  meta: {
172
181
  title: 'Login',
173
182
  icon: SecurityValidationIcon,
package/vite.config.js CHANGED
@@ -94,7 +94,7 @@ export default defineConfig({
94
94
  plugins: [
95
95
  themesIndexPlugin(),
96
96
  Components({
97
- dirs: "./vue/",
97
+ dirs: ['./vue/components/'],
98
98
  extensions: ['vue'],
99
99
  deep: true,
100
100
  dts: false,