Final few components

This commit is contained in:
Jai A 2023-03-14 14:22:50 -07:00
parent f5cbe0c4fe
commit dcc2a5afe0
No known key found for this signature in database
GPG Key ID: CC88DE86F48BE019
12 changed files with 408 additions and 2 deletions

View File

@ -22,6 +22,8 @@ export default {
{ text: 'Card', link: '/components/card' }, { text: 'Card', link: '/components/card' },
{ text: 'Checkbox', link: '/components/checkbox' }, { text: 'Checkbox', link: '/components/checkbox' },
{ text: 'Chips', link: '/components/chips' }, { text: 'Chips', link: '/components/chips' },
{ text: 'File Input', link: '/components/file-input' },
{ text: 'Drop Area', link: '/components/drop-area' },
{ text: 'Icons', link: '/components/icons' }, { text: 'Icons', link: '/components/icons' },
{ text: 'Pagination', link: '/components/pagination' }, { text: 'Pagination', link: '/components/pagination' },
{ text: 'Project Card', link: '/components/project-card' }, { text: 'Project Card', link: '/components/project-card' },
@ -31,6 +33,7 @@ export default {
{ text: 'Text Logo', link: '/components/text-logo' }, { text: 'Text Logo', link: '/components/text-logo' },
{ text: 'Slider', link: '/components/slider' }, { text: 'Slider', link: '/components/slider' },
{ text: 'Text Inputs', link: '/components/text-inputs' }, { text: 'Text Inputs', link: '/components/text-inputs' },
{ text: 'Search Filter', link: '/components/search-filter' },
], ],
}, },
], ],

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="demo bg-raised radius-lg padding-md"><slot /></div> <div class="demo"><slot /></div>
</template> </template>
<script> <script>
@ -8,8 +8,11 @@ export default {}
<style lang="scss" scoped> <style lang="scss" scoped>
.demo { .demo {
background: var(--color-raised-bg);
border: 1px solid var(--color-button-bg); border: 1px solid var(--color-button-bg);
border-radius: var(--gap-lg);
display: flex; display: flex;
padding: var(--gap-md);
gap: var(--gap-md); gap: var(--gap-md);
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;

View File

@ -22,3 +22,26 @@
class="tags" class="tags"
/> />
</DemoContainer> </DemoContainer>
```vue
<Categories
:categories="[{
name: 'magic',
icon: '<svg viewBox=\'0 0 24 24\' fill=\'none\' stroke=\'currentColor\' stroke-width=\'2\' stroke-linecap=\'round\' stroke-linejoin=\'round\'><path d=\'M15 4V2\'></path><path d=\'M15 16v-2\'></path><path d=\'M8 9h2\'></path><path d=\'M20 9h2\'></path><path d=\'M17.8 11.8 19 13\'></path><path d=\'M15 9h0\'></path><path d=\'M17.8 6.2 19 5\'></path><path d=\'m3 21 9-9\'></path><path d=\'M12.2 6.2 11 5\'></path></svg>',
},
{
icon: '<svg viewBox=\'0 0 24 24\' fill=\'none\' stroke=\'currentColor\' stroke-width=\'2\' stroke-linecap=\'round\' stroke-linejoin=\'round\'><rect x=\'2\' y=\'7\' width=\'20\' height=\'14\' rx=\'2\' ry=\'2\'/><path d=\'M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16\'/></svg>',
name: 'utility',
},
{
icon: '<svg xmlns=\'http://www.w3.org/2000/svg\' xml:space=\'preserve\' fill-rule=\'evenodd\' stroke-linecap=\'round\' stroke-linejoin=\'round\' clip-rule=\'evenodd\' viewBox=\'0 0 24 24\'>\n <path fill=\'none\' d=\'M0 0h24v24H0z\'/>\n <path fill=\'none\' stroke=\'currentColor\' stroke-width=\'23\' d=\'m820 761-85.6-87.6c-4.6-4.7-10.4-9.6-25.9 1-19.9 13.6-8.4 21.9-5.2 25.4 8.2 9 84.1 89 97.2 104 2.5 2.8-20.3-22.5-6.5-39.7 5.4-7 18-12 26-3 6.5 7.3 10.7 18-3.4 29.7-24.7 20.4-102 82.4-127 103-12.5 10.3-28.5 2.3-35.8-6-7.5-8.9-30.6-34.6-51.3-58.2-5.5-6.3-4.1-19.6 2.3-25 35-30.3 91.9-73.8 111.9-90.8\' transform=\'matrix(.08671 0 0 .0867 -49.8 -56)\'/>\n</svg>',
name: 'fabric',
},
{
icon: '<svg xml:space=\'preserve\' fill-rule=\'evenodd\' stroke-linecap=\'round\' stroke-linejoin=\'round\' stroke-miterlimit=\'1.5\' clip-rule=\'evenodd\' viewBox=\'0 0 24 24\'>\n <path fill=\'none\' d=\'M0 0h24v24H0z\'></path>\n <path fill=\'none\' stroke=\'currentColor\' stroke-width=\'2\' d=\'M2 7.5h8v-2h12v2s-7 3.4-7 6 3.1 3.1 3.1 3.1l.9 3.9H5l1-4.1s3.8.1 4-2.9c.2-2.7-6.5-.7-8-6Z\'></path>\n</svg>',
name: 'forge'
}]"
type="mod"
class="tags"
/>
```

View File

@ -0,0 +1,11 @@
# Drop Area
<DemoContainer>
<InfoIcon /> Click to choose a file or drag one onto this page
<DropArea accept="*" />
</DemoContainer>
```vue
<InfoIcon /> Click to choose a file or drag one onto this page
<DropArea accept="*" />
```

View File

@ -0,0 +1,23 @@
# File Input
<DemoContainer>
<FileInput
:max-size="262144"
accept="image/png,image/jpeg,image/gif,image/webp"
class="btn"
prompt="Upload icon"
>
<UploadIcon />
</FileInput>
</DemoContainer>
```vue
<FileInput
:max-size="262144"
accept="image/png,image/jpeg,image/gif,image/webp"
class="btn"
prompt="Upload icon"
>
<UploadIcon />
</FileInput>
```

View File

@ -0,0 +1,53 @@
# Search Filter
<script setup>
import { ref } from "vue";
const activeFilters = ref([]);
function toggleFilter(filter) {
const index = activeFilters.value.indexOf(filter);
if (index !== -1) {
activeFilters.value.splice(index, 1)
} else {
activeFilters.value.push(filter)
}
}
</script>
<DemoContainer>
<SearchFilter
:active-filters="activeFilters"
display-name="Client"
facet-name="client"
@toggle="toggleFilter"
>
<ClientIcon aria-hidden="true" />
</SearchFilter>
</DemoContainer>
```vue
<script setup>
import { ref } from "vue";
const activeFilters = ref([]);
function toggleFilter(filter) {
const index = activeFilters.value.indexOf(filter);
if (index !== -1) {
activeFilters.value.splice(index, 1)
} else {
activeFilters.value.push(filter)
}
}
</script>
<SearchFilter
:active-filters="activeFilters"
display-name="Client"
facet-name="client"
@toggle="toggleFilter"
>
<ClientIcon aria-hidden="true" />
</SearchFilter>
```

View File

@ -0,0 +1,87 @@
<template>
<div
ref="drop_area"
class="drop-area"
@drop.stop.prevent="
(event) => {
$refs.drop_area.style.visibility = 'hidden'
if (event.dataTransfer && event.dataTransfer.files && fileAllowed) {
$emit('change', event.dataTransfer.files)
}
}
"
@dragenter.prevent="allowDrag"
@dragover.prevent="allowDrag"
@dragleave.prevent="$refs.drop_area.style.visibility = 'hidden'"
/>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
props: {
accept: {
type: String,
default: '',
},
},
emits: ['change'],
data() {
return {
fileAllowed: false,
}
},
mounted() {
document.addEventListener('dragenter', this.allowDrag)
},
methods: {
allowDrag(event) {
const file = event.dataTransfer?.items[0]
if (
file &&
this.accept
.split(',')
.reduce((acc, t) => acc || file.type.startsWith(t) || file.type === t || t === '*', false)
) {
this.fileAllowed = true
event.dataTransfer.dropEffect = 'copy'
event.preventDefault()
if (this.$refs.drop_area) {
this.$refs.drop_area.style.visibility = 'visible'
}
} else {
this.fileAllowed = false
if (this.$refs.drop_area) {
this.$refs.drop_area.style.visibility = 'hidden'
}
}
},
},
})
</script>
<style lang="scss" scoped>
.drop-area {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10;
visibility: hidden;
background-color: hsla(0, 0%, 0%, 0.5);
transition: visibility 0.2s ease-in-out, background-color 0.1s ease-in-out;
display: flex;
&::before {
--indent: 4rem;
content: ' ';
position: relative;
top: var(--indent);
left: var(--indent);
width: calc(100% - (2 * var(--indent)));
height: calc(100% - (2 * var(--indent)));
border-radius: 1rem;
border: 0.25rem dashed var(--color-button-bg);
}
}
</style>

