Skip to content

EntityCard

Renders one row as a card: its thumbnail, name, type, status and a grid of the field paths you name. Give the card a row you hold, or a reference and it reads the row itself. The tile variant reads the same row picture first, and is what EntityGrid lays out.

Installing this pulls StatusBadge and Thumbnail with it.

Terminal window
pnpm dlx shadcn@latest add https://sg-widgets.vercel.app/r/react/entity-card.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://sg-widgets.vercel.app/r/svelte/entity-card.json
EntityCard: three sizes from a row, one card reading a picked reference, and the tile variant

React

Loading the React demo…

Svelte

Loading the Svelte demo…

proptypedefaultmeaning
contextSgContext—The widget context: the cached client, the schema, the site url and the preferences the card reads through.
clientSgClient—A client, for an app with no context. One context is built per client and shared.
rowEntityRow | nullnullA row you already read. Given, the card reads nothing.
entityEntityRef | nullnullThe row to read, when no row is given.
fieldsstring[][]Field paths for the grid, in order. Dotted paths allowed. Card only.
variant'card' | 'tile''card'The stacked surface, or the thumbnail-first tile.
size'sm' | 'md' | 'lg''md'Thumbnail 16, 16 and 24, with the gaps and name scale to match.
imagePathstring'image'The image field the thumbnail comes from.
labelFieldstring | nullthe type’s display nameField shown as the name. Tile only.
subLabelFieldFieldSpec | nullnullThe left of the tile’s metadata line, drawn by its data type: a status reads its display name.
subLabel(row) => string—The caller’s own sub-label. Wins over subLabelField.
secondaryFieldFieldSpec | nullnullThe right of the tile’s metadata line, drawn by its data type: a status reads its display name.
secondary(row) => string—The caller’s own text on the right of the metadata line. Wins over secondaryField.
showCodebooleanfalseShow the row’s code beside the name when the two differ. Tile only.
statusesRecord<string, StatusRecord> | nullthe context’sStatus rows by code (probe 010).
selectablebooleanfalseDraws the tile’s selection checkbox and gives the tile a focus ring.
selectedbooleanfalseWhether the tile is taken.
siteUrlstringthe context'sThe site the name and every linked row point at.
hoursPerDaynumberthe context'sThe site's working day. Durations then render in days.
localestringthe context's, then the runtime'sUsed for dates and numbers.
timeZonestringthe context's, then the runtime'sIANA zone a date_time is shown in.
frameRatenumberthe context'sFrames a second. A timecode then carries its frame digits.
emptyLabelstring'empty'What a field with no value shows.
errorLabelstring—Shown in place of what the failed read said.
class / classNamestring—Merged after the widget's own classes.

A card reads through a context. Given a client instead, it builds one and shares it with every other widget on that client.

These shape the tile and are ignored by the card:

Nothing is on the metadata line by default, and the row’s id is never on it. Both sides of it draw by the field’s data type, so a status reads the name the site gives its code and a linked row links to its own page. The tile takes its selected state rather than holding one, and it spreads whatever attributes and handlers it is given onto its root, so a collection can make it an option of a listbox and drive it.

Given a reference, the card reads the row itself: one search asking for the type’s identity chain, its thumbnail, its status field and your paths at once. The read goes through the context’s cache, so a second card on the same row costs nothing.

Every path is labelled through the schema. A hop names the type it travels through only when its field accepts several, so sg_task.Task.sg_status_list reads

Task › Status

and entity.Shot.sg_sequence reads

Link › Shot › Sequence

A card is a compact surface, so a value is one line: a status is a badge, an image a thumbnail, a linked row a link to its own page, and everything else the text core’s formatters give it. A path that names no field is shown under its own text with an empty value. A badge sits one step under the card’s own size.

The header names the row’s own status, so a path naming that same field is not drawn a second time in the grid. A path that ends at a linked row’s status is a different row’s status and stays. The tile’s metadata line draws whatever you name on it, that field included. The field is the type’s conventional one, sg_status_list or sg_status on a Project, whatever other status fields the site has added to the type.

A card with no row yet shows a skeleton shaped like the card; a read that fails shows the message inline.

The card paints no surface of its own, so it wears the card, popover or page it is put on: a chip’s hover card shows one on a popover, and a page frames it. The tile is a card and paints one.

The root fills its container and carries the size as a data attribute.

eventpayloadwhen
onSelectedChangebooleanThe tile's checkbox was toggled.
slotdraws
actionsThe thumbnail's top-right corner, on hover or focus. Tile only.
keydoes
TabOn a card, moves to the name, when a site url is known, then to any link inside a field value. On a tile, moves to the checkbox and to the actions.
EnterOpens the row's page on the site, in a new tab.
SpaceToggles the checkbox it is on.

Nothing else in the card takes focus. A tile is focusable only once whoever lays it out gives it a tab index; EntityGrid does, and owns the arrow keys.

The tile follows the thumbnail view of the Flow PT web app and the asset grid of Frame.io: the picture first and at one aspect, the state on the picture rather than under it, one line of name and one of metadata, and the controls only while the pointer or the focus is on the tile.

A dotted path is projected against the middle segment’s valid_types: a segment outside that list answers 200 with the key simply absent, so an unresolvable path is an empty value and not an error (059_dotted_path_type_check).

A path through a multi-entity field reads back nothing at all, in a single read and in a search alike, while the same path filters correctly (016_dotted_multi_entity).

The identity chain is intersected with the type’s own schema before the read, because a field a type does not have is a 400 and not an empty column: a Task has neither code nor name (entity_types/Task).

Every type but Project holds its status in sg_status_list; Project’s is sg_status, a plain list with no Status row behind it (entity_types/Project). A site may add status fields of its own to a type, and a schema read answers them in no fixed order, so the conventional name is what a row’s status is read from and never whichever status field the response listed first.

The web app addresses a row at /detail/<Type>/<id>. The API hands that path out for one type only, as Project.landing_page_url, site-relative and with the site url left to the caller (entity_types/Project). Every other type takes the same shape by a convention the API does not document. On the test site it answers for every type: the route resolves before authentication decides, redirecting an anonymous visitor to the login page with itself as the return path.