picocrank 1.27.0 → 1.28.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "picocrank",
3
- "version": "1.27.0",
3
+ "version": "1.28.0",
4
4
  "type": "module",
5
5
  "description": "A set of zero dependency Vue components built on the Femtocrank global CSS theme.",
6
6
  "main": "vite.config.js",
@@ -0,0 +1,71 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import { createRouter, createMemoryHistory } from 'vue-router'
4
+ import Header from './Header.vue'
5
+
6
+ const router = createRouter({
7
+ history: createMemoryHistory(),
8
+ routes: [
9
+ { path: '/login', name: 'Login', component: { template: '<div>Login</div>' } },
10
+ ],
11
+ })
12
+
13
+ const globalStubs = {
14
+ HugeiconsIcon: true,
15
+ Breadcrumbs: true,
16
+ TopBar: true,
17
+ Sidebar: true,
18
+ }
19
+
20
+ describe('Header loginRoute', () => {
21
+ it('shows a Login link when username is empty and loginRoute is set', async () => {
22
+ const wrapper = mount(Header, {
23
+ props: {
24
+ username: '',
25
+ loginRoute: { name: 'Login' },
26
+ sidebarEnabled: false,
27
+ },
28
+ global: {
29
+ plugins: [router],
30
+ stubs: globalStubs,
31
+ },
32
+ })
33
+
34
+ const loginLink = wrapper.find('a.login-link')
35
+ expect(loginLink.exists()).toBe(true)
36
+ expect(loginLink.text()).toBe('Login')
37
+ })
38
+
39
+ it('shows the username control instead of Login when username is set', () => {
40
+ const wrapper = mount(Header, {
41
+ props: {
42
+ username: 'tester',
43
+ loginRoute: { name: 'Login' },
44
+ sidebarEnabled: false,
45
+ },
46
+ global: {
47
+ plugins: [router],
48
+ stubs: globalStubs,
49
+ },
50
+ })
51
+
52
+ expect(wrapper.find('a.login-link').exists()).toBe(false)
53
+ expect(wrapper.find('.user-info span').text()).toBe('tester')
54
+ })
55
+
56
+ it('shows neither Login nor username when loginRoute is not set', () => {
57
+ const wrapper = mount(Header, {
58
+ props: {
59
+ username: '',
60
+ sidebarEnabled: false,
61
+ },
62
+ global: {
63
+ plugins: [router],
64
+ stubs: globalStubs,
65
+ },
66
+ })
67
+
68
+ expect(wrapper.find('a.login-link').exists()).toBe(false)
69
+ expect(wrapper.find('.user-info').exists()).toBe(false)
70
+ })
71
+ })
@@ -55,6 +55,13 @@
55
55
  <HugeiconsIcon :icon="UserIcon" width="1em" height="1em" aria-hidden="true" />
56
56
  <span>{{ username }}</span>
57
57
  </button>
58
+ <RouterLink
59
+ v-else-if="showLoginLink"
60
+ :to="loginRoute"
61
+ class="user-info flex-row neutral login-link"
62
+ >
63
+ <span>Login</span>
64
+ </RouterLink>
58
65
  </slot>
59
66
  </div>
60
67
  </header>
@@ -65,6 +72,7 @@
65
72
 
66
73
  <script setup>
67
74
  import { computed, ref } from "vue";
75
+ import { RouterLink } from "vue-router";
68
76
  import { HugeiconsIcon } from "@hugeicons/vue";
69
77
  import { BulbIcon, Menu01Icon, Moon02Icon, Sun01Icon, UserIcon } from "@hugeicons/core-free-icons";
70
78
 
@@ -110,6 +118,10 @@
110
118
  type: String,
111
119
  default: "",
112
120
  },
121
+ loginRoute: {
122
+ type: [String, Object],
123
+ default: null,
124
+ },
113
125
  title: {
114
126
  type: String,
115
127
  default: "Untitled",
@@ -155,6 +167,10 @@
155
167
  () => props.breadcrumbs && !isMobile.value,
156
168
  );
157
169
 
