7 files added
3 files modified
| | |
| | | <template> |
| | | <ul> |
| | | <learning-resource v-for="res in storedResouces" |
| | | :key="res.id" |
| | | :title="res.title" |
| | | :description="res.description" |
| | | :link="res.link" |
| | | > |
| | | </learning-resource> |
| | | </ul> |
| | | <TheHeader :title="rememberMe"></TheHeader> |
| | | <TheResources></TheResources> |
| | | </template> |
| | | |
| | | <script> |
| | | import LearningResource from './components/learning-resource/LearningResource.vue'; |
| | | import TheHeader from './components/layouts/TheHeader.vue'; |
| | | import TheResources from './components/learning-resource/TheResources.vue'; |
| | | |
| | | export default { |
| | | components:{ |
| | | LearningResource: LearningResource, |
| | | }, |
| | | data(){ |
| | | return { |
| | | storedResouces: [ |
| | | { |
| | | id:'official-guide', |
| | | title: 'Official Guide', |
| | | description: 'The official Vue.js documentation', |
| | | link: 'https://vuejs.org' |
| | | }, |
| | | { |
| | | id:'google', |
| | | title: 'Google', |
| | | description: 'Learn to google...', |
| | | link: 'https://google.com' |
| | | }, |
| | | ] |
| | | } |
| | | } |
| | | components: { |
| | | TheHeader: TheHeader, |
| | | TheResources: TheResources, |
| | | }, |
| | | data() { |
| | | return { |
| | | rememberMe: 'Remember Me', |
| | | }; |
| | | }, |
| | | }; |
| | | </script> |
| | | |
| | | <style> |
| | | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap'); |
| | | |
| | | * { |
| | | box-sizing: border-box; |
| | | } |
| | | </script> |
| | | |
| | | html { |
| | | font-family: 'Roboto', sans-serif; |
| | | } |
| | | |
| | | body { |
| | | margin: 0; |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <button :type="type" :class="mode"> |
| | | <slot></slot> |
| | | </button> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | props: ['type', 'mode'], |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | button { |
| | | padding: 0.75rem 1.5rem; |
| | | font-family: inherit; |
| | | background-color: #3a0061; |
| | | border: 1px solid #3a0061; |
| | | color: white; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | button:hover, |
| | | button:active { |
| | | background-color: #270041; |
| | | border-color: #270041; |
| | | } |
| | | |
| | | .flat { |
| | | background-color: transparent; |
| | | color: #3a0061; |
| | | border: none; |
| | | } |
| | | |
| | | .flat:hover, |
| | | .flat:active { |
| | | background-color: #edd2ff; |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <div> |
| | | <slot></slot> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | export default {}; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | div { |
| | | border-radius: 12px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26); |
| | | padding: 1rem; |
| | | margin: 2rem auto; |
| | | max-width: 40rem; |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <teleport to="body"> |
| | | <div @click="$emit('close')"></div> |
| | | <dialog open> |
| | | <header> |
| | | <slot name="header"> |
| | | <h2>{{ title }}</h2> |
| | | </slot> |
| | | </header> |
| | | <section> |
| | | <slot></slot> |
| | | </section> |
| | | <menu> |
| | | <slot name="action"> |
| | | <base-button @click="$emit('close')"></base-button> |
| | | </slot> |
| | | </menu> |
| | | </dialog> |
| | | </teleport> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | // props: ['title'], |
| | | props: { |
| | | title: { |
| | | type: String, |
| | | required: false, |
| | | }, |
| | | }, |
| | | emits: ['close'], |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | div { |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | height: 100vh; |
| | | width: 100%; |
| | | background-color: rgba(0, 0, 0, 0.75); |
| | | z-index: 10; |
| | | } |
| | | |
| | | dialog { |
| | | position: fixed; |
| | | top: 20vh; |
| | | left: 10%; |
| | | width: 80%; |
| | | z-index: 100; |
| | | border-radius: 12px; |
| | | border: none; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26); |
| | | padding: 0; |
| | | margin: 0; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | header { |
| | | background-color: #3a0061; |
| | | color: white; |
| | | width: 100%; |
| | | padding: 1rem; |
| | | } |
| | | |
| | | header h2 { |
| | | margin: 0; |
| | | } |
| | | |
| | | section { |
| | | padding: 1rem; |
| | | } |
| | | |
| | | menu { |
| | | padding: 1rem; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | margin: 0; |
| | | } |
| | | |
| | | @media (min-width: 768px) { |
| | | dialog { |
| | | left: calc(50% - 20rem); |
| | | width: 40rem; |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <header> |
| | | <h1>{{ title }}</h1> |
| | | </header> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | props: ['title'], |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | header { |
| | | width: 100%; |
| | | height: 5rem; |
| | | background-color: #640032; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | } |
| | | |
| | | header h1 { |
| | | color: white; |
| | | margin: 0; |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <base-dialog v-if="inputIsInvalid" title="Invalid Input" @close="confirmError"> |
| | | <template #default> |
| | | <p>Sfortunatamente un input รจ invalido</p> |
| | | <p>Controlla che tutti gli input contengano qualche carattere valido.</p> |
| | | </template> |
| | | <template #action> |
| | | <base-button @click="confirmError">Okay</base-button> |
| | | </template> |
| | | </base-dialog> |
| | | <base-card> |
| | | <form @submit.prevent="submitData"> |
| | | <div class="form-control"> |
| | | <label for="title">Title</label> |
| | | <input id="title" name="title" type="text" ref="titleInput" /> |
| | | </div> |
| | | <div class="form-control"> |
| | | <label for="description">Description</label> |
| | | <textarea |
| | | id="description" |
| | | name="description" |
| | | type="text" |
| | | rows="3" |
| | | ref="descInput" |
| | | ></textarea> |
| | | </div> |
| | | <div class="form-control"> |
| | | <label for="link">Link</label> |
| | | <input id="link" name="link" type="url" ref="linkInput" /> |
| | | </div> |
| | | <div> |
| | | <base-button type="submit">Add Resource</base-button> |
| | | </div> |
| | | </form> |
| | | </base-card> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | inject: ['addResource'], |
| | | data() { |
| | | return { |
| | | inputIsInvalid: false, |
| | | }; |
| | | }, |
| | | methods: { |
| | | submitData() { |
| | | const enteredTitle = this.$refs.titleInput.value; |
| | | const enteredDescription = this.$refs.descInput.value; |
| | | const enteredUrl = this.$refs.linkInput.value; |
| | | |
| | | if ( |
| | | enteredTitle.trim() === '' || |
| | | enteredDescription.trim() === '' || |
| | | enteredUrl.trim() === '' |
| | | ) { |
| | | this.inputIsInvalid = true; |
| | | return; |
| | | } |
| | | |
| | | this.addResource(enteredTitle, enteredDescription, enteredUrl); |
| | | }, |
| | | confirmError() { |
| | | this.inputIsInvalid = false; |
| | | }, |
| | | }, |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | label { |
| | | font-weight: bold; |
| | | display: block; |
| | | margin-bottom: 0.5rem; |
| | | } |
| | | |
| | | input, |
| | | textarea { |
| | | display: block; |
| | | width: 100%; |
| | | font: inherit; |
| | | padding: 0.15rem; |
| | | border: 1px solid #ccc; |
| | | } |
| | | |
| | | input:focus, |
| | | textarea:focus { |
| | | outline: none; |
| | | border-color: #3a0061; |
| | | background-color: #f7ebff; |
| | | } |
| | | |
| | | .form-control { |
| | | margin: 1rem 0; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <li> |
| | | <div> |
| | | <header> |
| | | <h3>{{ title }}</h3> |
| | | <button>Delete</button> |
| | | </header> |
| | | </div> |
| | | <p>{{ description }}</p> |
| | | <nav> |
| | | <a :href="link">View Resource</a> |
| | | </nav> |
| | | </li> |
| | | <li> |
| | | <base-card> |
| | | <header> |
| | | <h3>{{ title }}</h3> |
| | | <base-button mode="flat" @click="deleteResource(id)" |
| | | >Delete</base-button |
| | | > |
| | | </header> |
| | | <p>{{ description }}</p> |
| | | <nav> |
| | | <a :href="link">View Resource</a> |
| | | </nav> |
| | | </base-card> |
| | | </li> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | props: ['title', 'description', 'link'] |
| | | props: ['id', 'title', 'description', 'link'], |
| | | inject: ['deleteResource'], |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | li { |
| | | margin: auto; |
| | | max-width: 40rem; |
| | | } |
| | | </script> |
| | | |
| | | header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | h3 { |
| | | font-size: 1.25rem; |
| | | margin: 0.5rem 0; |
| | | } |
| | | |
| | | p { |
| | | margin: 0.5rem 0; |
| | | } |
| | | |
| | | a { |
| | | text-decoration: none; |
| | | color: #ce5c00; |
| | | } |
| | | |
| | | a:hover, |
| | | a:active { |
| | | color: #c89300; |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <ul> |
| | | <learning-resource |
| | | v-for="res in resources" |
| | | :key="res.id" |
| | | :id="res.id" |
| | | :title="res.title" |
| | | :description="res.description" |
| | | :link="res.link" |
| | | > |
| | | </learning-resource> |
| | | </ul> |
| | | </template> |
| | | |
| | | <script> |
| | | import LearningResource from './LearningResource.vue'; |
| | | |
| | | export default { |
| | | components: { |
| | | LearningResource: LearningResource, |
| | | }, |
| | | inject: ['resources'], |
| | | }; |
| | | </script> |
| | | |
| | | <style> |
| | | ul { |
| | | list-style: none; |
| | | margin: 0; |
| | | padding: 0; |
| | | margin: auto; |
| | | max-width: 40rem; |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <base-card> |
| | | <base-button |
| | | @click="setSelectedTab('stored-resources')" |
| | | :mode="storedResButtonMode" |
| | | >Stored Resource</base-button |
| | | > |
| | | <base-button |
| | | @click="setSelectedTab('add-resource')" |
| | | :mode="addResButtonMode" |
| | | >Add Resource</base-button |
| | | > |
| | | </base-card> |
| | | <KeepAlive> |
| | | <component :is="selectedTab"></component> |
| | | </KeepAlive> |
| | | </template> |
| | | |
| | | <script> |
| | | import StoredResources from './StoredResource.vue'; |
| | | import AddResource from './AddResource.vue'; |
| | | |
| | | export default { |
| | | components: { |
| | | StoredResources: StoredResources, |
| | | AddResource: AddResource, |
| | | }, |
| | | data() { |
| | | return { |
| | | selectedTab: 'stored-resources', |
| | | storedResouces: [ |
| | | { |
| | | id: 'official-guide', |
| | | title: 'Official Guide', |
| | | description: 'The official Vue.js documentation', |
| | | link: 'https://vuejs.org', |
| | | }, |
| | | { |
| | | id: 'google', |
| | | title: 'Google', |
| | | description: 'Learn to google...', |
| | | link: 'https://google.com', |
| | | }, |
| | | ], |
| | | }; |
| | | }, |
| | | computed: { |
| | | storedResButtonMode() { |
| | | return this.selectedTab === 'stored-resources' ? null : 'flat'; |
| | | }, |
| | | addResButtonMode() { |
| | | return this.selectedTab === 'add-resource' ? null : 'flat'; |
| | | }, |
| | | }, |
| | | provide() { |
| | | return { |
| | | resources: this.storedResouces, |
| | | addResource: this.addResource, |
| | | deleteResource: this.removeResource, |
| | | }; |
| | | }, |
| | | methods: { |
| | | setSelectedTab(tab) { |
| | | this.selectedTab = tab; |
| | | }, |
| | | addResource(title, description, url) { |
| | | const newResource = { |
| | | id: new Date().toISOString(), |
| | | title: title, |
| | | description: description, |
| | | link: url, |
| | | }; |
| | | |
| | | this.storedResouces.unshift(newResource); |
| | | this.selectedTab = 'stored-resources'; |
| | | }, |
| | | removeResource(resId) { |
| | | const resIndex = this.storedResouces.findIndex( |
| | | (res) => res.id === resId |
| | | ); |
| | | this.storedResouces.splice(resIndex, 1); |
| | | }, |
| | | }, |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped></style> |
| | |
| | | import { createApp } from 'vue'; |
| | | |
| | | import App from './App.vue'; |
| | | import BaseCard from './components/UI/BaseCard.vue'; |
| | | import BaseButton from './components/UI/BaseButton.vue'; |
| | | import BaseDialog from './components/UI/BaseDialog.vue'; |
| | | |
| | | createApp(App).mount('#app'); |
| | | const app = createApp(App); |
| | | |
| | | app.component('base-card', BaseCard); |
| | | app.component('base-button', BaseButton); |
| | | app.component('base-dialog', BaseDialog); |
| | | |
| | | app.mount('#app'); |
| | | |