What the Favorites list shows when it's empty: the standard Favorites divider heading with a plain message under it, not a boxed card. Three message treatments; B is picked.
<GroupHeading> · ui/components/GroupHeading.vue — the standard divider, unchanged; it just needs to RENDER when the list is empty (today it's gated on list.emojis.length > 0)<Text> · MyLists.vue wiring — ui/components/MyEmoji/MyLists.vue<EmptyListMessage> — tiny new atom (title + message), replaces the boxed <InlineMessage> here; reusable for any future empty list<script setup>
const props = defineProps(['title', 'message'])
</script>
<template>
<div class="empty-list-message">
<Text v-if="props.title" type="body/m-heavy" color="text-primary" :text="props.title" />
<Text type="body/s-med" color="text-tertiary" :text="props.message" />
</div>
</template>
<style scoped>
.empty-list-message {
display: flex;
flex-direction: column;
gap: 4px;
text-align: left;
/* bottom padding = the agreed extra room before the prompt-card
stack ("+16 to 32px vs today") — --sp-6 (24px) splits it;
tune live with the dev */
padding: var(--sp-3) 0 var(--sp-6);
}
</style>
<!-- BEFORE: the heading is gated on content, so an empty Favorites
list loses its divider and the boxed card floats like a banner -->
<GroupHeading
v-if="props.from === 'display'" <!-- was: ... && (list.emojis.length > 0 || list.combos.length > 0) -->
:leading-emoji="useEmojiService.getEmojiCharacters(list.icon?.id || '1f600').join('')"
:title="list.title"
...unchanged props...
/>
<EmojiGrid v-if="isListVisible(list) && list.emojis.length > 0" ...>...</EmojiGrid>
<!-- was <InlineMessage title="No Favorites Yet" subtitle="..." /> -->
<EmptyListMessage v-else-if="lists.my.length === 1 && lists.my[0].title === 'Favorites'"
title="No Favorites Yet"
message="Click an emoji and use the menu at the bottom right to add to your favorites list." />
<GroupHeading> · ui/components/GroupHeading.vue — the standard divider, unchanged; it just needs to RENDER when the list is empty (today it's gated on list.emojis.length > 0)<Text> · MyLists.vue wiring — ui/components/MyEmoji/MyLists.vue<Icon name="heart" /> · ui/components/Icon.vue — the Phosphor outline heart, 26px, text-disabled<EmptyListMessage> — tiny new atom (title + message + centered variant), replaces the boxed <InlineMessage> here; reusable for any future empty list<script setup>
const props = defineProps(['title', 'message', 'variant'])
</script>
<template>
<div class="empty-list-message" :class="props.variant ? 'variant_' + props.variant : ''">
<Icon v-if="props.variant === 'centered'" name="heart" class="__heart" />
<Text v-if="props.title" type="body/m-heavy" color="text-primary" :text="props.title" />
<Text type="body/s-med" color="text-tertiary" :text="props.message" />
</div>
</template>
<style scoped>
.empty-list-message {
display: flex;
flex-direction: column;
gap: 4px;
text-align: left;
/* bottom padding = the agreed extra room before the prompt-card
stack ("+16 to 32px vs today") — --sp-6 (24px) splits it;
tune live with the dev */
padding: var(--sp-3) 0 var(--sp-6);
}
</style>
<!-- BEFORE: the heading is gated on content, so an empty Favorites
list loses its divider and the boxed card floats like a banner -->
<GroupHeading
v-if="props.from === 'display'" <!-- was: ... && (list.emojis.length > 0 || list.combos.length > 0) -->
:leading-emoji="useEmojiService.getEmojiCharacters(list.icon?.id || '1f600').join('')"
:title="list.title"
...unchanged props...
/>
<EmojiGrid v-if="isListVisible(list) && list.emojis.length > 0" ...>...</EmojiGrid>
<!-- was <InlineMessage title="No Favorites Yet" subtitle="..." /> -->
<EmptyListMessage v-else-if="lists.my.length === 1 && lists.my[0].title === 'Favorites'"
variant="centered"
title="No Favorites Yet"
message="Click an emoji and use the menu at the bottom right to add to your favorites list." />
.empty-list-message.variant_centered {
align-items: center;
text-align: center;
gap: var(--sp-2);
padding: var(--sp-5) 0 var(--sp-6);
/* <Icon name="heart" class="__heart" /> — the Phosphor outline */
.__heart {
width: 26px;
height: 26px;
color: var(--c-text-disabled);
}
}
<GroupHeading> · ui/components/GroupHeading.vue — the standard divider, unchanged; it just needs to RENDER when the list is empty (today it's gated on list.emojis.length > 0)<Text> · MyLists.vue wiring — ui/components/MyEmoji/MyLists.vue<EmptyListMessage> — tiny new atom (title + message; this card passes message only), replaces the boxed <InlineMessage> here; reusable for any future empty list<script setup>
const props = defineProps(['title', 'message'])
</script>
<template>
<div class="empty-list-message">
<Text v-if="props.title" type="body/m-heavy" color="text-primary" :text="props.title" />
<Text type="body/s-med" color="text-tertiary" :text="props.message" />
</div>
</template>
<style scoped>
.empty-list-message {
display: flex;
flex-direction: column;
gap: 4px;
text-align: left;
/* bottom padding = the agreed extra room before the prompt-card
stack ("+16 to 32px vs today") — --sp-6 (24px) splits it;
tune live with the dev */
padding: var(--sp-3) 0 var(--sp-6);
}
</style>
<!-- BEFORE: the heading is gated on content, so an empty Favorites
list loses its divider and the boxed card floats like a banner -->
<GroupHeading
v-if="props.from === 'display'" <!-- was: ... && (list.emojis.length > 0 || list.combos.length > 0) -->
:leading-emoji="useEmojiService.getEmojiCharacters(list.icon?.id || '1f600').join('')"
:title="list.title"
...unchanged props...
/>
<EmojiGrid v-if="isListVisible(list) && list.emojis.length > 0" ...>...</EmojiGrid>
<!-- was <InlineMessage title="No Favorites Yet" subtitle="..." /> -->
<!-- C's one copy edit: the two shipped lines merge into one sentence -->
<EmptyListMessage v-else-if="lists.my.length === 1 && lists.my[0].title === 'Favorites'"
message="No favorites yet — click an emoji and use the menu at the bottom right to add one." />