Skip to content

Commit

Permalink
UI overhaul (#142)
Browse files Browse the repository at this point in the history
* Flat cards restyle

* Project Selection overhaul

* Styling refactor to custom base classes and base components

* Added viewer extensions

* Composition API changes
  • Loading branch information
fabianlinkflink authored Mar 10, 2025
1 parent c1eed5a commit 4726060
Show file tree
Hide file tree
Showing 109 changed files with 4,600 additions and 5,758 deletions.
4 changes: 4 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width initial-scale=1.0" />
<title>SpeckLCA</title>

<link href="https://fonts.googleapis.com/css2?family=Lexend+Mega&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Major+Mono+Display&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Merriweather&display=swap" rel="stylesheet"><link href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet">
</head>

<body>
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"postcss": "^8.4.31",
"prettier": "^2.8.4",
"tailwindcss": "^3.3.5",
"tailwindcss-bg-patterns": "^0.3.0",
"typescript": "~4.8.4",
"vite": "^4.5.2",
"vitest": "^3.0.5",
Expand Down
67 changes: 24 additions & 43 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,50 +1,31 @@
<template>
<div id="app">
<pacman-loader/>
<router-view />
</div>
<div id="app">
<router-view />
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { useRoute } from 'vue-router'
import { logMessageToSentry } from './utils/monitoring'
import pacmanLoader from '@/components/Misc/PacmanLoader.vue'
<script setup lang="ts">
import { onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { logMessageToSentry } from './utils/monitoring'
/**
* The main application component.
*/
export default defineComponent({
name: 'SpeckLCA',
components: {
pacmanLoader,
},
setup() {
const route = useRoute()
const route = useRoute()
/**
* Checks if the route has been redirected from another route.
*
* @param {Object} route - The route object.
* @returns {boolean} - Returns true if the route has been redirected, otherwise false.
*/
if (route.redirectedFrom !== null) {
const originalPath = route.redirectedFrom
const redirectPath = route.path
const query = route.query
const params = route.params
// Check for route redirects on mount
onMounted(() => {
if (route.redirectedFrom) {
const originalPath = route.redirectedFrom
const redirectPath = route.path
const query = route.query
const params = route.params
// Log the redirect route to Sentry as a warning message.
logMessageToSentry(
`Route redirected from ${originalPath} to ${redirectPath} with query params ${JSON.stringify(
query
)} and params ${JSON.stringify(params)}`,
'warning'
)
}
return {
route
}
}
})
// Log the redirect route to Sentry as a warning message
logMessageToSentry(
`Route redirected from ${originalPath} to ${redirectPath} with query params ${JSON.stringify(
query
)} and params ${JSON.stringify(params)}`,
'warning'
)
}
})
</script>
29 changes: 29 additions & 0 deletions src/components/Base/ActionButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<template>
<button
:aria-label="ariaLabel"
class="relative flex items-center justify-center whitespace-nowrap p-2 h-8 text-sm styled-element hoverable-sm pressable"
@click="$emit('onClick')"
:style="{ backgroundColor: navStore.activeColor }"
>
<span class="flex items-center gap-2">
<p>{{ text }}</p>
<component :is="icon" class="h-5 w-5" />
</span>
</button>
</template>

<script setup lang="ts">
import { useNavigationStore } from '@/stores/navigation'
const navStore = useNavigationStore()
defineProps<{
text: string
icon?: any
ariaLabel?: string
}>()
defineEmits<{
onClick: []
}>()
</script>
51 changes: 51 additions & 0 deletions src/components/Base/BaseChevron.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<template>
<div class="chevron-container relative" :style="containerStyle">
<div
:class="[
'absolute z-40 translate-x-0 translate-y-0 group-hover:translate-x-2 group-hover:translate-y-2 group-focus-within:translate-x-2 group-focus-within:translate-y-2 transition-all delay-150 duration-300 ease-in-out'
]"
:style="outerArrowStyle"
></div>
<div
:class="[
'absolute z-40 translate-x-0 translate-y-0 group-hover:translate-x-2 group-hover:translate-y-2 group-focus-within:translate-x-2 group-focus-within:translate-y-2 transition-all delay-150 duration-300 ease-in-out'
]"
:style="innerArrowStyle"
></div>
</div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps({
width: { type: Number, default: 15 },
height: { type: Number, default: 20 },
rotation: { type: Number, default: 0 },
innerColor: { type: String, default: '#000' }
})
const containerStyle = computed(() => ({
width: (props.width + 2) * 2 + 'px',
height: (props.height - 3) + 'px'
}))
const outerArrowStyle = computed(() => ({
right: "-3px",
left: "-3px",
top: "-2px",
width: 0,
height: 0,
borderLeft: (props.width + 4) + 'px solid transparent',
borderRight: (props.width + 4) + 'px solid transparent',
borderTop: (props.height + 6) + 'px solid black'
}))
const innerArrowStyle = computed(() => ({
width: 0,
height: 0,
borderLeft: props.width + 'px solid transparent',
borderRight: props.width + 'px solid transparent',
borderTop: props.height + `px solid ${props.innerColor}`
}))
</script>
72 changes: 72 additions & 0 deletions src/components/Base/BaseToggle.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<template>
<Switch
:style="toggleStyle"
:class="baseClasses"
@click="handleClick"
>
<span class="sr-only">{{ label }}</span>
<span :style="knobStyle" :class="knobClasses">
<span :style="iconInactiveStyle" :class="iconWrapperClasses" aria-hidden="true">
<slot name="inactive" />
</span>
<span :style="iconActiveStyle" :class="iconWrapperClasses" aria-hidden="true">
<slot name="active" />
</span>
</span>
</Switch>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { Switch } from '@headlessui/vue'
const props = defineProps({
active: { type: Boolean, default: false },
label: { type: String, default: 'Toggle' },
activeColor: { type: String, default: '#16a34a' },
inactiveColor: { type: String, default: '#e5e7eb' },
transitionDuration: { type: String, default: '200ms' },
baseClasses: {
type: String,
default: 'relative inline-flex mt-4 mb-2 h-8 w-16 styled-element cursor-pointer transition-colors ease-in-out'
},
knobClasses: {
type: String,
default: 'pointer-events-none relative ml-[1px] styled-element inline-block h-7 w-7 transform transition ease-in-out'
},
iconWrapperClasses: {
type: String,
default: 'absolute inset-0 flex h-full w-full items-center justify-center transition-opacity'
},
activeIconOpacity: { type: Number, default: 1 },
inactiveIconOpacity: { type: Number, default: 0 }
})
const emits = defineEmits(['change'])
const handleClick = () => {
emits('change', !props.active)
}
const toggleStyle = computed(() => ({
backgroundColor: props.active ? props.activeColor : "white",
transition: `background-color ${props.transitionDuration} ease-in-out`
}))
const knobStyle = computed(() => ({
transform: props.active ? 'translateX(2rem) translateY(0.05rem)' : 'translateX(0) translateY(0.05rem)',
backgroundColor: props.active ? props.inactiveColor : props.activeColor,
transition: `transform ${props.transitionDuration} ease-in-out`
}))
const iconInactiveStyle = computed(() => ({
opacity: props.active ? props.inactiveIconOpacity : 1,
color: props.active ? props.inactiveColor : props.activeColor,
transition: `opacity ${props.active ? '100ms ease-out' : '200ms ease-in'}`
}))
const iconActiveStyle = computed(() => ({
opacity: props.active ? 1 : 0,
color: props.active ? props.activeColor : props.inactiveColor,
transition: `opacity ${props.active ? '200ms ease-in' : '100ms ease-out'}`
}))
</script>
24 changes: 24 additions & 0 deletions src/components/Base/CheckBox.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<template>
<input
:id="id"
:name="name"
:value="value"
type="checkbox"
class="h-4 w-4 styled-element rounded-none text-black focus:ring-0 focus:outline-none"
:checked="checked"
@change="$emit('update:checked', ($event.target as HTMLInputElement).checked)"
/>
</template>