View File

@ -0,0 +1,96 @@
<template>
<label :class="{ 'long-style': longStyle }" @drop.prevent="handleDrop" @dragover.prevent>
<slot />
{{ prompt }}
<input type="file" :multiple="multiple" :accept="accept" @change="handleChange" />
</label>
</template>
<script>
import { fileIsValid } from '@/components/utils'
import { defineComponent } from 'vue'
export default defineComponent({
props: {
prompt: {
type: String,
default: 'Select file',
},
multiple: {
type: Boolean,
default: false,
},
accept: {
type: String,
default: null,
},
/**
* The max file size in bytes
*/
maxSize: {
type: Number,
default: null,
},
showIcon: {
type: Boolean,
default: true,
},
shouldAlwaysReset: {
type: Boolean,
default: false,
},
longStyle: {
type: Boolean,
default: false,
},
},
emits: ['change'],
data() {
return {
files: [],
}
},
methods: {
addFiles(files, shouldNotReset) {
if (!shouldNotReset || this.shouldAlwaysReset) {
this.files = files
}
const validationOptions = { maxSize: this.maxSize, alertOnInvalid: true }
this.files = [...this.files].filter((file) => fileIsValid(file, validationOptions))
if (this.files.length > 0) {
this.$emit('change', this.files)
}
},
handleDrop(e) {
this.addFiles(e.dataTransfer.files)
},
handleChange(e) {
this.addFiles(e.target.files)
},
},
})
</script>
<style lang="scss" scoped>
label {
flex-direction: unset;
max-height: unset;
svg {
height: 1rem;
}
input {
display: none;
}
&.long-style {
display: flex;
padding: 1.5rem 2rem;
justify-content: center;
align-items: center;
grid-gap: 0.5rem;
background-color: var(--color-button-bg);
border-radius: var(--radius-sm);
border: dashed 0.3rem var(--color-contrast);
cursor: pointer;
color: var(--color-accent-contrast);
}
}
</style>

