Final few components
This commit is contained in:
parent
f5cbe0c4fe
commit
dcc2a5afe0
@ -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' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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"
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|||||||
11
docs/components/drop-area.md
Normal file
11
docs/components/drop-area.md
Normal 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="*" />
|
||||||
|
```
|
||||||
23
docs/components/file-input.md
Normal file
23
docs/components/file-input.md
Normal 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>
|
||||||
|
```
|
||||||
53
docs/components/search-filter.md
Normal file
53
docs/components/search-filter.md
Normal 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>
|
||||||
|
```
|
||||||
87
lib/components/base/DropArea.vue
Normal file
87
lib/components/base/DropArea.vue
Normal 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>
|
||||||
96
lib/components/base/FileInput.vue
Normal file
96
lib/components/base/FileInput.vue
Normal 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>
|
||||||
@ -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'
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
73
lib/components/search/SearchFilter.vue
Normal file
73
lib/components/search/SearchFilter.vue
Normal 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>
|
||||||
@ -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 '*'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user