picocrank 1.9.1 → 1.11.0

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.
@@ -9,29 +9,29 @@
9
9
  <h3>System Overview</h3>
10
10
  <p>Welcome to the administrative panel. This is a placeholder for administrative functionality.</p>
11
11
 
12
- <div class="stats-grid">
13
- <div class="stat-card">
12
+ <div class="grid-boxed">
13
+ <div class="stat-display">
14
14
  <h4>Total Users</h4>
15
- <div class="stat-value">1,247</div>
16
- <div class="stat-change">+12% from last month</div>
15
+ <div class="stat fg-good">1,247</div>
16
+ <div class="subtle">+12% from last month</div>
17
17
  </div>
18
18
 
19
- <div class="stat-card">
19
+ <div class="stat-display">
20
20
  <h4>Active Sessions</h4>
21
- <div class="stat-value">89</div>
22
- <div class="stat-change">-3% from last hour</div>
21
+ <div class="stat fg-bad">89</div>
22
+ <div class="subtle">-3% from last hour</div>
23
23
  </div>
24
24
 
25
- <div class="stat-card">
25
+ <div class="stat-display">
26
26
  <h4>System Load</h4>
27
- <div class="stat-value">67%</div>
28
- <div class="stat-change">Normal</div>
27
+ <div class="stat fg-warning">67%</div>
28
+ <div class="subtle">Moderate</div>
29
29
  </div>
30
30
 
31
- <div class="stat-card">
31
+ <div class="stat-display">
32
32
  <h4>Storage Used</h4>
33
- <div class="stat-value">2.4 GB</div>
34
- <div class="stat-change">45% of total</div>
33
+ <div class="stat fg-good">2.4 GB</div>
34
+ <div class="subtle">45% of total</div>
35
35
  </div>
36
36
  </div>
37
37
  </div>
@@ -64,7 +64,7 @@
64
64
 
65
65
  <div class="admin-section">
66
66
  <h3>Quick Actions</h3>
67
- <div class="action-buttons">
67
+ <div role = "toolbar">
68
68
  <button class="action-btn">Create User</button>
69
69
  <button class="action-btn">System Backup</button>
70
70
  <button class="action-btn">View Logs</button>
package/vue/views/App.vue CHANGED
@@ -10,33 +10,37 @@
10
10
  </template>
11
11
  </Header>
12
12
 
13
- <div id = "layout">
14
- <Sidebar v-if="sidebarEnabled" ref = "sidebar" />
13
+ <Navigation ref="navigation">
14
+ <div id = "layout">
15
+ <Sidebar v-if="sidebarEnabled" ref = "sidebar" />
15
16
 
16
- <div id = "content">
17
- <main>
18
- <router-view />
19
- </main>
17
+ <div id = "content">
18
+ <main>
19
+ <router-view />
20
+ </main>
20
21
 
21
- <footer>
22
- <span><a href = "https://github.com/jamesread/picocrank">PicoCrank</a></span>
23
- </footer>
22
+ <footer>
23
+ <span><a href = "https://github.com/jamesread/picocrank">PicoCrank</a></span>
24
+ </footer>
25
+ </div>
24
26
  </div>
25
- </div>
27
+ </Navigation>
26
28
  </template>
27
29
 
28
30
  <script setup>
29
31
  import { ref, onMounted, provide } from 'vue';
30
32
  import { useRouter } from 'vue-router';
31
- import { Pin02Icon } from '@hugeicons/core-free-icons'
33
+ import { Pin02Icon, SecurityValidationIcon, EditIcon, ViewIcon, HomeIcon } from '@hugeicons/core-free-icons'
32
34
 
33
35
  import QuickSearch from './../components/QuickSearch.vue'
36
+ import Navigation from './../components/Navigation.vue'
34
37
  import '../../styles.css'
35
38
  import logoUrl from '/logo.png';
36
39
 
37
40
  const quickSearchRef = ref(null)
38
41
  const router = useRouter();
39
42
  const sidebar = ref(null);
43
+ const navigation = ref(null);
40
44
  const sidebarEnabled = ref(true);
41
45
 
