diff options
author | vnugent <public@vaughnnugent.com> | 2023-07-12 01:28:23 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2023-07-12 01:28:23 -0400 |
commit | f64955c69d91e578e580b409ba31ac4b3477da96 (patch) | |
tree | 16f01392ddf1abfea13d7d1ede3bfb0459fe8f0d /front-end/src/bootstrap |
Initial commit
Diffstat (limited to 'front-end/src/bootstrap')
-rw-r--r-- | front-end/src/bootstrap/Environment.vue | 106 | ||||
-rw-r--r-- | front-end/src/bootstrap/components/ConfirmPrompt.vue | 69 | ||||
-rw-r--r-- | front-end/src/bootstrap/components/CookieWarning.vue | 36 | ||||
-rw-r--r-- | front-end/src/bootstrap/components/Footer.vue | 69 | ||||
-rw-r--r-- | front-end/src/bootstrap/components/Header.vue | 162 | ||||
-rw-r--r-- | front-end/src/bootstrap/components/PasswordPrompt.vue | 110 | ||||
-rw-r--r-- | front-end/src/bootstrap/index.ts | 113 | ||||
-rw-r--r-- | front-end/src/bootstrap/style/all.scss | 14 | ||||
-rw-r--r-- | front-end/src/bootstrap/style/buttons.scss | 44 | ||||
-rw-r--r-- | front-end/src/bootstrap/style/etc.scss | 67 | ||||
-rw-r--r-- | front-end/src/bootstrap/style/footer.scss | 67 | ||||
-rw-r--r-- | front-end/src/bootstrap/style/header.scss | 40 | ||||
-rw-r--r-- | front-end/src/bootstrap/style/headings.scss | 32 | ||||
-rw-r--r-- | front-end/src/bootstrap/style/inputs.scss | 65 | ||||
-rw-r--r-- | front-end/src/bootstrap/style/modals.scss | 29 | ||||
-rw-r--r-- | front-end/src/bootstrap/style/toast.scss | 26 |
16 files changed, 1049 insertions, 0 deletions
diff --git a/front-end/src/bootstrap/Environment.vue b/front-end/src/bootstrap/Environment.vue new file mode 100644 index 0000000..c077869 --- /dev/null +++ b/front-end/src/bootstrap/Environment.vue @@ -0,0 +1,106 @@ +<template> + <head> + <title>{{ metaTile }}</title> + </head> + <div id="env-entry" ref="content" class="absolute top-0 left-0 w-full min-h-screen env-bg"> + <div class="absolute flex w-full"> + <notifications + class="general-toast" + group="general" + position="top" + :style="generalToastStyle" + /> + </div> + <div class="absolute flex w-full"> + <notifications + class="form-toast" + group="form" + position="top" + :style="formToastStyle" + /> + </div> + + <site-header ref="header" :site-title="siteTitle" :routes="routes" > + <template #site_logo> + <!-- Use the global site-logo if enabled --> + <site-logo /> + </template> + </site-header> + + <div id="env-body" class="flex w-full" :style="bodyStyle"> + <cookie-warning :hidden="showCookieWarning" /> + + <slot name="main" /> + </div> + + <!-- Setup footer with nav elements from global config --> + <site-footer ref="footer"> + <template #footer-nav-1> + <footer-nav-1/> + </template> + <template #footer-nav-2> + <footer-nav-2/> + </template> + </site-footer> + + <PasswordPrompt /> + <ConfirmPrompt /> + </div> +</template> + +<script setup lang="ts"> + +import { computed } from 'vue' +import { RouteLocation, useRouter } from 'vue-router' +import { filter, map, without, find, includes } from 'lodash' +import { useEnvSize, useScrollOnRouteChange, useSession, useTitle } from '@vnuge/vnlib.browser' +import CookieWarning from './components/CookieWarning.vue' +import PasswordPrompt from './components/PasswordPrompt.vue' +import siteHeader from './components/Header.vue' +import siteFooter from './components/Footer.vue' +import ConfirmPrompt from './components/ConfirmPrompt.vue' +import { headerRoutes, authRoutes, siteTitle, showCookieWarning } from './index' + +const { loggedIn } = useSession(); +const { getRoutes } = useRouter(); +const { title } = useTitle(siteTitle.value); + +//Use the env size to calculate the header and footer heights for us +const { header, footer, content, headerHeight, footerHeight } = useEnvSize(true) + +//setup autoscroll +useScrollOnRouteChange(); + +//Compute meta title from the default site title and the page title +const metaTile = computed(() => title.value ? `${title.value} | ${siteTitle.value}` : siteTitle.value) + +const routes = computed<RouteLocation[]>(() => { + // Get routes that are defined above but only if they are defined in the router + // This is a computed property because loggedin is a reactive property + const routeNames = loggedIn.value ? authRoutes.value : headerRoutes.value + + const routes = filter(getRoutes(), (pageName) => includes(routeNames, pageName.name)) + + const activeRoutes = map(routeNames, route => find(routes, { name: route })) + + return without<RouteLocation>(activeRoutes, undefined) +}) + + +//Forces the page content to be exactly the height of the viewport - header and footer sizes +const bodyStyle = computed(() => { + return { 'min-height': `calc(100vh - ${headerHeight.value + footerHeight.value}px)` } +}) + +const generalToastStyle = computed(() => { + return { top: `${headerHeight.value + 5}px` } +}) + +const formToastStyle = computed(() => { + return { top: `${headerHeight.value}px` } +}) +</script> + +<style> + +</style> diff --git a/front-end/src/bootstrap/components/ConfirmPrompt.vue b/front-end/src/bootstrap/components/ConfirmPrompt.vue new file mode 100644 index 0000000..8114387 --- /dev/null +++ b/front-end/src/bootstrap/components/ConfirmPrompt.vue @@ -0,0 +1,69 @@ + +<template> + <div id="confirm-prompt"> + <Dialog class="modal-entry" :style="style" :open="isRevealed" @close="cancel" > + <div class="modal-content-container"> + <DialogPanel> + <DialogTitle class="modal-title"> + {{ title }} + </DialogTitle> + + <DialogDescription class="modal-description"> + {{ message.text }} + </DialogDescription> + + <p class="modal-text-secondary"> + {{ message.subtext }} + </p> + + <div class="modal-button-container"> + <button class="rounded btn sm primary" @click="confirm"> + Confirm + </button> + <button class="rounded btn sm" @click="cancel"> + Close + </button> + </div> + </DialogPanel> + </div> + </Dialog> + </div> +</template> + +<script setup lang="ts"> +import { defaultTo } from 'lodash' +import { computed, ref } from 'vue' + +import { + Dialog, + DialogPanel, + DialogTitle, + DialogDescription, +} from '@headlessui/vue' + +import { onClickOutside } from '@vueuse/core' +import { useConfirm, useEnvSize } from '@vnuge/vnlib.browser' + +const { headerHeight } = useEnvSize() +//Use component side of confirm +const { isRevealed, confirm, cancel, onReveal } = useConfirm() + +const dialog = ref(null) +const message = ref({}) + +//Cancel prompt when user clicks outside of dialog, only when its open +onClickOutside(dialog, () => isRevealed.value ? cancel() : null) + +//Set message on reveal +onReveal(m => message.value = m); + +const title = computed(() => defaultTo(message.value.title, 'Confirm')) + +const style = computed(() => { + return { + 'height': `calc(100vh - ${headerHeight.value}px)`, + 'top': `${headerHeight.value}px` + } +}) + +</script>
\ No newline at end of file diff --git a/front-end/src/bootstrap/components/CookieWarning.vue b/front-end/src/bootstrap/components/CookieWarning.vue new file mode 100644 index 0000000..b5239f5 --- /dev/null +++ b/front-end/src/bootstrap/components/CookieWarning.vue @@ -0,0 +1,36 @@ +<template> + <div v-if="show" class="fixed top-0 left-0 z-10 w-full" :style="style"> + <div class="flex w-full p-2 text-center text-white bg-blue-600"> + <div class="m-auto text-sm font-semibold md:text-base"> + You must have cookies enabled for this site to work properly + </div> + </div> + </div> +</template> + +<script setup lang="ts"> + +import { computed, toRefs } from 'vue' +import { useEnvSize } from '@vnuge/vnlib.browser' + +const props = defineProps<{ + hidden?: boolean +}>() + +const { hidden } = toRefs(props) + +const { headerHeight } = useEnvSize() + +const show = computed(() => (!window.navigator.cookieEnabled) && !hidden.value) + +const style = computed(() => { + return { + top: headerHeight.value + 'px' + } +}) + +</script> + +<style> + +</style> diff --git a/front-end/src/bootstrap/components/Footer.vue b/front-end/src/bootstrap/components/Footer.vue new file mode 100644 index 0000000..98f6f55 --- /dev/null +++ b/front-end/src/bootstrap/components/Footer.vue @@ -0,0 +1,69 @@ +<template> + <footer class="bottom-0 left-0 z-10 w-full"> + <div id="footer-content" class="footer-content" > + <div class="footer-main-container"> + <div class="col-span-4 sm:col-span-6 lg:col-span-3"> + <p class="my-4 text-xs leading-normal"> + No tracking, no external dependencies, no ads. You shouldn't trust anyone with your data, + and I am no exception. + </p> + </div> + <nav> + <slot name="footer-nav-1" /> + </nav> + <nav> + <slot name="footer-nav-2" /> + </nav> + <nav> + <p class="nav-title"> + Built with + </p> + <a class="footer-link" href="https://www.vaughnnugent.com/resources/software/modules">VNLib HTTP v1.0.1</a> + <a class="footer-link" href="https://tailwindcss.com/">Tailwindcss</a> + <a class="footer-link" href="https://vuejs.org/">Vuejs v3</a> + <a class="footer-link" href="https://fontawesome.com/">Font Awesome</a> + </nav> + <div class="color-selector-container"> + <p class="nav-title"> + Color Scheme + </p> + <div class="flex flex-row gap-6 md:my-auto"> + <div class=""> + <button class="bg-sel-btn" @click.prevent="Dark" > + Dark + </button> + </div> + <div class=""> + <fa-icon icon="lightbulb" /> + </div> + <div class=""> + <button class="bg-sel-btn" @click.prevent="Light"> + Light + </button> + </div> + </div> + </div> + </div> + <div class="text-sm footer-lower"> + <div class="mb-6 md:mb-0"> + <p class="text-left"> + Highly angular trousers ~ Pete Jordanson + </p> + </div> + <div class="mb-6 text-left md:mb-0"> + Copyright © 2023 Vaughn Nugent. All Rights Reserved. + </div> + </div> + </div> + </footer> +</template> + +<script setup lang="ts"> +import { useDark } from '@vueuse/core' +import { debounce } from 'lodash' + +const isDark = useDark() + +const Dark = debounce(() => isDark.value = true, 50) +const Light = debounce(() => isDark.value = false, 50) +</script> diff --git a/front-end/src/bootstrap/components/Header.vue b/front-end/src/bootstrap/components/Header.vue new file mode 100644 index 0000000..f7481a3 --- /dev/null +++ b/front-end/src/bootstrap/components/Header.vue @@ -0,0 +1,162 @@ +<!-- eslint-disable vue/max-attributes-per-line --> +<template> + <header class="sticky top-0 left-0 z-40"> + <div class="flex header-container"> + <div id="header-mobile-menu" ref="sideMenu" class="side-menu" :style="sideMenuStyle"> + <div class="pt-4 pl-4 pr-6"> + <nav id="header-mobile-nav" class="relative flex flex-col pr-3"> + <div v-for="route in routes" :key="route.path" class="m-auto ml-0"> + <div class="my-1" @click="closeSideMenu"> + <router-link :to="route"> + {{ route.name }} + </router-link> + </div> + </div> + </nav> + </div> + </div> + <div class="flex flex-row w-full md:mx-3"> + <div class="hidden w-4 lg:block" /> + <div class="flex px-4 my-auto text-xl md:hidden"> + <div v-if="!sideMenuActive" class="w-7" @click.prevent="openSideMenu"> + <fa-icon icon="bars" /> + </div> + <div v-else class="text-2xl w-7"> + <fa-icon icon="times" /> + </div> + </div> + <div id="site-title-container" class="flex m-0 mr-3"> + <div class="inline-block px-1"> + <slot name="site_logo" /> + </div> + <div id="site-title" class="inline-block m-auto mx-1"> + <router-link to="/"> + <h3>{{ siteTitle }}</h3> + </router-link> + </div> + </div> + <div class="hidden w-4 lg:block" /> + <nav id="header-desktop-nav" class="flex-row hidden mr-2 md:flex"> + <span v-for="route in routes" :key="route.fullPath" class="flex px-1 lg:px-3"> + <div v-if="!route.hide" class="m-auto"> + <router-link :to="route" class="flex-auto"> + {{ route.name }} + </router-link> + </div> + </span> + </nav> + <div id="user-menu" ref="userMenu" class="drop-controller" :class="{ 'hovered': userMenuHovered }"> + <div class="user-menu"> + Hello <span class="font-semibold">{{ uname }}</span> + </div> + <div ref="userDrop" class="absolute top-0 right-0 duration-100 ease-in-out" style="z-index:-1" :style="dropStyle"> + <div class="drop-menu" @click.prevent="userMenuHovered = false"> + <span class="space-x-2" /> + <a v-if="!loggedIn" href="#" @click="gotoRoute('/register')"> + Register + </a> + <a v-else href="#" @click="gotoRoute('/account')"> + Account + </a> + <a v-if="!loggedIn" href="#" @click="gotoRoute('/login')"> + Login + </a> + <a v-else href="#" @click.prevent="OnLogout"> + Logout + </a> + </div> + </div> + </div> + <div class="hidden space-x-4 lg:block" /> + </div> + </div> + </header> +</template> + +<script setup lang="ts"> + +import { debounce, find } from 'lodash' +import { useElementSize, onClickOutside, useElementHover } from '@vueuse/core' +import { computed, ref, toRefs } from 'vue' +import { useSession, useUser, useEnvSize, apiCall } from '@vnuge/vnlib.browser' +import { RouteLocation, useRouter } from 'vue-router'; + +const props = defineProps<{ + siteTitle: string, + routes: RouteLocation[] +}>() + +const { siteTitle, routes } = toRefs(props) + +const { loggedIn } = useSession() +const { userName, logout } = useUser() +const { headerHeight } = useEnvSize() + +//Get the router for navigation +const router = useRouter() + +const sideMenuActive = ref(false) + +const userDrop = ref(null) +const sideMenu = ref(null) +const userMenu = ref(null) + +const dropMenuSize = useElementSize(userDrop) +const sideMenuSize = useElementSize(sideMenu) +const userMenuHovered = useElementHover(userMenu) + +const uname = computed(() => userName.value || 'Visitor') +const sideMenuStyle = computed(() => { + // Side menu should be the exact height of the page and under the header, + // So menu height is the height of the page minus the height of the header + return { + height: `calc(100vh - ${headerHeight.value}px)`, + left: sideMenuActive.value ? '0' : `-${sideMenuSize.width.value}px`, + top: `${headerHeight.value}px` + } +}) + +const dropStyle = computed(() => { + return { + 'margin-top': userMenuHovered.value ? `${headerHeight.value}px` : `-${(dropMenuSize.height.value - headerHeight.value)}px` + } +}) + +const closeSideMenu = debounce(() => sideMenuActive.value = false, 50) +const openSideMenu = debounce(() => sideMenuActive.value = true, 50) + +//Close side menu when clicking outside of it +onClickOutside(sideMenu, closeSideMenu) + +//Redirect to the route when clicking on it +const gotoRoute = (route : string) =>{ + + //Get all routes from the router + const allRoutes = router.getRoutes(); + + //Try to find the route by its path + const goto = find(allRoutes, { path: route }); + + if(goto){ + //navigate to the route manually + router.push(goto); + } + else{ + //Fallback to full navigation + window.location.assign(route); + } +} + +const OnLogout = () =>{ + apiCall(async ({ toaster }) => { + await logout() + toaster.general.success({ + id: 'logout-success', + title: 'Success', + text: 'You have been logged out', + duration: 5000 + }) + }) +} + +</script>
\ No newline at end of file diff --git a/front-end/src/bootstrap/components/PasswordPrompt.vue b/front-end/src/bootstrap/components/PasswordPrompt.vue new file mode 100644 index 0000000..ae29358 --- /dev/null +++ b/front-end/src/bootstrap/components/PasswordPrompt.vue @@ -0,0 +1,110 @@ +<template> + <div id="password-prompt"> + <Dialog + class="modal-entry" + :style="style" + :open="isRevealed" + @close="close" + > + <div ref="dialog" class="modal-content-container" > + <DialogPanel> + <DialogTitle class="modal-title"> + Enter your password + </DialogTitle> + + <DialogDescription class="modal-description"> + Please re-enter your password to continue. + </DialogDescription> + + <form id="password-form" @submit.prevent="formSubmitted" :disabled="waiting"> + <fieldset> + <div class="input-container"> + <input v-model="v$.password.$model" type="password" class="rounded input primary" placeholder="Password" @input="onInput"> + </div> + </fieldset> + </form> + + <div class="modal-button-container"> + <button class="rounded btn sm primary" form="password-form"> + Submit + </button> + <button class="rounded btn sm" @click="close" > + Close + </button> + </div> + </DialogPanel> + </div> + </Dialog> + </div> +</template> + +<script setup lang="ts"> +import { onClickOutside } from '@vueuse/core' +import useVuelidate from '@vuelidate/core' +import { reactive, ref, computed } from 'vue' +import { helpers, required, maxLength } from '@vuelidate/validators' +import { useWait, useMessage, usePassConfirm, useEnvSize, useVuelidateWrapper } from '@vnuge/vnlib.browser' +import { Dialog, DialogPanel, DialogTitle, DialogDescription } from '@headlessui/vue' + +const { headerHeight } = useEnvSize() + +//Use component side of pw prompt +const { isRevealed, confirm, cancel } = usePassConfirm() + +const { waiting } = useWait() +const { onInput } = useMessage() + +//Dialog html ref +const dialog = ref(null) + +const pwState = reactive({ password: '' }) + +const rules = { + password: { + required: helpers.withMessage('Please enter your password', required), + maxLength: helpers.withMessage('Password must be less than 100 characters', maxLength(100)) + } +} + +const v$ = useVuelidate(rules, pwState, { $lazy: true }) + +//Wrap validator so we an display error message on validation, defaults to the form toaster +const { validate } = useVuelidateWrapper(v$); + +const style = computed(() => { + return { + 'height': `calc(100vh - ${headerHeight.value}px)`, + 'top': `${headerHeight.value}px` + } +}) + +const formSubmitted = async function () { + //Calls validate on the vuelidate instance + if (!await validate()) { + return + } + + //Store pw copy + const password = v$.value.password.$model; + + //Clear the password form + v$.value.password.$model = ''; + v$.value.$reset(); + + //Pass the password to the confirm function + confirm({ password }); +} + +const close = function () { + // Clear the password form + v$.value.password.$model = ''; + v$.value.$reset(); + + //Close prompt + cancel(null); +} + +//Cancel prompt when user clicks outside of dialog, only when its open +onClickOutside(dialog, () => isRevealed.value ? cancel() : null) + +</script>
\ No newline at end of file diff --git a/front-end/src/bootstrap/index.ts b/front-end/src/bootstrap/index.ts new file mode 100644 index 0000000..ead41e1 --- /dev/null +++ b/front-end/src/bootstrap/index.ts @@ -0,0 +1,113 @@ +import App from '../App.vue' +import Notifications, { notify } from '@kyvg/vue3-notification' +import { configureNotifier } from '@vnuge/vnlib.browser' +import { createApp as vueCreateApp, ref } from "vue"; +import { useDark } from "@vueuse/core"; + +//Font awesome support +import { Library } from "@fortawesome/fontawesome-svg-core"; +import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' +import { faBars, faLightbulb, faTimes } from '@fortawesome/free-solid-svg-icons' + + +//Required global state elements for app components +export const headerRoutes = ref<string[]>([]); +export const authRoutes = ref<string[]>([]); +export const siteTitle = ref<string>(""); +export const showCookieWarning = ref<boolean>(false); + + +export interface AppConfig { + /** + * Routes to be displayed in the header when the user is not logged in + */ + headerRoutes: string[]; + + /** + * Routes to be displayed in the header when the user is logged in + */ + authRoutes: string[]; + + /** + * The title of the site, used in the title bar + */ + siteTitle: string; + + /** + * Enables dark mode support + */ + useDarkMode: boolean; + + /** + * The element to mount the app to + */ + mountElement: string; + + /** + * library instance for adding required icons + */ + faLibrary: Library; + + /** + * If true, the cookie warning will not be displayed + */ + hideCookieWarning?: boolean; + + /** + * Called when the app is created for you to add custom elements + * and configure the app + * @param app The app instance + */ + onCreate: (app: any) => void; +} + +/** + * Creates a new vn-minimal vuejs web-app with the given configuration + * @param config The configuration for the app + * @returns The app instance + */ +export const createVnApp = (config: AppConfig, createApp?: (app: any) => any) => { + headerRoutes.value = config.headerRoutes; + authRoutes.value = config.authRoutes; + siteTitle.value = config.siteTitle; + + //Allow the user to override the createApp function + createApp = createApp || vueCreateApp; + + //Enable dark mode support + if (config.useDarkMode) { + useDark({ + selector: 'html', + valueDark: 'dark', + valueLight: 'light', + }); + } + + if (!config.hideCookieWarning) { + //Configure the cookie warning to be displayed cookies are not enabled + showCookieWarning.value = navigator?.cookieEnabled === false; + } + + //Add required icons to library + config.faLibrary.add(faBars, faLightbulb, faTimes); + + //create the vue app + const app = createApp(App) + + //Add the library to the app + app.component('fa-icon', FontAwesomeIcon) + + //Add the notification and router to the app + app.use(Notifications); + + //Call the onCreate callback + config.onCreate(app); + + //Mount the app + app.mount(config.mountElement); + + //Configure notification handler + configureNotifier({ notify, close: notify.close }); + + return app; +}
\ No newline at end of file diff --git a/front-end/src/bootstrap/style/all.scss b/front-end/src/bootstrap/style/all.scss new file mode 100644 index 0000000..ede0b30 --- /dev/null +++ b/front-end/src/bootstrap/style/all.scss @@ -0,0 +1,14 @@ +@tailwind base; + +@tailwind components; + +@tailwind utilities; + +@import "./buttons.scss"; +@import "./footer.scss"; +@import "./header.scss"; +@import "./inputs.scss"; +@import "./headings.scss"; +@import "./toast.scss"; +@import "./etc.scss"; +@import "./modals.scss";
\ No newline at end of file diff --git a/front-end/src/bootstrap/style/buttons.scss b/front-end/src/bootstrap/style/buttons.scss new file mode 100644 index 0000000..a440def --- /dev/null +++ b/front-end/src/bootstrap/style/buttons.scss @@ -0,0 +1,44 @@ +.button-group { + @apply inline-flex -space-x-0 divide-x overflow-hidden rounded-lg border border-transparent shadow-sm; + @apply divide-gray-300 border-gray-300 dark:divide-dark-500 dark:border-dark-500; + + & .btn { + @apply border-0 ring-0 focus:ring-0; + } +} + +.btn { + @apply ease-in-out duration-100 border-2 px-4 py-2 text-center text-sm font-medium transition-all focus:ring-2; + @apply bg-white border-gray-300 text-gray-700 shadow-sm hover:bg-gray-100 focus:ring-gray-100; + + .dark & { + @apply bg-transparent text-inherit border-dark-300 hover:bg-transparent hover:border-gray-400 focus:ring-gray-300; + } + + &.borderless, + &.no-border, + &.b-0{ + @apply bg-transparent border-0 shadow-none ring-0 focus:ring-0 active:ring-0; + } + + &:disabled { + @apply cursor-not-allowed border-gray-100 bg-gray-50 text-gray-400; + @apply dark:bg-transparent dark:border-dark-400 dark:text-dark-300; + } + + &.sm { + @apply px-3 py-1.5 text-sm; + } + + &.xs { + @apply px-2 py-1; + } + + &.lg { + @apply px-5 py-3 text-lg; + } + + &.xl { + @apply px-6 py-4 text-xl; + } +} diff --git a/front-end/src/bootstrap/style/etc.scss b/front-end/src/bootstrap/style/etc.scss new file mode 100644 index 0000000..5d65ff4 --- /dev/null +++ b/front-end/src/bootstrap/style/etc.scss @@ -0,0 +1,67 @@ +#header-mobile-nav a:hover, +#header-desktop-nav a:hover, +#header-mobile-nav .router-link-active, +#header-desktop-nav .router-link-active, +footer .footer-content .router-link-active { + @apply text-primary-500 dark:text-primary-600; +} + +#header-mobile-nav a { + @apply text-2xl; +} + +#site-title h3 { + @apply mb-0 text-xl; +} + + +.env-bg { + font-family: "Nunito"; + background: #f7f7f7; + @apply dark:bg-dark-900; + @apply text-gray-700 dark:text-gray-300; +} + +.env-bg-gradient { + background: #98E4C8; + background: -webkit-linear-gradient(bottom right, #98E4C8, #2C6BC3); + background: -moz-linear-gradient(bottom right, #98E4C8, #2C6BC3); + background: linear-gradient(to top left, #98E4C8, #2C6BC3); + @apply text-gray-700; +} + +.app-component-entry { + @apply flex flex-col flex-auto mb-10; + + @screen sm { + & { + min-height: 640px; + } + } +} + +.vue-notification-group.general-toast { + @apply right-5; +} + +.float-label { + @apply mt-5 relative; + + &>label { + @apply absolute top-0 left-0 bottom-0 block pl-3 ease-linear duration-75 opacity-0; + } + + &>input:focus+label { + @apply opacity-100 -mt-6; + } +} + +.default-page-template { + min-height: 400px; + @apply container w-full max-w-4xl px-4 pt-3 mx-auto sm:pt-6 md:px-0; +} + +a.link { + @apply duration-150 ease-in-out; + @apply text-primary-500 hover:text-primary-600; +}
\ No newline at end of file diff --git a/front-end/src/bootstrap/style/footer.scss b/front-end/src/bootstrap/style/footer.scss new file mode 100644 index 0000000..4d05928 --- /dev/null +++ b/front-end/src/bootstrap/style/footer.scss @@ -0,0 +1,67 @@ +footer{ + @apply text-center shadow-md bg-white dark:bg-dark-800 dark:text-gray-500; + + .footer-content{ + @apply mx-auto max-w-7xl p-4; + + nav, + .color-selector-container{ + @apply col-span-4 sm:col-span-3 md:col-span-2 lg:col-span-2 ml-8 sm:ml-0; + } + .footer-link.router-link-active { + @apply text-primary-500 dark:text-primary-600; + } + + .footer-main-container { + @apply grid mb-3 sm:pl-0; + @apply grid-cols-4 sm:grid-cols-6 md:grid-cols-6 lg:grid-cols-11 gap-10 lg:gap-20; + } + + p.nav-title { + @apply mb-3 text-xs font-semibold tracking-wider uppercase text-left; + } + + a.footer-link { + @apply flex mb-3 text-sm font-medium md:mb-2 text-center transform hover:scale-105 ease-in-out duration-75; + @apply hover:text-primary-500 dark:hover:text-primary-600; + } + + button.bg-sel-btn { + @apply text-sm ease-in-out duration-100 transform hover:scale-105; + @apply hover:text-primary-500 dark:hover:text-primary-600; + } + + .footer-lower { + @apply flex flex-col flex-wrap items-start justify-between pt-10 mt-10 border-t md:flex-row md:items-center dark:border-dark-500; + } + } + + .env-bg-gradient &{ + @apply text-current text-gray-700 bg-transparent; + .footer-content { + border-top: 1px solid; + @apply border-gray-200; + + .nav-title { + @apply text-gray-700; + } + + nav .footer-link, + button.bg-sel-btn{ + @apply hover:text-white; + + &.router-link-active { + @apply text-white; + } + } + + button.bg-sel-btn{ + @apply hover:text-white; + } + } + + .footer-lower { + @apply border-t-0; + } + } +}
\ No newline at end of file diff --git a/front-end/src/bootstrap/style/header.scss b/front-end/src/bootstrap/style/header.scss new file mode 100644 index 0000000..bc5789d --- /dev/null +++ b/front-end/src/bootstrap/style/header.scss @@ -0,0 +1,40 @@ +header{ + .header-container{ + @apply h-12; + } + .header-container, + .side-menu{ + @apply shadow-md; + @apply bg-white dark:bg-dark-800 dark:text-gray-100; + } + + .side-menu { + @apply absolute pt-2 ease-in-out duration-150; + } + + .drop-controller { + min-width: 11rem; + @apply hidden md:flex mr-0 ml-auto my-0 relative; + + .drop-menu{ + @apply w-36 m-auto cursor-pointer text-left rounded-b-lg flex flex-col; + + @apply text-gray-700 bg-white dark:bg-dark-800 dark:text-gray-300; + + @apply bg-white border-b border-l border-r border-transparent; + + a { + @apply px-3 py-1; + @apply text-gray-700 hover:text-black dark:text-gray-300 dark:hover:text-gray-100; + } + } + + &.hovered .drop-menu { + @apply border-gray-200 shadow-md dark:border-dark-500; + } + } + + .user-menu{ + @apply m-auto cursor-default truncate whitespace-nowrap max-w-xs; + } +}
\ No newline at end of file diff --git a/front-end/src/bootstrap/style/headings.scss b/front-end/src/bootstrap/style/headings.scss new file mode 100644 index 0000000..326a7c2 --- /dev/null +++ b/front-end/src/bootstrap/style/headings.scss @@ -0,0 +1,32 @@ +h1, +h2, +h3, +h4, +h5, +h6 { + @apply font-medium leading-tight mt-0 mb-2; +} + +h1 { + @apply sm:text-5xl text-4xl; +} + +h2 { + @apply sm:text-4xl text-3xl; +} + +h3 { + @apply sm:text-3xl text-2xl; +} + +h4 { + @apply sm:text-2xl text-xl; +} + +h5 { + @apply sm:text-xl text-lg; +} + +h6 { + @apply sm:text-base text-sm; +}
\ No newline at end of file diff --git a/front-end/src/bootstrap/style/inputs.scss b/front-end/src/bootstrap/style/inputs.scss new file mode 100644 index 0000000..64f8901 --- /dev/null +++ b/front-end/src/bootstrap/style/inputs.scss @@ -0,0 +1,65 @@ +input.input, +select.input, +textarea.input { + @apply duration-100 ease-in-out outline-none border p-2; + @apply border-gray-200 bg-inherit dark:border-dark-400 dark:text-white hover:border-gray-300 hover:dark:border-dark-200; +} + + +/* CHECKBOXES */ + +label.checkbox { + @apply flex items-center cursor-pointer; + + input[type="checkbox"] { + @apply ease-in-out duration-100 w-5 h-5; + @apply border-2 rounded-sm border-gray-300 dark:border-dark-500; + + &:checked { + @apply text-primary-500 dark:text-primary-600 border-primary-500 dark:border-primary-600; + } + } + + &.primary { + input[type="checkbox"] { + @apply appearance-none; + @apply hover:border-primary-500 dark:hover:border-primary-600; + + &:checked { + @apply bg-primary-500 dark:bg-primary-600 border-primary-500 dark:border-primary-600; + } + + &+span.check { + clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); + @apply bg-white dark:bg-dark-500; + } + } + + span.check { + margin: 0px 0px 1px 4px; + @apply absolute h-3 w-3; + } + } + +} + +/*Select */ + +select.input.options { + @apply text-current; +} + +select.input:disabled{ + @apply appearance-none; +} + +/*Validation inputs*/ +input.input.dirty.data-valid, +select.input.dirty.data-valid { + @apply border-primary-500 dark:border-primary-600; +} + +input.input.dirty.data-invalid, +select.input.dirty.data-invalid { + @apply border-red-600 dark:border-red-500; +} diff --git a/front-end/src/bootstrap/style/modals.scss b/front-end/src/bootstrap/style/modals.scss new file mode 100644 index 0000000..254b8e1 --- /dev/null +++ b/front-end/src/bootstrap/style/modals.scss @@ -0,0 +1,29 @@ +.modal-entry { + background: #00000077; + @apply fixed z-50 flex w-full px-6; + + .modal-content-container { + @apply w-full max-w-md p-5 m-auto rounded-md shadow-2xl mt-44; + @apply bg-white border border-transparent dark:bg-dark-600 dark:border-primary-500 dark:text-white; + + .modal-title { + @apply text-xl font-bold; + } + + .modal-description { + @apply text-sm; + } + } + + .modal-button-container { + @apply flex flex-row justify-end pt-3 gap-3; + } + + .input-container { + @apply pt-5; + + input { + @apply w-full; + } + } +}
\ No newline at end of file diff --git a/front-end/src/bootstrap/style/toast.scss b/front-end/src/bootstrap/style/toast.scss new file mode 100644 index 0000000..3ddc3ac --- /dev/null +++ b/front-end/src/bootstrap/style/toast.scss @@ -0,0 +1,26 @@ +.general-toast { + .notification-title { + font-size: 16px; + } + + .notification-content { + font-size: 14px; + } + + .vue-notification { + @apply duration-200 ease-in-out shadow-md hover:shadow-lg; + } +} + +.form-toast { + left: calc(50% - 150px); + @apply pt-2 mx-auto mb-3; + + .notification-title { + font-size: 14px; + } + + .notification-content { + font-size: 12px; + } +} |