diff --git a/docs/.vitepress/config.js b/docs/.vitepress/config.js
index 56011ad8e..ee27ff5bb 100644
--- a/docs/.vitepress/config.js
+++ b/docs/.vitepress/config.js
@@ -30,6 +30,8 @@ export default {
{ text: 'Modal', link: '/components/modal' },
{ text: 'Dropdown Select', link: '/components/dropdown-select' },
{ text: 'Dropdown Button', link: '/components/dropdown-button' },
+ { text: 'Popout Menu', link: '/components/popout-menu' },
+ { text: 'Overflow Menu', link: '/components/overflow-menu' },
{ text: 'Project Card', link: '/components/project-card' },
{ text: 'Environment Indicator', link: '/components/environment-indicator' },
{ text: 'Categories', link: '/components/categories' },
diff --git a/docs/components/button.md b/docs/components/button.md
index 5fdd55810..0dcac4c19 100644
--- a/docs/components/button.md
+++ b/docs/components/button.md
@@ -1,35 +1,77 @@
-# Button
+# Buttons
+
+## Standard
-Standard
-Primary
-Secondary
-Highlight
-Raised
-Danger
- With Icon
-
-Large button
-Discover mods
-Be warned
-Sign up
- Follow project
- Delete project
+ Save
+ Upload
+ Create new instance
+ Submit for review
+ Delete
```vue
-Standard
-Primary
-Secondary
-Highlight
-Raised
-Danger
- With Icon
-
-Large button
-Discover mods
-Be warned
-Sign up
- Follow project
- Delete project
+ Like
+ Upload
+ Edit
+ Submit for review
+ Delete
+```
+
+## Large
+
+
+ Download
+ Host a Server
+ Donate
+
+
+```vue
+ Download
+ Host a Server
+ Donate
+```
+
+## Outline
+
+
+ Get Modrinth App
+ Report project
+
+
+```vue
+ Get Modrinth App
+ Report project
+```
+
+## Transparent
+
+
+ Report issues
+ View sources
+ Visit website
+
+
+```vue
+ Report issues
+ View sources
+ Visit website
+```
+
+## Icon-only
+
+
+
+
+
+
+
+
+
+```vue
+
+
+
+
+
```
diff --git a/docs/components/overflow-menu.md b/docs/components/overflow-menu.md
new file mode 100644
index 000000000..3278590d2
--- /dev/null
+++ b/docs/components/overflow-menu.md
@@ -0,0 +1,43 @@
+
+
+# Popout Menu
+
+
+ More options...
+
+ Like
+
+
+ Report
+
+
+ Delete
+
+
+
+
+```vue
+
+Bottom going right
+
+ Menu contents!
+
+
+```
diff --git a/docs/components/popout-menu.md b/docs/components/popout-menu.md
new file mode 100644
index 000000000..34e26a0ab
--- /dev/null
+++ b/docs/components/popout-menu.md
@@ -0,0 +1,76 @@
+# Popout Menu
+
+
+ Bottom going left
+
+ Menu contents!
+ Menu contents!
+ Menu contents!
+
+
+
+ Bottom going right
+
+ Menu contents!
+ Menu contents!
+ Menu contents!
+
+
+
+ Top going left
+
+ Menu contents!
+ Menu contents!
+ Menu contents!
+
+
+
+ Top going right
+
+ Menu contents!
+ Menu contents!
+ Menu contents!
+
+
+
+ Left going up
+
+ Menu contents!
+ Menu contents!
+ Menu contents!
+
+
+
+ Left going down
+
+ Menu contents!
+ Menu contents!
+ Menu contents!
+
+
+
+ Right going up
+
+ Menu contents!
+ Menu contents!
+ Menu contents!
+
+
+
+ Right going down
+
+ Menu contents!
+ Menu contents!
+ Menu contents!
+
+
+
+
+```vue
+
+Bottom going right
+
+ Menu contents!
+
+
+```
diff --git a/lib/assets/icons/bookmark.svg b/lib/assets/icons/bookmark.svg
new file mode 100644
index 000000000..fbe559bc5
--- /dev/null
+++ b/lib/assets/icons/bookmark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/assets/icons/more-horizontal.svg b/lib/assets/icons/more-horizontal.svg
new file mode 100644
index 000000000..65d9a0563
--- /dev/null
+++ b/lib/assets/icons/more-horizontal.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/assets/icons/more-vertical.svg b/lib/assets/icons/more-vertical.svg
new file mode 100644
index 000000000..98f4a4dfe
--- /dev/null
+++ b/lib/assets/icons/more-vertical.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/assets/styles/classes.scss b/lib/assets/styles/classes.scss
index 588ed6939..a7e7ceab3 100644
--- a/lib/assets/styles/classes.scss
+++ b/lib/assets/styles/classes.scss
@@ -321,19 +321,30 @@ a,
.btn {
@extend .button-base;
- --text-color: var(--color-contrast);
+ --text-color: var(--color-base);
--background-color: var(--color-button-bg);
--shadow: var(--shadow-inset-sm), 0 0 0 0 transparent;
- &.btn-outline {
- --background-color: var(--color-contrast);
+ &.btn-outline,
+ &.btn-transparent {
+ --background-color: var(--color-base);
--text-color: var(--color-raised-bg);
box-sizing: border-box;
background-color: transparent;
- border: 2px solid var(--background-color);
color: var(--background-color);
+ transition: background-color 0.2s ease-in-out;
+ &:focus-visible:not(&:disabled),
+ &:hover:not(&:disabled) {
+ background-color: var(--color-button-bg);
+ filter: none;
+ }
+ }
+
+ &.btn-outline {
+ --background-color: var(--color-contrast);
+ border: 2px solid var(--background-color);
padding-block: calc(var(--gap-sm) - 2px);
}
@@ -353,6 +364,7 @@ a,
}
&.btn-secondary {
+ --text-color: var(--color-contrast);
--background-color: var(--color-brand-highlight);
}
@@ -361,6 +373,36 @@ a,
--background-color: var(--color-orange);
}
+ &.btn-red {
+ --text-color: var(--color-accent-contrast);
+ --background-color: var(--color-red);
+ }
+
+ &.btn-orange {
+ --text-color: var(--color-accent-contrast);
+ --background-color: var(--color-orange);
+ }
+
+ &.btn-green {
+ --text-color: var(--color-accent-contrast);
+ --background-color: var(--color-green);
+ }
+
+ &.btn-blue {
+ --text-color: var(--color-accent-contrast);
+ --background-color: var(--color-blue);
+ }
+
+ &.btn-purple {
+ --text-color: var(--color-accent-contrast);
+ --background-color: var(--color-purple);
+ }
+
+ &.btn-gray {
+ --text-color: var(--color-accent-contrast);
+ --background-color: var(--color-gray);
+ }
+
&.btn-large {
font-weight: 700;
min-height: 2.5rem;
@@ -373,20 +415,21 @@ a,
box-shadow: var(--shadow);
border-radius: var(--radius-md);
- padding-inline: var(--gap-lg);
- padding-block: var(--gap-sm);
+ padding: var(--gap-sm) var(--gap-lg);
display: flex;
align-items: center;
+ justify-content: center;
cursor: pointer;
width: fit-content;
height: fit-content;
text-decoration: none;
+ gap: 0.5rem;
+ line-height: 1.25rem;
svg {
- width: 1.1rem;
- height: 1.1rem;
- margin-right: 0.5rem;
+ width: 1.25rem;
+ height: 1.25rem;
}
:deep(.external-icon) {
@@ -398,17 +441,7 @@ a,
}
&.icon-only {
- padding: 0;
- height: 2.25rem;
- width: 2.25rem;
-
- svg {
- min-width: 1.25rem;
- max-width: 1.25rem;
- min-height: 1.25rem;
- max-height: 1.25rem;
- margin: auto;
- }
+ padding: var(--gap-sm);
}
&.transparent {
diff --git a/lib/assets/styles/variables.scss b/lib/assets/styles/variables.scss
index 14c023cdd..5e9a1343f 100644
--- a/lib/assets/styles/variables.scss
+++ b/lib/assets/styles/variables.scss
@@ -25,6 +25,7 @@ html {
--color-button-bg: hsl(220, 13%, 91%);
--color-base: hsl(221, 39%, 11%);
+ --color-secondary: #6b7280;
--color-contrast: #1a202c;
--color-accent-contrast: #ffffff;
@@ -67,6 +68,7 @@ html {
--color-button-bg: hsl(222, 13%, 30%);
--color-base: var(--dark-color-base);
+ --color-secondary: #96a2b0;
--color-contrast: var(--dark-color-contrast);
--color-accent-contrast: #000000;
diff --git a/lib/components/base/Button.vue b/lib/components/base/Button.vue
index 8bddc3018..12290661c 100644
--- a/lib/components/base/Button.vue
+++ b/lib/components/base/Button.vue
@@ -32,9 +32,15 @@ const props = defineProps({
type: Boolean,
default: false,
},
+ transparent: {
+ type: Boolean,
+ default: false,
+ },
})
-const accentedButton = computed(() => ['danger', 'primary'].includes(props.color))
+const accentedButton = computed(() =>
+ ['danger', 'primary', 'red', 'orange', 'green', 'blue', 'purple', 'gray'].includes(props.color)
+)
@@ -42,14 +48,20 @@ const accentedButton = computed(() => ['danger', 'primary'].includes(props.color
v-if="link"
class="btn"
:class="{
- 'icon-only': props.iconOnly,
- 'btn-large': props.large,
- 'btn-raised': color === 'raised',
+ 'icon-only': iconOnly,
+ 'btn-large': large,
'btn-danger': color === 'danger',
'btn-primary': color === 'primary',
'btn-secondary': color === 'secondary',
'btn-highlight': color === 'highlight',
- 'btn-outline': props.outline,
+ 'btn-red': color === 'red',
+ 'btn-orange': color === 'orange',
+ 'btn-green': color === 'green',
+ 'btn-blue': color === 'blue',
+ 'btn-purple': color === 'purple',
+ 'btn-gray': color === 'gray',
+ 'btn-transparent': transparent,
+ 'btn-outline': outline,
'color-accent-contrast': accentedButton,
}"
:to="link"
@@ -63,14 +75,20 @@ const accentedButton = computed(() => ['danger', 'primary'].includes(props.color
v-else
class="btn"
:class="{
- 'icon-only': props.iconOnly,
- 'btn-large': props.large,
- 'btn-raised': color === 'raised',
+ 'icon-only': iconOnly,
+ 'btn-large': large,
'btn-danger': color === 'danger',
'btn-primary': color === 'primary',
'btn-secondary': color === 'secondary',
'btn-highlight': color === 'highlight',
- 'btn-outline': props.outline,
+ 'btn-red': color === 'red',
+ 'btn-orange': color === 'orange',
+ 'btn-green': color === 'green',
+ 'btn-blue': color === 'blue',
+ 'btn-purple': color === 'purple',
+ 'btn-gray': color === 'gray',
+ 'btn-transparent': transparent,
+ 'btn-outline': outline,
'color-accent-contrast': accentedButton,
}"
@click="action"
diff --git a/lib/components/base/OverflowMenu.vue b/lib/components/base/OverflowMenu.vue
new file mode 100644
index 000000000..c7dbae28a
--- /dev/null
+++ b/lib/components/base/OverflowMenu.vue
@@ -0,0 +1,78 @@
+
+
+
+
+
+ {{ option.id }}
+
+
+
+
+
+
+
+
+
diff --git a/lib/components/base/PopoutMenu.vue b/lib/components/base/PopoutMenu.vue
new file mode 100644
index 000000000..13a2b6f5d
--- /dev/null
+++ b/lib/components/base/PopoutMenu.vue
@@ -0,0 +1,195 @@
+
+
+
+
+
+
+
diff --git a/lib/components/index.js b/lib/components/index.js
index 67f1a74c1..8fd3d367e 100644
--- a/lib/components/index.js
+++ b/lib/components/index.js
@@ -36,12 +36,16 @@ export { default as NavItem } from './nav/NavItem.vue'
export { default as NavRow } from './nav/NavRow.vue'
export { default as NavStack } from './nav/NavStack.vue'
+export { default as PopoutMenu } from './base/PopoutMenu.vue'
+export { default as OverflowMenu } from './base/OverflowMenu.vue'
+
export { default as AlignLeftIcon } from '@/assets/icons/align-left.svg'
export { default as ArchiveIcon } from '@/assets/icons/archive.svg'
export { default as AsteriskIcon } from '@/assets/icons/asterisk.svg'
export { default as BellIcon } from '@/assets/icons/bell.svg'
export { default as BellRingIcon } from '@/assets/icons/bell-ring.svg'
export { default as BookIcon } from '@/assets/icons/book.svg'
+export { default as BookmarkIcon } from '@/assets/icons/bookmark.svg'
export { default as BoxIcon } from '@/assets/icons/box.svg'
export { default as CalendarIcon } from '@/assets/icons/calendar.svg'
export { default as ChartIcon } from '@/assets/icons/chart.svg'
@@ -94,6 +98,8 @@ export { default as LockIcon } from '@/assets/icons/lock.svg'
export { default as LogInIcon } from '@/assets/icons/log-in.svg'
export { default as LogOutIcon } from '@/assets/icons/log-out.svg'
export { default as MoonIcon } from '@/assets/icons/moon.svg'
+export { default as MoreHorizontalIcon } from '@/assets/icons/more-horizontal.svg'
+export { default as MoreVerticalIcon } from '@/assets/icons/more-vertical.svg'
export { default as OmorphiaIcon } from '@/assets/icons/omorphia.svg'
export { default as PaintBrushIcon } from '@/assets/icons/paintbrush.svg'
export { default as PlayIcon } from '@/assets/icons/play.svg'