170
+ const showLoginLink = computed(
171
+ () => !props.username.trim() && props.loginRoute != null && props.loginRoute !== '',
172
+ );
173
+
158
174
  function toggleNavSidebar() {
159
175
  if (needsFallbackSidebar.value) {
160
176
  fallbackSidebar.value?.toggle();
@@ -201,6 +217,11 @@
201
217
  font-weight: normal;
202
218
  }
203
219
 
220
+ .login-link {
221
+ text-decoration: none;
222
+ color: inherit;
223
+ }
224
+
204
225
  .user-info span {
205
226
  padding-right: 0;
206
227
  font-weight: normal;
@@ -218,6 +239,7 @@
218
239
 
219
240
  /* Match Femtocrank header chrome (same idea as #sidebar-button:hover) */
220
241
  .header-actions > button,
242
+ .header-actions > a.login-link,
221
243
  .header-actions :deep(button) {
222
244
  border: 0;
223
245
  border-radius: 0;
@@ -233,6 +255,7 @@
233
255
  }
234
256
 
235
257
  .header-actions > button:hover,
258
+ .header-actions > a.login-link:hover,
236
259
  .header-actions :deep(button:hover),
237
260
  .user-info:hover {
238
261
  background-color: var(--header-hover-background-color);
@@ -298,4 +321,8 @@ header .search-trigger:hover {
298
321
  header .user-info {
299
322
  font-weight: normal;
300
323
  }
324
+
325
+ header .login-link {
326
+ font-weight: normal;
327
+ }
301
328
  </style>
@@ -2,7 +2,8 @@
2
2
  <Navigation ref="navigation">
3
3
  <Navigation ref="topBarNavigation">
4
4
  <Header
5
- username="Guest"
5
+ :username="headerUsername"
6
+ :login-route="headerLoginRoute"
6
7
  @toggleSidebar="toggleSidebar"
7
8
  @logoClick="goToIndex"
8
9
  @userClick="goToUserDetails"
@@ -85,6 +86,8 @@
85
86
  const includeSupplementalThemes = ref(true);
86
87
  const topBarEnabled = ref(false);
87
88
  const breadcrumbsEnabled = ref(true);
89
+ const headerUsername = ref('tester');
90
+ const headerLoginRoute = { name: 'Login' };
88
91
 
89
92
  const { discoverThemes } = initCustomTheme({ includeSupplementalThemes: true });
90
93
 
@@ -134,6 +137,10 @@
134
137
  breadcrumbsEnabled.value = !breadcrumbsEnabled.value;
135
138
  }
136
139
 
140
+ function setHeaderUsername(username) {
141
+ headerUsername.value = username;
142
+ }
143
+
137
144
  async function dummyFetchResults(_query, { signal } = {}) {
138
145
  await new Promise((resolve, reject) => {
139
146
  const timer = setTimeout(resolve, 1500)
@@ -200,6 +207,8 @@
200
207
  provide('toggleTopBarEnabled', toggleTopBarEnabled);
201
208
  provide('breadcrumbsEnabled', breadcrumbsEnabled);
202
209
  provide('toggleBreadcrumbsEnabled', toggleBreadcrumbsEnabled);
210
+ provide('headerUsername', headerUsername);
211
+ provide('setHeaderUsername', setHeaderUsername);
203
212
 
204
213
  watch(quickSearchRef, (quickSearch) => {
205
214
  if (quickSearch) {
@@ -102,6 +102,23 @@
102
102
  @update:model-value="setBreadcrumbsEnabled"
103
103
  />
104
104
  </FormField>
105
+
106
+ <FormField label="Header username" component-has-label>
107
+ <div>
108
+ <RadioGroup
109
+ name="ps-header-username"
110
+ variant="list"
111
+ aria-label="Header username"
112
+ :model-value="headerUsername"
113
+ :options="headerUsernameOptions"
114
+ @update:model-value="setHeaderUsernameValue"
115
+ />
116
+ <p class="subtle">
117
+ When no username is set, the header shows a Login link if
118
+ <code>loginRoute</code> is configured.
119
+ </p>
120
+ </div>
121
+ </FormField>
105
122
  </FormLayout>
106
123
  </Section>
107
124
 
@@ -174,6 +191,11 @@ const onOffOptions = [
174
191
  { label: 'Off', value: false },
175
192
  ]
176
193
 
194
+ const headerUsernameOptions = [
195
+ { label: 'No username', value: '' },
196
+ { label: 'tester', value: 'tester' },
197
+ ]
198
+
177
199
  const sidebarEnabled = inject('sidebarEnabled')
178
200
  const toggleSidebarEnabled = inject('toggleSidebarEnabled')
179
201
  const brandingEnabled = inject('brandingEnabled')
@@ -190,6 +212,8 @@ const topBarEnabled = inject('topBarEnabled')
190
212
  const toggleTopBarEnabled = inject('toggleTopBarEnabled')
191
213
  const breadcrumbsEnabled = inject('breadcrumbsEnabled')
192
214
  const toggleBreadcrumbsEnabled = inject('toggleBreadcrumbsEnabled')
215
+ const headerUsername = inject('headerUsername')
216
+ const setHeaderUsername = inject('setHeaderUsername')
193
217
  const navigation = inject('navigation', null)
194
218
 
195
219
  const exampleLinksEnabled = ref(false)
@@ -228,6 +252,10 @@ function setBreadcrumbsEnabled(want) {
228
252
  setFlag(breadcrumbsEnabled, toggleBreadcrumbsEnabled, want)
229
253
  }
230
254
 
255
+ function setHeaderUsernameValue(username) {
256
+ setHeaderUsername(username)
257
+ }
258
+
231
259
  function setSidebarEnabled(want) {
232
260
  setFlag(sidebarEnabled, toggleSidebarEnabled, want)
233
261
  }