Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/appstore/lib/Search/AppSearch.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ public function search(IUser $user, ISearchQuery $query): SearchResult {
$entry['name'],
'',
$entry['href'],
'icon-confirm'
$entry['icon'],
true,
);
}

Expand Down
11 changes: 6 additions & 5 deletions apps/settings/lib/Search/SectionSearch.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,18 @@ public function searchSections(ISearchQuery $query, array $sections, string $sub
continue;
}

/**
* We can't use the icon URL at the moment as they don't invert correctly for dark theme
* $iconUrl = $section->getIcon();
*/
// The section's own icon, or a generic cog fallback.
$icon = $section->getIcon();
if ($icon === '') {
$icon = $this->urlGenerator->imagePath('settings', 'settings.svg');
}

$result[] = new SearchResultEntry(
'',
$section->getName(),
$subline,
$this->urlGenerator->linkToRouteAbsolute($routeName, ['section' => $section->getID()]),
'icon-settings-dark'
$icon,
);
}
}
Expand Down
74 changes: 74 additions & 0 deletions core/src/components/AppIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<!--
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<template>
<span
class="app-icon"
:class="{ 'app-icon--outlined': outlined }">
<img
class="app-icon__img"
:src="icon"
alt=""
aria-hidden="true">
<!-- @slot Overlay positioned over the circle, e.g. an unread badge. -->
<slot />
</span>
</template>

<script setup lang="ts">
withDefaults(defineProps<{
/** URL of the app icon (painted bright on the coloured circle). */
icon: string
/** Render the circle as an outline only (no fill or gradient). */
outlined?: boolean
}>(), {
outlined: false,
})
</script>

<style scoped lang="scss">
.app-icon {
--app-icon-circle-size: calc(var(--default-grid-baseline) * 10);
--app-icon-icon-size: 22px;
box-sizing: border-box;
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: var(--app-icon-circle-size);
height: var(--app-icon-circle-size);
border-radius: 50%;
background-color: var(--color-primary-element);
background-image: linear-gradient(
to bottom,
rgba(255, 255, 255, 0.18) 0%,
rgba(255, 255, 255, 0) 45%,
rgba(0, 0, 0, 0.15) 100%
);
box-shadow:
inset 0 1px 0 0 rgba(255, 255, 255, 0.25),
inset 0 -1px 0 0 rgba(0, 0, 0, 0.2),
0 2px 4px rgba(0, 0, 0, 0.15);

&__img {
width: var(--app-icon-icon-size);
height: var(--app-icon-icon-size);
// App icons are bright; flip to dark when the circle background is bright (e.g. white in dark mode).
filter: var(--primary-invert-if-bright);
mask: var(--header-menu-icon-mask);
}

&--outlined {
background: transparent;
background-image: none;
box-shadow: inset 0 0 0 2px var(--color-border-maxcontrast);
}

&--outlined &__img {
filter: var(--background-invert-if-dark);
mask: none;
}
}
</style>
56 changes: 3 additions & 53 deletions core/src/components/AppItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
class="app-item"
:class="{
'app-item--active': app.active,
'app-item--outlined': outlined,
}"
:href="app.href"
:target="newTab ? '_blank' : undefined"
Expand All @@ -17,17 +16,12 @@
:tabindex="tabindex"
:title="app.name"
role="menuitem">
<span class="app-item__circle">
<img
class="app-item__icon"
:src="app.icon"
alt=""
aria-hidden="true">
<AppIcon :icon="app.icon" :outlined="outlined">
<span
v-if="app.unread"
class="app-item__unread"
aria-hidden="true" />
</span>
</AppIcon>
<span class="app-item__label">
{{ app.name }}
<span v-if="app.unread" class="hidden-visually">, {{ unreadLabel }}</span>
Expand All @@ -40,6 +34,7 @@ import type { INavigationEntry } from '../types/navigation.d.ts'

import { n } from '@nextcloud/l10n'
import { computed } from 'vue'
import AppIcon from './AppIcon.vue'

const props = withDefaults(defineProps<{
app: INavigationEntry
Expand Down Expand Up @@ -73,8 +68,6 @@ const unreadLabel = computed(() => {

<style scoped lang="scss">
.app-item {
--app-item-circle-size: calc(var(--default-grid-baseline) * 10);
--app-item-icon-size: 22px;
display: flex;
flex-direction: column;
align-items: center;
Expand All @@ -100,37 +93,6 @@ const unreadLabel = computed(() => {
box-shadow: inset 0 0 0 2px var(--color-primary-element);
}

&__circle {
box-sizing: border-box;
position: relative;
width: var(--app-item-circle-size);
height: var(--app-item-circle-size);
border-radius: 50%;
background-color: var(--color-primary-element);
background-image: linear-gradient(
to bottom,
rgba(255, 255, 255, 0.18) 0%,
rgba(255, 255, 255, 0) 45%,
rgba(0, 0, 0, 0.15) 100%
);
box-shadow:
inset 0 1px 0 0 rgba(255, 255, 255, 0.25),
inset 0 -1px 0 0 rgba(0, 0, 0, 0.2),
0 2px 4px rgba(0, 0, 0, 0.15);
display: flex;
align-items: center;
justify-content: center;
}

&__icon {
width: var(--app-item-icon-size);
height: var(--app-item-icon-size);
// App icons are bright by default; flip them to dark when the
// primary color (circle background) is bright (e.g. white in dark mode).
filter: var(--primary-invert-if-bright);
mask: var(--header-menu-icon-mask);
}

&__unread {
position: absolute;
top: 0;
Expand Down Expand Up @@ -160,17 +122,5 @@ const unreadLabel = computed(() => {
&--active &__label {
font-weight: bold;
}

// Outlined variant: no fill or gradient.
&--outlined &__circle {
background: transparent;
background-image: none;
box-shadow: inset 0 0 0 2px var(--color-border-maxcontrast);
}

&--outlined &__icon {
filter: var(--background-invert-if-dark);
mask: none;
}
}
</style>
Loading
Loading