picocrank 1.10.0 → 1.11.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.
@@ -0,0 +1,233 @@
1
+ <template>
2
+ <div class="tabs-container">
3
+ <div class="tabs-header" role="tablist">
4
+ <button
5
+ v-for="(tab, index) in tabs"
6
+ :key="tab.id || index"
7
+ :class="['tab-button', { active: activeTabId === (tab.id || index) }]"
8
+ :aria-selected="activeTabId === (tab.id || index)"
9
+ :aria-controls="`tab-panel-${tab.id || index}`"
10
+ :id="`tab-${tab.id || index}`"
11
+ role="tab"
12
+ @click="selectTab(tab.id || index)"
13
+ >
14
+ <HugeiconsIcon
15
+ v-if="tab.icon"
16
+ :icon="tab.icon"
17
+ width="1em"
18
+ height="1em"
19
+ />
20
+ <span>{{ tab.label }}</span>
21
+ <slot :name="`tab-${tab.id || index}-badge`" />
22
+ </button>
23
+ </div>
24
+ <div class="tabs-content">
25
+ <div
26
+ v-for="(tab, index) in tabs"
27
+ :key="tab.id || index"
28
+ :id="`tab-panel-${tab.id || index}`"
29
+ :class="['tab-panel', { active: activeTabId === (tab.id || index) }]"
30
+ role="tabpanel"
31
+ :aria-labelledby="`tab-${tab.id || index}`"
32
+ v-show="activeTabId === (tab.id || index)"
33
+ >
34
+ <slot :name="`tab-${tab.id || index}`" :tab="tab" />
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </template>
39
+
40
+ <script setup>
41
+ import { ref, onMounted, watch } from 'vue';
42
+ import { HugeiconsIcon } from '@hugeicons/vue';
43
+
44
+ const props = defineProps({
45
+ tabs: {
46
+ type: Array,
47
+ required: true,
48
+ validator: (tabs) => {
49
+ return tabs.every(tab =>
50
+ typeof tab === 'object' &&
51
+ (tab.id !== undefined || true) &&
52
+ typeof tab.label === 'string'
53
+ );
54
+ }
55
+ },
56
+ defaultTab: {
57
+ type: [String, Number],
58
+ default: null
59
+ }
60
+ });
61
+
62
+ const emit = defineEmits(['tab-change']);
63
+
64
+ // Initialize activeTabId with first tab by default
65
+ function getInitialTabId() {
66
+ if (props.defaultTab !== null) {
67
+ return props.defaultTab;
68
+ }
69
+ if (props.tabs.length > 0) {
70
+ return props.tabs[0].id ?? 0;
71
+ }
72
+ return null;
73
+ }
74
+
75
+ const activeTabId = ref(getInitialTabId());
76
+
77
+ function selectTab(tabId) {
78
+ if (activeTabId.value !== tabId) {
79
+ activeTabId.value = tabId;
80
+ const activeTab = props.tabs.find(tab => (tab.id ?? props.tabs.indexOf(tab)) === tabId);
81
+ emit('tab-change', activeTab, tabId);
82
+ }
83
+ }
84
+
85
+ function setActiveTab(tabId) {
86
+ const tab = props.tabs.find(t => (t.id ?? props.tabs.indexOf(t)) === tabId);
87
+ if (tab) {
88
+ selectTab(tabId);
89
+ }
90
+ }
91
+
92
+ // Initialize active tab on mount if not already set
93
+ onMounted(() => {
94
+ if (activeTabId.value === null && props.tabs.length > 0) {
95
+ activeTabId.value = props.tabs[0].id ?? 0;
96
+ }
97
+ });
98
+
99
+ // Watch for tabs changes
100
+ watch(() => props.tabs, (newTabs) => {
101
+ if (newTabs.length > 0 && activeTabId.value === null) {
102
+ activeTabId.value = newTabs[0].id ?? 0;
103
+ }
104
+ }, { immediate: true });
105
+
106
+ defineExpose({
107
+ setActiveTab,
108
+ activeTabId
109
+ });
110
+ </script>
111
+
112
+ <style scoped>
113
+ .tabs-container {
114
+ width: 100%;
115
+ }
116
+
117
+ .tabs-header {
118
+ display: flex;
119
+ border-bottom: 2px solid var(--border-color, #e1e5e9);
120
+ gap: 0.25rem;
121
+ overflow-x: auto;
122
+ scrollbar-width: thin;
123
+ }
124
+
125
+ .tabs-header::-webkit-scrollbar {
126
+ height: 4px;
127
+ }
128
+
129
+ .tabs-header::-webkit-scrollbar-track {
130
+ background: transparent;
131
+ }
132
+
133
+ .tabs-header::-webkit-scrollbar-thumb {
134
+ background: var(--border-color, #ccc);
135
+ border-radius: 2px;
136
+ }
137
+
138
+ .tab-button {
139
+ display: flex;
140
+ align-items: center;
141
+ gap: 0.5rem;
142
+ padding: 0.75rem 1.25rem;
143
+ background: transparent;
144
+ border: none;
145
+ border-bottom: 2px solid transparent;
146
+ cursor: pointer;
147
+ font-size: 1em;
148
+ color: var(--text-muted, #666);
149
+ transition: all 0.2s ease;
150
+ white-space: nowrap;
151
+ position: relative;
152
+ }
153
+
154
+ .tab-button:hover {
155
+ color: var(--text-color, #000);
156
+ background-color: var(--hover-background-color, #f5f5f5);
157
+ }
158
+
159
+ .tab-button.active {
160
+ color: var(--primary-color, #007bff);
161
+ border-bottom-color: var(--primary-color, #007bff);
162
+ font-weight: 500;
163
+ }
164
+
165
+ .tab-button:focus {
166
+ outline: 2px solid var(--primary-color, #007bff);
167
+ outline-offset: -2px;
168
+ border-radius: 4px 4px 0 0;
169
+ }
170
+
171
+ .tabs-content {
172
+ width: 100%;
173
+ min-height: 200px;
174
+ }
175
+
176
+ .tab-panel {
177
+ padding: 1.5rem;
178
+ display: none;
179
+ }
180
+
181
+ .tab-panel.active {
182
+ display: block;
183
+ animation: fadeIn 0.2s ease-in;
184
+ }
185
+
186
+ @keyframes fadeIn {
187
+ from {
188
+ opacity: 0;
189
+ transform: translateY(4px);
190
+ }
191
+ to {
192
+ opacity: 1;
193
+ transform: translateY(0);
194
+ }
195
+ }
196
+
197
+ /* Dark theme support */
198
+ @media (prefers-color-scheme: dark) {
199
+ .tabs-header {
200
+ border-bottom-color: var(--border-color, #444);
201
+ }
202
+
203
+ .tab-button {
204
+ color: var(--text-muted, #999);
205
+ }
206
+
207
+ .tab-button:hover {
208
+ color: var(--text-color, #fff);
209
+ background-color: var(--hover-background-color, #333);
210
+ }
211
+
212
+ .tab-button.active {
213
+ color: var(--primary-color, #60a5fa);
214
+ border-bottom-color: var(--primary-color, #60a5fa);
215
+ }
216
+ }
217
+
218
+ /* Responsive */
219
+ @media (max-width: 768px) {
220
+ .tabs-header {
221
+ gap: 0;
222
+ }
223
+
224
+ .tab-button {
225
+ padding: 0.75rem 1rem;
226
+ font-size: 0.9em;
227
+ }
228
+
229
+ .tab-panel {
230
+ padding: 1rem;
231
+ }
232
+ }
233
+ </style>
@@ -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
@@ -30,7 +30,7 @@
30
30
  <script setup>
31
31
  import { ref, onMounted, provide } from 'vue';
32
32
  import { useRouter } from 'vue-router';
33
- import { Pin02Icon } from '@hugeicons/core-free-icons'
33
+ import { Pin02Icon, SecurityValidationIcon, EditIcon, ViewIcon, HomeIcon } from '@hugeicons/core-free-icons'
34
34
 
35
35
  import QuickSearch from './../components/QuickSearch.vue'
36
36
  import Navigation from './../components/Navigation.vue'
@@ -63,11 +63,18 @@
63
63
  navigation.value.addRouterLink('TableExample')
64
64
  navigation.value.addRouterLink('CalendarExample')
65
65
  navigation.value.addRouterLink('FormExample')
66
+ navigation.value.addRouterLink('TabsExample')
66
67
  navigation.value.addSeparator('separator-1');
67
68
  navigation.value.addRouterLink('ViewItem', 'View Awesome Item', { params: { id: 1 } })
68
69
  navigation.value.addSeparator('separator-2');
69
70
  navigation.value.addCallback('Callback Example', helloWorld, { icon: Pin02Icon })
70
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');
71
78
  navigation.value.addHtml('<h2 style = "padding: 0.75em;">Administration</h2>', { name: 'admin-heading' })
72
79
  navigation.value.addRouterLink('Admin')
73
80
  }
@@ -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>