<script setup lang="ts">
const { value = "" } = defineProps<{
id: string
name: string
value?: string
checked: boolean
}>()
defineEmits<{
'update:checked': [boolean]
}>()
</script>
106 changes: 106 additions & 0 deletions src/components/Base/Dropdown.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<!-- eslint-disable vue/multi-word-component-names -->
<template>
<div class="relative inline-block text-left min-w-30">
<div class="max-w-xs">
<button
@click="toggleDropdown"
class="inline-flex w-full justify-between px-6 py-1 text-sm truncate max-w-80 styled-element hoverable-sm"
:style="{ backgroundColor: navStore.activeColor}"
>
<div class="flex items-center space-x-2 truncate">
{{ selectedItem }}
</div>
<ChevronDownIcon class="-mr-1 h-5 w-5 text-black" aria-hidden="true" />
</button>
</div>

<transition
enter-active-class="transition ease-out duration-100"
enter-from-class="transform opacity-0 scale-95"
enter-to-class="transform opacity-100 scale-100"
leave-active-class="transition ease-in duration-75"
leave-from-class="transform opacity-100 scale-100"
leave-to-class="transform opacity-0 scale-95"
>
<div
v-if="isOpen"
class="absolute z-[100] mt-2 w-full origin-top-right styled-element"
:style="{ backgroundColor: navStore.activeColor}"
>
<div class="py-1 max-h-60 overflow-y-auto">
<DropdownMenuItem
v-for="item in items"
:key="item.name"
:item="item"
:selectedItem="selectedItem"
@select="select"
/>
</div>
</div>
</transition>
</div>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue'
import { ChevronDownIcon } from '@heroicons/vue/20/solid'
import DropdownMenuItem from '@/components/Base/DropdownMenuItem.vue'
import { useNavigationStore } from '@/stores/navigation'

export interface dropdownItem {
name: string
data?: string
children?: dropdownItem[]
}

interface Props {
items: dropdownItem[]
dropdownName?: string
}

const navStore = useNavigationStore()

const props = withDefaults(defineProps<Props>(), {
dropdownName: 'Options'
})

const emit = defineEmits<{
(e: 'selectedItem', item: dropdownItem): void
}>()

const selectedItem = ref(props.dropdownName)
const isOpen = ref(false)

const select = (item: dropdownItem) => {
selectedItem.value = item.name
isOpen.value = false
emit('selectedItem', item)
}

const toggleDropdown = () => {
isOpen.value = !isOpen.value
}

// Watch for changes in dropdownName
watch(
() => props.dropdownName,
(newValue) => {
selectedItem.value = newValue
}
)

// Watch for changes in items array
watch(
() => props.items,
(newItems) => {
if (!newItems.some((item) => item.name === selectedItem.value)) {
if (newItems.length > 0) {
//selectedItem.value = newItems[0].name
} else {
selectedItem.value = props.dropdownName
}
}
},
{ immediate: true }
)
</script>
Loading

0 comments on commit 4726060

Please sign in to comment.