├── src
├── scss
│ ├── global
│ │ ├── _a11y.scss
│ │ ├── _boxmodel.scss
│ │ ├── _headlines.scss
│ │ ├── _settings.scss
│ │ └── _fonts.scss
│ ├── components
│ │ ├── _content.scss
│ │ ├── _top-bar.scss
│ │ └── _dialog.scss
│ ├── objects
│ │ ├── _containers.scss
│ │ ├── _link.scss
│ │ ├── _button.scss
│ │ └── _table.scss
│ ├── base.scss
│ └── utils
│ │ └── _a11y.scss
├── views
│ ├── About.vue
│ ├── ProductListing.vue
│ ├── Settings.vue
│ └── Orders.vue
├── components
│ ├── Logline.vue
│ ├── Hint.vue
│ ├── Logo.vue
│ ├── UserActions.vue
│ ├── ShoppingCartButton.vue
│ ├── ProgressBar.vue
│ ├── Navigation.vue
│ ├── accessibleapp
│ │ ├── AccessibleAppInfo.vue
│ │ └── components
│ │ │ └── SwitchButton.vue
│ ├── AccountNavigationMenu.vue
│ ├── Menu.vue
│ ├── ProductTable.vue
│ └── ShoppingCartMenu.vue
├── main.js
├── store.js
├── router.js
└── App.vue
├── babel.config.js
├── public
├── favicon.ico
└── index.html
├── vue.config.js
├── .gitignore
├── README.md
└── package.json
/src/scss/global/_a11y.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/src/scss/components/_content.scss:
--------------------------------------------------------------------------------
1 | .c-content {
2 | max-width: $width-max-content;
3 | }
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/accessible-app/vuejs/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/scss/global/_boxmodel.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | box-sizing: border-box;
5 | }
--------------------------------------------------------------------------------
/src/scss/objects/_containers.scss:
--------------------------------------------------------------------------------
1 | .o-layout-inner {
2 | max-width: $width-max;
3 | margin: auto;
4 | padding: 0 1rem;
5 | }
--------------------------------------------------------------------------------
/src/scss/global/_headlines.scss:
--------------------------------------------------------------------------------
1 | h1,
2 | h2,
3 | h3,
4 | h4,
5 | h5,
6 | h6 {
7 | margin-top: 0;
8 | font-weight: normal;
9 | }
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | lintOnSave: undefined,
3 |
4 | css: {
5 | loaderOptions: {
6 | sass: {
7 | data: '@import "@/scss/global/_settings.scss";'
8 | }
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/views/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
This is an about page
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw*
22 |
--------------------------------------------------------------------------------
/src/scss/global/_settings.scss:
--------------------------------------------------------------------------------
1 | // Colors
2 | $color-brand-primary: #2368a2;
3 | $textcolor-brand-primary: #ffffff;
4 |
5 | $brand-white: #ffffff;
6 |
7 | $textcolor-content: #373a3c;
8 | $textcolor-content-link: $color-brand-primary;
9 |
10 | // Dimensions
11 | $width-max: 62.5rem;
12 | $width-max-content: 65rem;
13 |
--------------------------------------------------------------------------------
/src/scss/global/_fonts.scss:
--------------------------------------------------------------------------------
1 | html {
2 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif;
3 | font-size: 16px;
4 | -ms-text-size-adjust: 100%;
5 | -webkit-text-size-adjust: 100%;
6 | -moz-osx-font-smoothing: grayscale;
7 | -webkit-font-smoothing: antialiased;
8 |
9 | @media (max-width: 37.5em) {
10 | font-size: 14px;
11 | }
12 | }
--------------------------------------------------------------------------------
/src/scss/base.scss:
--------------------------------------------------------------------------------
1 | @import 'global/boxmodel';
2 | @import 'global/headlines';
3 | @import 'global/fonts';
4 | @import 'global/a11y';
5 |
6 | @import 'objects/containers';
7 | @import 'objects/link';
8 | @import 'objects/button';
9 | @import 'objects/table';
10 |
11 | @import 'components/top-bar';
12 | @import 'components/content';
13 | @import 'components/dialog';
14 |
15 | @import 'utils/a11y';
16 |
--------------------------------------------------------------------------------
/src/components/Logline.vue:
--------------------------------------------------------------------------------
1 |
2 | The vue.js demo of accessible-app.com
3 |
4 |
5 |
10 |
11 |
21 |
--------------------------------------------------------------------------------
/src/scss/objects/_link.scss:
--------------------------------------------------------------------------------
1 | .o-link {
2 |
3 | &,
4 | &:active,
5 | &:visited {
6 | padding-bottom: 2px;
7 | text-decoration: underline;
8 | color: $textcolor-content-link;
9 | font-weight: 700;
10 | }
11 |
12 | &:hover,
13 | &:focus {
14 | color: $textcolor-brand-primary;
15 | box-shadow: inset 0 1.25em 0 0 $textcolor-content-link;
16 | outline: 0;
17 | text-decoration: none;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Hint.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vuejs implentation of accessible-app.
2 |
3 | **[FIND THE DEMO HERE](https://vuejs.accessible-app.com)**
4 |
5 | ## Project setup
6 | ```
7 | npm install
8 | ```
9 |
10 | ### Compiles and hot-reloads for development
11 | ```
12 | npm run serve
13 | ```
14 |
15 | ### Compiles and minifies for production
16 | ```
17 | npm run build
18 | ```
19 |
20 | ### Run your tests
21 | ```
22 | npm run test
23 | ```
24 |
25 | ### Lints and fixes files
26 | ```
27 | npm run lint
28 | ```
29 |
30 | ### Customize configuration
31 | See [Configuration Reference](https://cli.vuejs.org/config/).
32 |
--------------------------------------------------------------------------------
/src/components/Logo.vue:
--------------------------------------------------------------------------------
1 |
2 | Accessibooks
3 |
4 |
5 |
10 |
11 |
31 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 |
3 | import App from './App.vue';
4 |
5 | import store from './store';
6 | import router from './router';
7 |
8 | import A11yDialog from 'vue-a11y-dialog';
9 | import PortalVue from 'portal-vue';
10 | import VueAnnouncer from 'vue-announcer'
11 |
12 | Vue.use(VueAnnouncer);
13 | Vue.use(PortalVue);
14 | Vue.use(A11yDialog);
15 |
16 | Vue.config.productionTip = false;
17 |
18 | Vue.filter('toEUR', function (value) {
19 | return "€ " + (value / 100).toLocaleString("de-DE", {minimumFractionDigits: 2});
20 | });
21 |
22 | new Vue({
23 | store,
24 | router,
25 | render: h => h(App)
26 | }).$mount('#app');
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Accessibooks - Products
9 |
10 |
11 |
12 | We're sorry but vue-app doesn't work properly without JavaScript enabled. Please enable it to continue.
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/views/ProductListing.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The place for great books on web accessibility
5 |
6 |
Following books are available:
7 |
8 |
9 |
10 |
11 |
29 |
--------------------------------------------------------------------------------
/src/views/Settings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
My settings
4 |
5 |
11 | Disable animations
13 |
14 |
15 |
16 |
33 |
--------------------------------------------------------------------------------
/src/components/UserActions.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
28 |
29 |
37 |
--------------------------------------------------------------------------------
/src/scss/components/_top-bar.scss:
--------------------------------------------------------------------------------
1 | .c-top-bar {
2 | background-color: $color-brand-primary;
3 | color: $textcolor-brand-primary;
4 | height: 6.75rem;
5 |
6 | @media (max-width: 37.5em) {
7 | height: auto;
8 | }
9 |
10 | &__wrapper {
11 | display: flex;
12 | justify-content: space-between;
13 | align-items: flex-end;
14 | height: inherit;
15 | }
16 |
17 | &__footer {
18 | display: flex;
19 | justify-content: space-between;
20 | align-items: center;
21 | height: 5rem;
22 | margin-bottom: 1rem;
23 |
24 | @media (max-width: 37.5em) {
25 | margin-bottom: 0;
26 | }
27 | }
28 |
29 | &__wrapper,
30 | &__footer {
31 | @media (max-width: 37.5em) {
32 | flex-direction: column;
33 | justify-content: center;
34 | align-items: flex-start;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/scss/utils/_a11y.scss:
--------------------------------------------------------------------------------
1 | .u-visually-hidden {
2 | clip: rect(1px 1px 1px 1px);
3 | clip: rect(1px, 1px, 1px, 1px);
4 | height: 1px;
5 | overflow: hidden;
6 | position: absolute;
7 | white-space: nowrap;
8 | width: 1px;
9 | }
10 |
11 | .u-button-link-look {
12 | border: none;
13 | -webkit-appearance: none;
14 | background: transparent;
15 | padding: 0;
16 | font-size: inherit;
17 | font-family: inherit;
18 | cursor: pointer;
19 |
20 | &:focus {
21 | outline: 0;
22 | }
23 | }
24 |
25 | [tabindex="-1"]:focus {
26 | outline: none;
27 | }
28 |
29 | @media (prefers-reduced-motion: reduce) {
30 |
31 | * {
32 | animation: none !important;
33 | -webkit-animation: none !important;
34 | }
35 | }
36 |
37 | .user-prefers-reduced-motion-reduce {
38 |
39 | * {
40 | animation: none !important;
41 | -webkit-animation: none !important;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/ShoppingCartButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 | {{ label }}
7 |
8 |
9 |
10 |
35 |
--------------------------------------------------------------------------------
/src/components/ProgressBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
38 |
39 |
44 |
--------------------------------------------------------------------------------
/src/scss/objects/_button.scss:
--------------------------------------------------------------------------------
1 | .o-button,
2 | [data-vue-details-summmary],
3 | [data-vue-menu-button] {
4 | -webkit-appearance: none;
5 | background: transparent;
6 | font-size: inherit;
7 | font-family: inherit;
8 | border-radius: 4px;
9 | padding: .5rem .75rem;
10 | border: 1px solid $color-brand-primary;
11 | color: $color-brand-primary;
12 | opacity: 1;
13 | transition: opacity .2s;
14 |
15 | &[disabled] {
16 | opacity: .33;
17 | }
18 |
19 | &:not([disabled]):hover,
20 | &:not([disabled]):focus {
21 | color: $textcolor-brand-primary;
22 | background-color: $color-brand-primary;
23 | outline: 0;
24 | }
25 |
26 | & + & {
27 | margin-left: 1rem;
28 | }
29 | }
30 |
31 | .o-button--secondary {
32 | font-size: .75rem;
33 | padding: .33rem;
34 | min-width: 9rem;
35 | margin-right: 1rem;
36 | margin-bottom: .5rem;
37 |
38 | & + & {
39 | margin: 0 .5rem 0 0;
40 | }
41 | }
42 |
43 | .o-button--bare {
44 | padding: .25rem .5rem;
45 | color: $color-brand-primary;
46 | display: inline-flex;
47 |
48 | &:focus,
49 | &:hover {
50 | color: #ffffff;
51 | }
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/src/scss/objects/_table.scss:
--------------------------------------------------------------------------------
1 | .o-table {
2 | overflow-y: auto;
3 | width: 100%;
4 | background-image: linear-gradient(to right, white, white), linear-gradient(to right, white, white), linear-gradient(to right, rgba(0, 0, 20, .25), rgba(255, 255, 255, 0)), linear-gradient(to left, rgba(0, 0, 20, .25), rgba(255, 255, 255, 0));
5 | background-position: left center, right center, left center, right center;
6 | background-repeat: no-repeat;
7 | background-color: white;
8 | background-size: 25px 100%, 25px 100%, 12px 100%, 12px 100%;
9 | background-attachment: local, local, scroll, scroll;
10 |
11 | &__table {
12 | border: 2px solid rgba(235, 235, 235, 0.99);
13 | border-collapse: collapse;
14 | width: 100%;
15 | }
16 |
17 | &::-webkit-scrollbar {
18 | -webkit-appearance: none;
19 | width: 14px;
20 | height: 14px;
21 | }
22 |
23 | &::-webkit-scrollbar-thumb {
24 | border-radius: 8px;
25 | border: 3px solid #fff;
26 | background-color: rgba(0, 0, 0, .3);
27 | }
28 |
29 | &__cell--data,
30 | &__cell--header {
31 | text-align: left;
32 | padding: .75rem;
33 | }
34 |
35 | &__cell--price {
36 | text-align: right;
37 | }
38 |
39 | &__cell--header {
40 | padding-right: 2rem;
41 | background-color: rgba(235, 235, 235, 0.99);
42 | }
43 | }
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import Vuex from "vuex";
3 |
4 | Vue.use(Vuex);
5 |
6 | const store = new Vuex.Store({
7 | state: {
8 | enhancedFocus: false,
9 | reducedMotion: false,
10 | shoppingCartItems: []
11 | },
12 | mutations: {
13 | toggleEnhancedFocus(state) {
14 | state.enhancedFocus = !state.enhancedFocus;
15 | },
16 | toggleReducedMotion(state) {
17 | state.reducedMotion = !state.reducedMotion;
18 | },
19 | toggleShoppingCartState(state, product) {
20 | if (state.shoppingCartItems.includes(product)) {
21 | // Remove from shopping cart
22 | state.shoppingCartItems = state.shoppingCartItems.filter(
23 | item => item !== product
24 | );
25 | } else {
26 | // Add to shopping cart
27 | state.shoppingCartItems.push(product);
28 | }
29 | },
30 | removeAllShoppingCartItems(state) {
31 | state.shoppingCartItems = [];
32 | }
33 | },
34 | getters: {
35 | getEnhancedFocus: state => {
36 | return state.enhancedFocus;
37 | },
38 | getReducedMotion: state => {
39 | return state.reducedMotion;
40 | },
41 | getProductIsInShoppingCart: (state) => (product) => {
42 | return state.shoppingCartItems.includes(product);
43 | },
44 | shoppingCartItems: state => {
45 | return state.shoppingCartItems;
46 | }
47 | }
48 | });
49 |
50 | export default store;
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vue-cli-service serve",
7 | "serve": "vue-cli-service serve",
8 | "build": "vue-cli-service build",
9 | "lint": "vue-cli-service lint"
10 | },
11 | "dependencies": {
12 | "details-element-polyfill": "^2.3.0",
13 | "portal-vue": "^1.5.0",
14 | "vue": "^2.5.17",
15 | "vue-a11y-dialog": "^0.3.1",
16 | "vue-announcer": "^1.0.4",
17 | "vue-router": "^3.0.1",
18 | "vuex": "^3.0.1"
19 | },
20 | "devDependencies": {
21 | "@vue/cli-plugin-babel": "^3.2.0",
22 | "@vue/cli-plugin-eslint": "^3.2.0",
23 | "@vue/cli-service": "^4.1.2",
24 | "babel-eslint": "^10.0.1",
25 | "eslint": "^5.8.0",
26 | "eslint-plugin-vue": "^5.0.0-0",
27 | "node-sass": "^4.11.0",
28 | "sass-loader": "^7.1.0",
29 | "vue-cli-plugin-scss-base": "^0.1.10",
30 | "vue-template-compiler": "^2.5.17"
31 | },
32 | "eslintConfig": {
33 | "root": true,
34 | "env": {
35 | "node": true
36 | },
37 | "extends": [
38 | "plugin:vue/essential",
39 | "eslint:recommended"
40 | ],
41 | "parserOptions": {
42 | "parser": "babel-eslint"
43 | }
44 | },
45 | "postcss": {
46 | "plugins": {
47 | "autoprefixer": {}
48 | }
49 | },
50 | "browserslist": [
51 | "> 1%",
52 | "last 2 versions",
53 | "not ie <= 8"
54 | ]
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/Navigation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Main Menu
4 |
5 | Products
6 | About
7 |
8 |
9 |
10 |
11 |
16 |
17 |
64 |
--------------------------------------------------------------------------------
/src/components/accessibleapp/AccessibleAppInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Accessible App Debugging
4 |
5 | Show red focus indicator on everything, even non-interactive
6 | elements
8 |
9 |
10 |
11 |
28 |
29 |
60 |
--------------------------------------------------------------------------------
/src/components/AccountNavigationMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Account
5 |
6 |
7 |
8 |
9 | Past Orders
12 |
13 |
14 | My Settings
17 |
18 |
19 |
20 |
21 |
22 |
23 |
32 |
33 |
74 |
--------------------------------------------------------------------------------
/src/router.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import Router from "vue-router";
3 | import ProductListing from "./views/ProductListing.vue";
4 |
5 | Vue.use(Router);
6 |
7 | export default new Router({
8 | routes: [
9 | {
10 | path: "/",
11 | name: "home",
12 | component: ProductListing,
13 | meta: { title: "Accessibooks - Products" }
14 | },
15 | {
16 | path: "/about",
17 | name: "about",
18 | // route level code-splitting
19 | // this generates a separate chunk (about.[hash].js) for this route
20 | // which is lazy-loaded when the route is visited.
21 | component: () =>
22 | import(/* webpackChunkName: "about" */ "./views/About.vue"),
23 | meta: { title: "Accessibooks - About us" }
24 | },
25 | {
26 | path: "/settings",
27 | name: "settings",
28 | // route level code-splitting
29 | // this generates a separate chunk (about.[hash].js) for this route
30 | // which is lazy-loaded when the route is visited.
31 | component: () =>
32 | import(/* webpackChunkName: "about" */ "./views/Settings.vue"),
33 | meta: { title: "Accessibooks - My Settings" }
34 | },
35 | {
36 | path: "/orders",
37 | name: "orders",
38 | // route level code-splitting
39 | // this generates a separate chunk (about.[hash].js) for this route
40 | // which is lazy-loaded when the route is visited.
41 | component: () =>
42 | import(/* webpackChunkName: "about" */ "./views/Orders.vue"),
43 | meta: { title: "Accessibooks - My past orders" }
44 | }
45 | ]
46 | });
47 |
--------------------------------------------------------------------------------
/src/components/accessibleapp/components/SwitchButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 | {{ onLabel }} {{ offLabel }}
11 |
12 |
13 |
14 |
15 |
50 |
51 |
74 |
--------------------------------------------------------------------------------
/src/views/Orders.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
This is the orders page
4 |
5 | Lorem Ipsum is simply dummy text of the printing and typesetting industry.
6 | It has been the industry's standard dummy text ever since the 1500s, when
7 | an unknown printer took a galley of type and scrambled it to make a type
8 | specimen book. It has survived not only five centuries, but also the leap
9 | into electronic typesetting, remaining essentially unchanged. It was
10 | popularised in the 1960s with the release of Letraset sheets containing
11 | Lorem Ipsum passages, and more recently with desktop publishing software
12 | like Aldus PageMaker including versions of Lorem Ipsum.
13 |
14 |
15 | Click here to query our database for your past orders
16 |
17 |
18 |
Loading progress:
19 |
20 |
21 |
22 |
Your past orders
23 |
24 | Accessibility For Everyone
25 | Inclusive Components
26 | Inclusive Design Patterns
27 | Pro HTML5 Accessibility
28 | Color Accessibility Workflows
29 |
30 |
31 |
32 |
33 |
34 |
85 |
--------------------------------------------------------------------------------
/src/components/Menu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 |
20 |
21 |
22 |
23 |
24 |
25 |
69 |
70 |
135 |
--------------------------------------------------------------------------------
/src/scss/components/_dialog.scss:
--------------------------------------------------------------------------------
1 | /* -------------------------------------------------------------------------- *\
2 | * Necessary styling for the dialog to work
3 | * -------------------------------------------------------------------------- */
4 |
5 | /**
6 | * When `` is properly supported, the overlay is implied and can be
7 | * styled with `::backdrop`, which means the DOM one should be removed.
8 | */
9 | [data-a11y-dialog-native] .dialog-overlay {
10 | display: none;
11 | }
12 |
13 | /**
14 | * When `` is not supported, its default display is `inline` which can
15 | * cause layout issues.
16 | */
17 | dialog[open] {
18 | display: block;
19 | }
20 |
21 | .dialog[aria-hidden="true"] {
22 | display: none;
23 | }
24 |
25 | /* -------------------------------------------------------------------------- *\
26 | * Styling to make the dialog look like a dialog
27 | * -------------------------------------------------------------------------- */
28 |
29 | .dialog-overlay {
30 | z-index: 2;
31 | background-color: rgba(0, 0, 0, 0.66);
32 | position: fixed;
33 | top: 0;
34 | left: 0;
35 | bottom: 0;
36 | right: 0;
37 | }
38 |
39 | dialog::backdrop {
40 | background-color: rgba(0, 0, 0, 0.66);
41 | }
42 |
43 | .dialog-content {
44 | background-color: rgb(255, 255, 255);
45 | z-index: 3;
46 | position: fixed;
47 | top: 50%;
48 | left: 50%;
49 | -webkit-transform: translate(-50%, -50%);
50 | -ms-transform: translate(-50%, -50%);
51 | transform: translate(-50%, -50%);
52 | margin: 0;
53 | }
54 |
55 | /* -------------------------------------------------------------------------- *\
56 | * Extra dialog styling to make it shiny
57 | * -------------------------------------------------------------------------- */
58 |
59 | @keyframes fade-in {
60 | from { opacity: 0; }
61 | to { opacity: 1; }
62 | }
63 |
64 | @keyframes appear {
65 | from { transform: translate(-50%, -40%); opacity: 0; }
66 | to { transform: translate(-50%, -50%); opacity: 1; }
67 | }
68 |
69 | .dialog:not([aria-hidden='true']) > .dialog-overlay {
70 | animation: fade-in 200ms 1 both;
71 | }
72 |
73 | .dialog:not([aria-hidden='true']) > .dialog-content {
74 | animation: appear 400ms 150ms 1 both;
75 | }
76 |
77 | .dialog-content {
78 | padding: 1em;
79 | max-width: 90%;
80 | width: 600px;
81 | border-radius: 2px;
82 | }
83 |
84 |
85 | @media screen and (min-width: 700px) {
86 | .dialog-content {
87 | padding: 2em;
88 | }
89 | }
90 |
91 | .dialog-overlay {
92 | background-color: rgba(43, 46, 56, 0.9);
93 | }
94 |
95 | .dialog h1 {
96 | margin: 0 0 1rem;
97 | font-size: 1.25em;
98 | }
99 |
100 | .dialog-close {
101 | position: absolute;
102 | top: 0.5em;
103 | right: 0.5em;
104 | border: 0;
105 | padding: 0;
106 | background-color: transparent;
107 | font-weight: bold;
108 | font-size: 1.25em;
109 | width: 1.2em;
110 | height: 1.2em;
111 | text-align: center;
112 | cursor: pointer;
113 | transition: 0.15s;
114 |
115 | &:focus {
116 | outline: 1px dashed #2368a2;
117 | line-height: 0;
118 | padding: 0;
119 | }
120 | }
121 |
122 | @media screen and (min-width: 700px) {
123 | .dialog-close {
124 | top: 1em;
125 | right: 1em;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
107 |
108 |
111 |
--------------------------------------------------------------------------------
/src/components/ProductTable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{ book.title }}
17 |
18 | {{ book.author }}
19 | {{ book.published }}
20 |
21 | {{ book.price | toEUR }}
22 |
23 |
24 |
28 | More Info
29 |
30 |
31 |
32 |
46 |
47 | {{ book.title }}
48 |
49 |
50 | This is an awesome book from {{ book.author }}, published
51 | {{ book.published }}. Go to
52 | {{ book.info }} to
53 | learn more.
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/src/components/ShoppingCartMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 | {{ numberOfCartItems }} items
11 |
12 |
13 |
14 | Shopping Cart (empty)
16 |
17 |
18 |
19 |
20 |
57 |
61 | Remove all items
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
115 |
116 |
175 |
--------------------------------------------------------------------------------