View File

@ -11,8 +11,11 @@ export { default as TextLogo } from './brand/TextLogo.vue'
export { default as Pagination } from './base/Pagination.vue' export { default as Pagination } from './base/Pagination.vue'
export { default as ProjectCard } from './base/ProjectCard.vue' export { default as ProjectCard } from './base/ProjectCard.vue'
export { default as EnvironmentIndicator } from './base/EnvironmentIndicator.vue' export { default as EnvironmentIndicator } from './base/EnvironmentIndicator.vue'
export { default as FileInput } from './base/FileInput.vue'
export { default as DropArea } from './base/DropArea.vue'
export { default as Categories } from './search/Categories.vue' export { default as Categories } from './search/Categories.vue'
export { default as SearchFilter } from './search/SearchFilter.vue'
export { default as NavItem } from './nav/NavItem.vue' export { default as NavItem } from './nav/NavItem.vue'
export { default as NavRow } from './nav/NavRow.vue' export { default as NavRow } from './nav/NavRow.vue'

View File

@ -39,7 +39,7 @@ export default {
flex-wrap: wrap; flex-wrap: wrap;
gap: var(--gap-sm); gap: var(--gap-sm);
span ::v-deep { :deep(span) {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
color: var(--color-icon); color: var(--color-icon);

View File

@ -0,0 +1,73 @@
<template>
<Checkbox
class="filter"
:model-value="activeFilters.includes(facetName)"
:description="displayName"
@update:model-value="toggle()"
>
<div class="filter-text">
<div v-if="icon" aria-hidden="true" class="icon" v-html="icon" />
<div v-else class="icon">
<slot />
</div>
<span aria-hidden="true"> {{ displayName }}</span>
</div>
</Checkbox>
</template>
<script>
import { defineComponent } from 'vue'
import Checkbox from '@/components/base/Checkbox.vue'
export default defineComponent({
components: {
Checkbox,
},
props: {
facetName: {
type: String,
default: '',
},
displayName: {
type: String,
default: '',
},
icon: {
type: String,
default: '',
},
activeFilters: {
type: Array,
default() {
return []
},
},
},
emits: ['toggle'],
methods: {
toggle() {
this.$emit('toggle', this.facetName)
},
},
})
</script>
<style lang="scss" scoped>
.filter {
margin-bottom: 0.5rem;
:deep(.filter-text) {
display: flex;
align-items: center;
.icon {
height: 1rem;
svg {
margin-right: 0.25rem;
width: 1rem;
height: 1rem;
}
}
}
span {
user-select: none;
}
}
</style>

View File

@ -189,3 +189,34 @@ export function cycleValue(value, values) {
const index = values.indexOf(value) + 1 const index = values.indexOf(value) + 1
return values[index % values.length] return values[index % values.length]
} }
export const fileIsValid = (file, validationOptions) => {
const { maxSize, alertOnInvalid } = validationOptions
if (maxSize !== null && maxSize !== undefined && file.size > maxSize) {
if (alertOnInvalid) {
alert(`File ${file.name} is too big! Must be less than ${formatBytes(maxSize)}`)
}
return false
}
return true
}
export const acceptFileFromProjectType = (projectType) => {
switch (projectType) {
case 'mod':
return '.jar,.zip,.litemod,application/java-archive,application/x-java-archive,application/zip'
case 'plugin':
return '.jar,.zip,application/java-archive,application/x-java-archive,application/zip'
case 'resourcepack':
return '.zip,application/zip'
case 'shader':
return '.zip,application/zip'
case 'datapack':
return '.zip,application/zip'
case 'modpack':
return '.mrpack,application/x-modrinth-modpack+zip,application/zip'
default:
return '*'
}
}