feat: add inset property to Grid components for customizable padding

This aligns the grid with Raycast's API, specifically allowing it to work with the Spotify Search command (among others, of course)
This commit is contained in:
ByteAtATime 2025-06-23 18:40:24 -07:00
parent 05fef6ef05
commit afa6fc8235
No known key found for this signature in database
5 changed files with 70 additions and 12 deletions

View file

@ -28,10 +28,17 @@ const GridDropdown = createWrapperComponent('Grid.Dropdown');
const GridDropdownItem = createWrapperComponent('Grid.Dropdown.Item');
const GridDropdownSection = createWrapperComponent('Grid.Dropdown.Section');
const Inset = {
Small: 'small',
Medium: 'medium',
Large: 'large'
} as const;
Object.assign(Grid, {
Section: GridSection,
Item: GridItem,
Dropdown: GridDropdown
Dropdown: GridDropdown,
Inset: Inset
});
Object.assign(GridDropdown, {
Item: GridDropdownItem,

View file

@ -23,7 +23,8 @@
columns: gridProps?.columns ?? 6,
searchText,
filtering: gridProps?.filtering,
onSearchTextChange: !!gridProps?.onSearchTextChange
onSearchTextChange: !!gridProps?.onSearchTextChange,
inset: gridProps?.inset
}));
</script>
@ -44,6 +45,7 @@
props={item.props as GridItemProps}
selected={view.selectedItemIndex === index}
onclick={() => view.setSelectedItemIndex(index)}
inset={item.inset}
/>
</div>
{/if}

View file

@ -1,19 +1,38 @@
<script lang="ts">
import type { GridItemProps } from '$lib/props';
import type { HTMLButtonAttributes } from 'svelte/elements';
import { cn } from '$lib/utils';
import type { GridInset } from '$lib/props/grid';
type Props = {
props: GridItemProps;
selected: boolean;
inset?: GridInset;
} & HTMLButtonAttributes;
let { props, selected, ...restProps }: Props = $props();
let { props, selected, inset, ...restProps }: Props = $props();
const paddingClass = $derived(() => {
switch (inset) {
case 'small':
return 'p-1';
case 'medium':
return 'p-2';
case 'large':
return 'p-4';
default:
return 'px-4 py-2';
}
});
</script>
<button
type="button"
class="hover:bg-accent/50 flex w-full flex-col items-center gap-3 px-4 py-2 text-left"
class:bg-accent={selected}
class={cn(
'hover:bg-accent/50 flex w-full flex-col items-center gap-3 text-left',
paddingClass,
selected && 'bg-accent'
)}
{...restProps}
>
<img src={props.content} alt={props.title} />

View file

@ -1,18 +1,23 @@
import { z } from 'zod/v4';
import { ImageLikeSchema } from './image';
export const GridInsetSchema = z.enum(['small', 'medium', 'large']);
export type GridInset = z.infer<typeof GridInsetSchema>;
export const GridPropsSchema = z.object({
filtering: z.boolean().optional(),
throttle: z.boolean().default(false),
columns: z.number().default(6), // TODO: is this the default?
searchBarPlaceholder: z.string().optional(),
onSearchTextChange: z.boolean().optional(),
isLoading: z.boolean().default(false)
isLoading: z.boolean().default(false),
inset: GridInsetSchema.optional()
});
export type GridProps = z.infer<typeof GridPropsSchema>;
export const GridSectionPropsSchema = z.object({
title: z.string().optional()
title: z.string().optional(),
inset: GridInsetSchema.optional()
});
export type GridSectionProps = z.infer<typeof GridSectionPropsSchema>;

View file

@ -1,8 +1,33 @@
import { _useBaseView, type BaseViewArgs } from './base.svelte';
import type { GridInset, GridSectionProps } from '$lib/props';
export function useGridView(args: () => BaseViewArgs & { columns: number }) {
export function useGridView(args: () => BaseViewArgs & { columns: number; inset?: GridInset }) {
const base = _useBaseView(args, 'Grid.Item');
const { columns } = $derived.by(args);
const { columns, inset: gridInset } = $derived.by(args);
const processedFlatList = $derived.by(() => {
const list = base.flatList;
const newList: ((typeof list)[number] & { inset?: GridInset })[] = [];
let currentSectionInset: GridInset | undefined;
for (const item of list) {
if (item.type === 'header') {
const sectionProps = item.props as GridSectionProps;
if (item.id === -1) {
// This is the synthetic section for top-level items, so it should inherit from the Grid.
currentSectionInset = gridInset;
} else {
// This is a user-defined <Grid.Section>. It does not inherit.
// If `sectionProps.inset` is undefined, it's treated as "none" by GridItem.
currentSectionInset = sectionProps.inset;
}
newList.push(item);
} else {
newList.push({ ...item, inset: currentSectionInset });
}
}
return newList;
});
type GridMapItem = {
flatListIndex: number;
@ -15,7 +40,7 @@ export function useGridView(args: () => BaseViewArgs & { columns: number }) {
let sectionIndex = -1,
rowIndex = 0,
colIndex = 0;
base.flatList.forEach((item, index) => {
processedFlatList.forEach((item, index) => {
if (item.type === 'header') {
sectionIndex++;
rowIndex = 0;
@ -31,7 +56,7 @@ export function useGridView(args: () => BaseViewArgs & { columns: number }) {
$effect(() => {
if (base.selectedItemIndex < 0) return;
const elementId = `item-${base.flatList[base.selectedItemIndex]?.id}`;
const elementId = `item-${processedFlatList[base.selectedItemIndex]?.id}`;
document.getElementById(elementId)?.scrollIntoView({ block: 'nearest' });
});
@ -92,7 +117,7 @@ export function useGridView(args: () => BaseViewArgs & { columns: number }) {
return {
get flatList() {
return base.flatList;
return processedFlatList;
},
get selectedItemIndex() {
return base.selectedItemIndex;