Skip to content

Commit 491f8cf

Browse files
authored
Merge pull request #2 from markteekman/develop
Develop
2 parents fb67f50 + 46c5108 commit 491f8cf

8 files changed

Lines changed: 150 additions & 67 deletions

File tree

README.md

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Accessible Astro Dashboard
22

3-
This Dashboard Theme is build upon the (awesome) [Astro Static Site Builder](https://astro.build/). This theme offers a couple of Accessibility components and some utility classes to get you building your project faster. Also check out the [Accessible Astro Components](https://github.com/markteekman/accessible-astro-components) npm package which can be used with (or without) this starter or the [Accessible Astro Starter](https://github.com/markteekman/accessible-astro-starter) theme, which comes with a blog and other accessibility features!
3+
This Dashboard theme is build upon the (awesome) [Astro Static Site Builder](https://astro.build/). This theme offers a couple of Accessibility components, a couple of example pages, some admin pages, a custom 404 page and some utility classes to get you building your project faster. Also check out the [Accessible Astro Components](https://github.com/markteekman/accessible-astro-components) npm package which can be used with (or without) this starter!
44

55
[Live demo](https://dashboard.accessible-astro.dev)
66

@@ -12,23 +12,20 @@ npm install && npm start
1212

1313
## (Accessibility) features
1414

15-
In this Dashboard Theme you'll find a couple of things:
16-
17-
- Utilizes Astro's experimental SSR feature to redirect users to `/login` is there not logged in
18-
- Uses `localStorage` to set a value of `isLoggedIn` to true when you log in using the login form
19-
- Contains a `login.astro` page with an example login using a separate `LoginLayout.astro` layout
20-
- `login.astro` contains example login data and a warning notification when the credentials are incorrect
15+
- Contains a `login.astro` page with an example login using the `localStorage` and a separate `LoginLayout.astro` layout
16+
- `login.astro` contains example login data and a warning notification for when the credentials are incorrect
2117
- Contains a dashboard example in the `index.astro` page using the `DefaultLayout.astro` layout
2218
- Several examples of admin pages such as `media.astro`, `messages.astro`, `products.astro`, `settings.astro` and `users.astro`
2319
- `Button.astro` component with simple, accessible styling and a property for `type="submit"`
2420
- `LoginForm.astro` component with a basic accessible login form and some form controls
2521
- `SkipLinks.astro` component to skip to either the main menu or the main content
26-
- `Navigation.astro` component with keyboard accessible (dropdown) navigation (arrow keys, escape key), which is expandable on desktop
22+
- `Navigation.astro` component with keyboard accessible navigation (arrow keys, escape key)
2723
- This component is a comprehensive sidebar navigation on desktop with the option to expand or collapse
2824
- The users menu width preference is stored in a `localStorage` value so that it is preserved during page reloads
2925
- The navigation automatically switches to an accessible mobile navigation for viewport widths below the medium breakpoint
30-
- `ResponsiveToggle.astro` component with an accessible responsive toggle button for the navigation
26+
- `ResponsiveToggle.astro` component with an accessible responsive toggle button for the mobile navigation
3127
- `DarkMode.astro` component toggle with accessible button which saves the users preference in the `localStorage`
28+
- `DashboardWidget.astro` component serves as an example for the dashboard on `index.astro`
3229
- `EmpyState.astro` component which can be displayed on pages that don't have any data yet
3330
- `404.astro` provides a custom 404 error page which you can adjust to your needs
3431
- `.sr-only` utility class for screen reader only text content
@@ -39,16 +36,22 @@ In this Dashboard Theme you'll find a couple of things:
3936

4037
## Login & Authentication
4138

42-
This theme contains an example login flow, using a fake email address and password and by utilizing the `localStorage` and Astro's (experimental) SSR feature. All pages redirect to `/login.astro` if you haven't logged in using `Astro.redirect`. After logging in the value `isLoggedIn` is set to `true` and your authenticated to view the admin pages.
39+
This starter contains a basic example of authentication and redirecting based on a users logged in status. However, this is done using SSG, which is not ideal, but serves the purpose of this demo. For better authentication and redirecting you should use Astro's (experimental) SSR.
4340

44-
⚠️ **Note: this is just an example, make sure you build your own secure authentication. Checkout this [official Astro Blog post](https://astro.build/blog/experimental-server-side-rendering/) for more information about authentication and login.**
41+
### Enabling SSR
42+
43+
For the purpose of the demo I have not enabled SSR, simply due to the fact how I've setup the demo websites of all [accessible-astro.dev](https://accessible-astro.dev) subdomains. However, I might add it in the future. It is the preferred way to handle your login and redirect cases.
4544

4645
### LoginForm.astro
4746

47+
This theme contains an example login flow, using a fake email address and password and by utilizing the `localStorage`. All pages redirect to `/login.astro` if you haven't logged in. After logging in the value `isLoggedIn` is set to `true` and your authenticated to view the admin pages. When building this using SSR you should use `cookies` instead of the `localStorage`.
48+
49+
⚠️ **Note: this is just an example, make sure you build your own secure authentication. Checkout this [official Astro Blog post](https://astro.build/blog/experimental-server-side-rendering/) for more information about authentication and login.**
50+
4851
```js
4952
<script>
5053
// fetch your data from an API
51-
// and replace that an actual user object for example
54+
// and replace that with an actual user object for example
5255

5356
submitButton.addEventListener('click', event => {
5457
event.preventDefault()
@@ -65,9 +68,11 @@ submitButton.addEventListener('click', event => {
6568

6669
```js
6770
---
68-
let isLoggedIn = localStorage.getItem('isLoggedIn')
71+
import { getUser } from '../api/index.js'
72+
73+
const user = await getUser(Astro.request)
6974

70-
if (isLoggedIn !== 'true') {
75+
if (!user) {
7176
return Astro.redirect('/login')
7277
}
7378
---
@@ -233,14 +238,20 @@ If you need an exception on your font-size for a specific reason you can use siz
233238
</div>
234239
```
235240

241+
## Other Accessible Astro projects
242+
243+
- [Accessible Astro Starter](https://github.com/markteekman/accessible-astro-starter/)
244+
- [Accessible Astro Components](https://github.com/markteekman/accessible-astro-components/)
245+
- [Accessible Astro Documentation](https://accessible-astro.dev)
246+
236247
## Helping out
237248

238249
If you find that something isn't working right then I'm always happy to hear it to improve this starter! You can contribute in many ways and forms. Let me know by either:
239250

240-
1. [Filing an issue](https://github.com/markteekman/accessible-astro-starter/issues)
241-
2. [Submitting a pull request](https://github.com/markteekman/accessible-astro-starter/pulls)
242-
3. [Starting a discussion](https://github.com/markteekman/accessible-astro-starter/discussions)
243-
4. [Buying me a coffee!](https://www.buymeacoffee.com/markteekman) Help keep the demo's and docs up and running 😊
251+
1. [Filing an issue](https://github.com/markteekman/accessible-astro-dashboard/issues)
252+
2. [Submitting a pull request](https://github.com/markteekman/accessible-astro-dashboard/pulls)
253+
3. [Starting a discussion](https://github.com/markteekman/accessible-astro-dashboard/discussions)
254+
4. [Buying me a coffee!](https://www.buymeacoffee.com/markteekman)
244255

245256
## Thank you!
246257

astro.config.mjs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { defineConfig } from 'astro/config'
2-
import nodejs from '@astrojs/node'
32

43
// https://astro.build/config
54
export default defineConfig({
6-
adapter: nodejs(),
75
vite: {
86
ssr: {
97
external: ['svgo']

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515
"astro-icon": "^0.7.1"
1616
},
1717
"devDependencies": {
18-
"@astrojs/node": "^0.1.1",
19-
"astro": "^1.0.0-beta.24",
18+
"astro": "^1.0.0-beta.26",
2019
"autoprefixer": "^10.4.0",
2120
"sass": "^1.49.9",
2221
"stylelint": "^13.13.1",
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
const {
3+
number = '+54',
4+
title = 'New messages',
5+
type = 'success'
6+
} = Astro.props
7+
---
8+
9+
<div class="dashboard-widget radius-large space-24 elevation-400 space-content type-${type}">
10+
<p class="size-48">
11+
<strong>{number}</strong>
12+
</p>
13+
<p class="size-20">
14+
<em>{title}</em>
15+
</p>
16+
</div>
17+
18+
<style lang="scss">
19+
.dashboard-widget {
20+
&.type-success {
21+
> p:first-child {
22+
color: var(--success-700);
23+
}
24+
}
25+
26+
&.type-error {
27+
> p:first-child {
28+
color: var(--error-700);
29+
}
30+
}
31+
32+
background-color: var(--neutral-100);
33+
}
34+
35+
:global(.darkmode .dashboard-widget) {
36+
background-color: var(--neutral-900);
37+
38+
&.type-success {
39+
> p:first-child {
40+
color: var(--success-400);
41+
}
42+
}
43+
44+
&.type-error {
45+
> p:first-child {
46+
color: var(--error-400);
47+
}
48+
}
49+
}
50+
</style>

src/components/Navigation.astro

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,25 @@ import { Icon } from 'astro-icon'
77
<div class="desktop-menu">
88
<nav>
99
<ul>
10-
<li class="menu-item brand-logo">
11-
<a href="/">
12-
<img src={(await import('../assets/img/logo.svg')).default} alt="Your Logo">
13-
</a>
10+
<li class="brand-logo">
11+
<img src={(await import('../assets/img/logo.svg')).default} >
12+
<span class="sr-only">Brand Name</span>
1413
</li>
1514
<li class="menu-item">
16-
<button class="toggle-expanded-view">
15+
<button class="toggle-expanded-view" aria-expanded="false">
1716
<Icon pack="majesticons" name="chevron-double-right-line" />
18-
<div class="sr-only">Collapse</div>
17+
<span class="sr-only">Expand menu</span>
1918
</button>
2019
</li>
2120
<slot />
2221
<li class="menu-item bottom-position">
23-
<a href="/login">
22+
<a href="/login/">
2423
<Icon pack="majesticons" name="logout-line" />
2524
<span class="sr-only">Logout</span>
2625
</a>
2726
</li>
2827
<li class="menu-item bottom-position user-avatar">
29-
<a href="/settings">
28+
<a href="/settings/" class="logout-link">
3029
<img src="/user-avatar.png" alt="">
3130
<span class="sr-only">Settings</span>
3231
</a>
@@ -63,10 +62,10 @@ import { Icon } from 'astro-icon'
6362
// variables
6463
const mainNav = document.querySelector('#main-navigation')
6564
const mainMenu = mainNav.querySelector('ul')
66-
const toggleExpandedButton = document.querySelector('.toggle-expanded-view')
65+
const toggleExpandedView = document.querySelector('.toggle-expanded-view')
6766
const menuIconLabels = [...mainNav.querySelectorAll('.sr-only')]
6867
const mediaQuery = window.matchMedia('(min-width: 48em)')
69-
let menuIsExpanded = localStorage.getItem('menuIsExpanded')
68+
let isMenuExpanded = localStorage.getItem('isMenuExpanded')
7069

7170
// functions
7271
const setActiveMenuItem = () => {
@@ -82,16 +81,20 @@ import { Icon } from 'astro-icon'
8281

8382
const expandMenu = () => {
8483
mainNav.classList.add('is-expanded')
85-
localStorage.setItem('menuIsExpanded', 'true')
84+
localStorage.setItem('isMenuExpanded', 'true')
85+
toggleExpandedView.setAttribute('aria-expanded', 'true')
86+
toggleExpandedView.querySelector('span').textContent = 'Collpase menu'
8687

8788
menuIconLabels.forEach(menuIconLabel => {
8889
menuIconLabel.classList.remove('sr-only')
8990
})
9091
}
9192

92-
const minifyMenu = () => {
93+
const collapseMenu = () => {
9394
mainNav.classList.remove('is-expanded')
94-
localStorage.setItem('menuIsExpanded', null)
95+
localStorage.setItem('isMenuExpanded', null)
96+
toggleExpandedView.setAttribute('aria-expanded', 'false')
97+
toggleExpandedView.querySelector('span').textContent = 'Expand menu'
9598

9699
menuIconLabels.forEach(menuIconLabel => {
97100
menuIconLabel.classList.add('sr-only')
@@ -103,12 +106,12 @@ import { Icon } from 'astro-icon'
103106
mainNav.classList.add('is-desktop')
104107
mainNav.classList.remove('is-mobile')
105108

106-
minifyMenu()
109+
collapseMenu()
107110
} else {
108111
mainNav.classList.remove('is-desktop')
109112
mainNav.classList.add('is-mobile')
110113

111-
minifyMenu()
114+
collapseMenu()
112115
}
113116
}
114117

@@ -148,9 +151,9 @@ import { Icon } from 'astro-icon'
148151
}
149152
})
150153

151-
toggleExpandedButton.addEventListener('click', () => {
154+
toggleExpandedView.addEventListener('click', () => {
152155
mainNav.classList.contains('is-expanded')
153-
? minifyMenu()
156+
? collapseMenu()
154157
: expandMenu()
155158
})
156159

@@ -159,7 +162,7 @@ import { Icon } from 'astro-icon'
159162
setActiveMenuItem()
160163
checkViewportWidth()
161164

162-
if (menuIsExpanded === 'true') {
165+
if (isMenuExpanded === 'true') {
163166
expandMenu()
164167
}
165168
</script>
@@ -185,7 +188,6 @@ import { Icon } from 'astro-icon'
185188
position: fixed;
186189
z-index: 1;
187190
box-shadow: 0 0 40px rgba(0, 0, 0, 0.15);
188-
// transition: width 0.2s ease-in-out;
189191

190192
.mobile-menu {
191193
display: none;
@@ -265,8 +267,10 @@ import { Icon } from 'astro-icon'
265267
}
266268

267269
a:hover,
270+
a:focus,
271+
a.is-active,
268272
button:hover,
269-
a.is-active {
273+
button:not(.toggle-expanded-view):focus {
270274
color: var(--neutral-800);
271275
background-color: var(--secondary-200);
272276

@@ -298,26 +302,34 @@ import { Icon } from 'astro-icon'
298302
.bottom-position:nth-last-of-type(3) { bottom: 8rem; }
299303

300304
.brand-logo {
305+
display: flex;
306+
align-items: center;
307+
gap: 0.5rem;
301308
padding: 1rem;
309+
color: var(--neutral-100);
302310
background-color: var(--neutral-700);
303311

304-
a {
305-
padding: 0;
306-
307-
&:hover,
308-
&.is-active {
309-
background-color: transparent;
310-
}
311-
}
312-
313312
img {
314313
max-height: 18px;
315314
}
316315
}
317316
}
318317

319-
.toggle-expanded-view svg {
320-
padding-top: 3px;
318+
.toggle-expanded-view {
319+
svg {
320+
padding-top: 3px;
321+
}
322+
323+
span {
324+
text-decoration: underline;
325+
}
326+
327+
&:hover,
328+
&:focus {
329+
span {
330+
text-decoration: none;
331+
}
332+
}
321333
}
322334
}
323335

@@ -353,7 +365,8 @@ import { Icon } from 'astro-icon'
353365
align-items: center;
354366
gap: 0.5rem;
355367

356-
&:hover {
368+
&:hover,
369+
&:focus {
357370
color: var(--secondary-200);
358371
background-color: transparent;
359372

0 commit comments

Comments
 (0)