42
46
  function toggleSidebar() {
@@ -54,17 +58,28 @@
54
58
  provide('toggleSidebarEnabled', toggleSidebarEnabled);
55
59
 
56
60
  onMounted(() => {
61
+ if (navigation.value) {
62
+ navigation.value.addRouterLink('Welcome')
63
+ navigation.value.addRouterLink('TableExample')
64
+ navigation.value.addRouterLink('CalendarExample')
65
+ navigation.value.addRouterLink('FormExample')
66
+ navigation.value.addRouterLink('TabsExample')
67
+ navigation.value.addSeparator('separator-1');
68
+ navigation.value.addRouterLink('ViewItem', 'View Awesome Item', { params: { id: 1 } })
69
+ navigation.value.addSeparator('separator-2');
70
+ navigation.value.addCallback('Callback Example', helloWorld, { icon: Pin02Icon })
71
+ navigation.value.addSeparator('separator-3');
72
+ navigation.value.addHtml('<h2 style = "padding: 0.75em;">Authentication</h2>', { name: 'auth-heading' })
73
+ navigation.value.addRouterLink('LoginExample')
74
+ navigation.value.addSeparator('separator-4');
75
+ navigation.value.addHtml('<h2 style = "padding: 0.75em;">User Control Panel</h2>', { name: 'user-panel-heading' })
76
+ navigation.value.addRouterLink('UserControlPanel')
77
+ navigation.value.addSeparator('separator-5');
78
+ navigation.value.addHtml('<h2 style = "padding: 0.75em;">Administration</h2>', { name: 'admin-heading' })
79
+ navigation.value.addRouterLink('Admin')
80
+ }
81
+
57
82
  if (sidebar.value) {
58
- sidebar.value.addRouterLink('Welcome')
59
- sidebar.value.addRouterLink('TableExample')
60
- sidebar.value.addRouterLink('CalendarExample')
61
- sidebar.value.addSeparator('separator-1');
62
- sidebar.value.addRouterLink('ViewItem', 'View Awesome Item', { params: { id: 1 } })
63
- sidebar.value.addSeparator('separator-2');
64
- sidebar.value.addCallback('Callback Example', helloWorld, { icon: Pin02Icon })
65
- sidebar.value.addSeparator('separator-3');
66
- sidebar.value.addHtml('<h2 style = "padding: 0.75em;">Administration</h2>', { name: 'admin-heading' })
67
- sidebar.value.addRouterLink('Admin')
68
83
  sidebar.value.open();
69
84
  sidebar.value.stick();
70
85
  }
@@ -0,0 +1,268 @@
1
+ <template>
2
+ <Section
3
+ title="Form Example"
4
+ subtitle="A simple form with various input types"
5
+ >
6
+ <template #toolbar>
7
+ <button @click="resetForm">Reset</button>
8
+ </template>
9
+
10
+ <form @submit.prevent="handleSubmit">
11
+ <div class="form-group">
12
+ <label for="name">Name</label>
13
+ <input
14
+ id="name"
15
+ v-model="formData.name"
16
+ type="text"
17
+ placeholder="Enter your name"
18
+ required
19
+ />
20
+ </div>
21
+
22
+ <div class="form-group">
23
+ <label for="email">Email</label>
24
+ <input
25
+ id="email"
26
+ v-model="formData.email"
27
+ type="email"
28
+ placeholder="Enter your email"
29
+ required
30
+ />
31
+ </div>
32
+
33
+ <div class="form-group">
34
+ <label for="country">Country</label>
35
+ <select id="country" v-model="formData.country" required>
36
+ <option value="">Select a country</option>
37
+ <option value="us">United States</option>
38
+ <option value="uk">United Kingdom</option>
39
+ <option value="ca">Canada</option>
40
+ <option value="au">Australia</option>
41
+ <option value="de">Germany</option>
42
+ <option value="fr">France</option>
43
+ </select>
44
+ </div>
45
+
46
+ <div class="form-group">
47
+ <label for="message">Message</label>
48
+ <textarea
49
+ id="message"
50
+ v-model="formData.message"
51
+ placeholder="Enter your message"
52
+ rows="4"
53
+ ></textarea>
54
+ </div>
55
+
56
+ <div class="form-group">
57
+ <label>
58
+ <input
59
+ type="checkbox"
60
+ v-model="formData.newsletter"
61
+ />
62
+ Subscribe to newsletter
63
+ </label>
64
+ </div>
65
+
66
+ <div class="form-group">
67
+ <label>Preferred Contact Method</label>
68
+ <div class="radio-group">
69
+ <label>
70
+ <input
71
+ type="radio"
72
+ v-model="formData.contactMethod"
73
+ value="email"
74
+ />
75
+ Email
76
+ </label>
77
+ <label>
78
+ <input
79
+ type="radio"
80
+ v-model="formData.contactMethod"
81
+ value="phone"
82
+ />
83
+ Phone
84
+ </label>
85
+ <label>
86
+ <input
87
+ type="radio"
88
+ v-model="formData.contactMethod"
89
+ value="mail"
90
+ />
91
+ Mail
92
+ </label>
93
+ </div>
94
+ </div>
95
+
96
+ <div class="form-actions">
97
+ <button type="submit">Submit</button>
98
+ <button type="button" @click="resetForm">Clear</button>
99
+ </div>
100
+ </form>
101
+
102
+ <div v-if="submitted" class="form-result">
103
+ <h3>Form Submitted!</h3>
104
+ <pre>{{ JSON.stringify(formData, null, 2) }}</pre>
105
+ </div>
106
+ </Section>
107
+ </template>
108
+
109
+ <script setup>
110
+ import { ref } from 'vue'
111
+ import Section from '../components/Section.vue'
112
+
113
+ const formData = ref({
114
+ name: '',
115
+ email: '',
116
+ country: '',
117
+ message: '',
118
+ newsletter: false,
119
+ contactMethod: 'email'
120
+ })
121
+
122
+ const submitted = ref(false)
123
+
124
+ function handleSubmit() {
125
+ submitted.value = true
126
+ console.log('Form submitted:', formData.value)
127
+ // In a real application, you would send this data to a server
128
+ alert('Form submitted! Check the console and the form result below.')
129
+ }
130
+
131
+ function resetForm() {
132
+ formData.value = {
133
+ name: '',
134
+ email: '',
135
+ country: '',
136
+ message: '',
137
+ newsletter: false,
138
+ contactMethod: 'email'
139
+ }
140
+ submitted.value = false
141
+ }
142
+ </script>
143
+
144
+ <style scoped>
145
+ .form-group {
146
+ margin-bottom: 1.5rem;
147
+ }
148
+
149
+ .form-group label {
150
+ display: block;
151
+ margin-bottom: 0.5rem;
152
+ font-weight: 500;
153
+ }
154
+
155
+ .form-group input[type="text"],
156
+ .form-group input[type="email"],
157
+ .form-group select,
158
+ .form-group textarea {
159
+ width: 100%;
160
+ padding: 0.5rem;
161
+ border: 1px solid #ccc;
162
+ border-radius: 0.25rem;
163
+ font-size: 1rem;
164
+ }
165
+
166
+ .form-group textarea {
167
+ resize: vertical;
168
+ font-family: inherit;
169
+ }
170
+
171
+ .form-group input[type="checkbox"],
172
+ .form-group input[type="radio"] {
173
+ margin-right: 0.5rem;
174
+ }
175
+
176
+ .form-group label:has(input[type="checkbox"]) {
177
+ display: flex;
178
+ align-items: center;
179
+ font-weight: normal;
180
+ }
181
+
182
+ .radio-group {
183
+ display: flex;
184
+ gap: 1.5rem;
185
+ flex-wrap: wrap;
186
+ }
187
+
188
+ .radio-group label {
189
+ display: flex;
190
+ align-items: center;
191
+ font-weight: normal;
192
+ }
193
+
194
+ .form-actions {
195
+ display: flex;
196
+ gap: 1rem;
197
+ margin-top: 2rem;
198
+ }
199
+
200
+ .form-actions button {
201
+ padding: 0.75rem 1.5rem;
202
+ border: none;
203
+ border-radius: 0.25rem;
204
+ font-size: 1rem;
205
+ cursor: pointer;
206
+ }
207
+
208
+ .form-actions button[type="submit"] {
209
+ background-color: #0366d6;
210
+ color: white;
211
+ }
212
+
213
+ .form-actions button[type="submit"]:hover {
214
+ background-color: #0256c2;
215
+ }
216
+
217
+ .form-actions button[type="button"] {
218
+ background-color: #6c757d;
219
+ color: white;
220
+ }
221
+
222
+ .form-actions button[type="button"]:hover {
223
+ background-color: #5a6268;
224
+ }
225
+
226
+ .form-result {
227
+ margin-top: 2rem;
228
+ padding: 1rem;
229
+ background-color: #f8f9fa;
230
+ border-radius: 0.25rem;
231
+ border: 1px solid #dee2e6;
232
+ }
233
+
234
+ .form-result h3 {
235
+ margin-top: 0;
236
+ margin-bottom: 1rem;
237
+ }
238
+
239
+ .form-result pre {
240
+ margin: 0;
241
+ padding: 1rem;
242
+ background-color: white;
243
+ border-radius: 0.25rem;
244
+ overflow-x: auto;
245
+ }
246
+
247
+ @media (prefers-color-scheme: dark) {
248
+ .form-group input[type="text"],
249
+ .form-group input[type="email"],
250
+ .form-group select,
251
+ .form-group textarea {
252
+ background-color: #333;
253
+ border-color: #555;
254
+ color: #fff;
255
+ }
256
+
257
+ .form-result {
258
+ background-color: #2d2d2d;
259
+ border-color: #444;
260
+ }
261
+
262
+ .form-result pre {
263
+ background-color: #1a1a1a;
264
+ color: #fff;
265
+ }
266
+ }
267
+ </style>
268
+
@@ -0,0 +1,65 @@
1
+ <template>
2
+ <Section title="Login" classes="">
3
+ <Login
4
+ @local-login="handleLocalLogin"
5
+ @oauth-login="handleOAuthLogin"
6
+ @tab-change="onTabChange"
7
+ />
8
+ </Section>
9
+ </template>
10
+
11
+ <script setup>
12
+ import { useRouter } from 'vue-router';
13
+ import Section from '../components/Section.vue';
14
+ import Login from '../components/Login.vue';
15
+
16
+ const router = useRouter();
17
+
18
+ // Handle local login
19
+ function handleLocalLogin(credentials) {
20
+ console.log('Local login attempt:', {
21
+ username: credentials.username,
22
+ password: '***' // Don't log passwords in production
23
+ });
24
+
25
+ // TODO: Implement actual authentication logic
26
+ // Example: Simulate API call
27
+ // try {
28
+ // const response = await fetch('/api/auth/local', {
29
+ // method: 'POST',
30
+ // headers: { 'Content-Type': 'application/json' },
31
+ // body: JSON.stringify(credentials)
32
+ // });
33
+ //
34
+ // if (response.ok) {
35
+ // const data = await response.json();
36
+ // // Store token, redirect, etc.
37
+ // router.push('/');
38
+ // } else {
39
+ // // Handle error
40
+ // }
41
+ // } catch (error) {
42
+ // // Handle error
43
+ // }
44
+
45
+ // Placeholder success
46
+ alert(`Local login would authenticate: ${credentials.username}`);
47
+ }
48
+
49
+ // Handle OAuth login
50
+ function handleOAuthLogin(provider) {
51
+ console.log('OAuth login attempt:', provider);
52
+
53
+ // TODO: Implement actual OAuth redirect logic
54
+ // Example: Redirect to OAuth provider
55
+ // window.location.href = provider.authUrl;
56
+
57
+ // Placeholder
58
+ alert(`OAuth login would redirect to: ${provider.name}`);
59
+ }
60
+
61
+ // Handle tab change
62
+ function onTabChange(tab, tabId) {
63
+ console.log('Tab changed:', tab, tabId);
64
+ }
65
+ </script>
@@ -0,0 +1,117 @@
1
+ <template>
2
+ <Section title="Login Example" subtitle="A reusable login form component with local and OAuth2 authentication">
3
+ <Login
4
+ :oauth-providers="oauthProviders"
5
+ @local-login="handleLocalLogin"
6
+ @oauth-login="handleOAuthLogin"
7
+ @tab-change="onTabChange"
8
+ />
9
+ </Section>
10
+ </template>
11
+
12
+ <script setup>
13
+ import { ref } from 'vue';
14
+ import { useRouter } from 'vue-router';
15
+ import Section from '../components/Section.vue';
16
+ import Login from '../components/Login.vue';
17
+
18
+ const router = useRouter();
19
+
20
+ // OAuth providers configuration
21
+ const oauthProviders = ref([
22
+ {
23
+ id: 'google',
24
+ name: 'Google',
25
+ icon: null, // Add icon from @hugeicons/core-free-icons if available
26
+ class: 'good',
27
+ authUrl: '/api/auth/google'
28
+ },
29
+ {
30
+ id: 'github',
31
+ name: 'GitHub',
32
+ icon: null, // Add icon from @hugeicons/core-free-icons if available
33
+ class: 'neutral',
34
+ authUrl: '/api/auth/github'
35
+ },
36
+ {
37
+ id: 'microsoft',
38
+ name: 'Microsoft',
39
+ icon: null, // Add icon from @hugeicons/core-free-icons if available
40
+ class: 'good',
41
+ authUrl: '/api/auth/microsoft'
42
+ },
43
+ {
44
+ id: 'facebook',
45
+ name: 'Facebook',
46
+ icon: null, // Add icon from @hugeicons/core-free-icons if available
47
+ class: 'good',
48
+ authUrl: '/api/auth/facebook'
49
+ },
50
+ {
51
+ id: 'twitter',
52
+ name: 'Twitter',
53
+ icon: null, // Add icon from @hugeicons/core-free-icons if available
54
+ class: 'neutral',
55
+ authUrl: '/api/auth/twitter'
56
+ },
57
+ {
58
+ id: 'apple',
59
+ name: 'Apple',
60
+ icon: null, // Add icon from @hugeicons/core-free-icons if available
61
+ class: 'neutral',
62
+ authUrl: '/api/auth/apple'
63
+ }
64
+ ]);
65
+
66
+ // Handle local login
67
+ function handleLocalLogin(credentials) {
68
+ console.log('Local login attempt:', {
69
+ username: credentials.username,
70
+ password: '***' // Don't log passwords in production
71
+ });
72
+
73
+ // TODO: Implement actual authentication logic
74
+ // Example: Simulate API call
75
+ // try {
76
+ // const response = await fetch('/api/auth/local', {
77
+ // method: 'POST',
78
+ // headers: { 'Content-Type': 'application/json' },
79
+ // body: JSON.stringify(credentials)
80
+ // });
81
+ //
82
+ // if (response.ok) {
83
+ // const data = await response.json();
84
+ // // Store token, redirect, etc.
85
+ // router.push('/');
86
+ // } else {
87
+ // // Handle error - you can use the component's setLocalLoginError method
88
+ // }
89
+ // } catch (error) {
90
+ // // Handle error
91
+ // }
92
+
93
+ // Placeholder success
94
+ alert(`Local login would authenticate: ${credentials.username}`);
95
+ }
96
+
97
+ // Handle OAuth login
98
+ function handleOAuthLogin(provider) {
99
+ console.log('OAuth login attempt:', provider);
100
+
101
+ // TODO: Implement actual OAuth redirect logic
102
+ // Example: Redirect to OAuth provider
103
+ // window.location.href = provider.authUrl;
104
+
105
+ // Placeholder
106
+ alert(`OAuth login would redirect to: ${provider.name}`);
107
+ }
108
+
109
+ // Handle tab change
110
+ function onTabChange(tab, tabId) {
111
+ console.log('Tab changed:', tab, tabId);
112
+ }
113
+ </script>
114
+
115
+ <style scoped>
116
+ /* Additional styles if needed */
117
+ </style>