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
6 changes: 6 additions & 0 deletions apps/catalog/src/catalog.css
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,12 @@ body {
list-style: none;
}

/* The pointer's room over a line held to one line, as wide as the column. */
.catalog-one-line {
display: block;
min-width: 0;
}

/* A column narrower than a phone, where long words have to wrap. */
.catalog-narrow {
display: flex;
Expand Down
6 changes: 6 additions & 0 deletions apps/catalog/src/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ATTACHMENT_CHIPS_EXAMPLE } from "./examples/attachment-chips";
import { AUTH_PAGE_EXAMPLE } from "./examples/auth-page";
import { AVATARS_EXAMPLE } from "./examples/avatars";
import { BADGES_EXAMPLE } from "./examples/badges";
import { BANNERS_EXAMPLE } from "./examples/banners";
import { BREADCRUMBS_EXAMPLE } from "./examples/breadcrumbs";
import { BUTTONS_EXAMPLE } from "./examples/buttons";
import { CARDS_EXAMPLE } from "./examples/cards";
Expand All @@ -29,8 +30,10 @@ import { LAYOUT_EXAMPLE } from "./examples/layout";
import { LIST_ROW_EXAMPLE } from "./examples/list-row";
import { MENU_EXAMPLE } from "./examples/menu";
import { NAV_ITEMS_EXAMPLE } from "./examples/nav-items";
import { ONE_LINE_EXAMPLE } from "./examples/one-line";
import { PAGINATION_EXAMPLE } from "./examples/pagination";
import { POPOVER_PANELS_EXAMPLE } from "./examples/popover-panels";
import { PROGRESS_EXAMPLE } from "./examples/progress";
import { PROSE_EXAMPLE } from "./examples/prose";
import { PROVIDER_BUTTONS_EXAMPLE } from "./examples/provider-buttons";
import { PUBLIC_PAGE_EXAMPLE } from "./examples/public-page";
Expand Down Expand Up @@ -73,6 +76,7 @@ export const EXAMPLES: readonly Example[] = [
ATTACHMENT_CHIPS_EXAMPLE,
AVATARS_EXAMPLE,
BADGES_EXAMPLE,
BANNERS_EXAMPLE,
BREADCRUMBS_EXAMPLE,
BUTTONS_EXAMPLE,
CARDS_EXAMPLE,
Expand Down Expand Up @@ -102,8 +106,10 @@ export const EXAMPLES: readonly Example[] = [
LIST_ROW_EXAMPLE,
MENU_EXAMPLE,
NAV_ITEMS_EXAMPLE,
ONE_LINE_EXAMPLE,
PAGINATION_EXAMPLE,
POPOVER_PANELS_EXAMPLE,
PROGRESS_EXAMPLE,
PROSE_EXAMPLE,
PROVIDER_BUTTONS_EXAMPLE,
PUBLIC_PAGE_EXAMPLE,
Expand Down
22 changes: 20 additions & 2 deletions apps/catalog/src/examples/attachment-chips.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AttachmentChip, ImageIcon } from "@luke/ui";
import { ATTACHMENT_CHIP_STATE, AttachmentChip, ImageIcon } from "@luke/ui";
import { useState } from "react";
import type { Example } from "../examples";
import { CatalogSection } from "../section";
Expand All @@ -7,7 +7,8 @@ import { CatalogSection } from "../section";
* attachment-chips.tsx -- a row of files coming along with a message: a
* picture's thumbnail, files drawn by name and size, one with nothing to
* open, a long name cut short, and the way off each, unavailable while busy;
* then the same chips in a narrow column.
* then the same chips in a narrow column, and a call's pictures still read,
* gone, and not kept.
*/

/** A synthetic picture drawn in place, so the catalog renders offline. */
Expand Down Expand Up @@ -67,6 +68,23 @@ function AttachmentChips(): React.JSX.Element {
<CatalogSection id="attachment-chip-busy" title="While sending">
<AttachmentRow busy />
</CatalogSection>
<CatalogSection id="attachment-chip-states" title="Still read, and failed">
<ul className="catalog-attachments" aria-label="Pictures the call answered">
<AttachmentChip name="dashboard.png" state={ATTACHMENT_CHIP_STATE.LOADING} />
<AttachmentChip
name="chart.png"
icon={<ImageIcon />}
state={ATTACHMENT_CHIP_STATE.FAILED}
/>
<AttachmentChip
name="full-page.png"
icon={<ImageIcon />}
state={ATTACHMENT_CHIP_STATE.FAILED}
failure="Not kept"
detail="6.2 MB"
/>
</ul>
</CatalogSection>
<CatalogSection id="attachment-chip-narrow" title="Narrow column, long names">
<div className="catalog-narrow">
<AttachmentRow busy={false} />
Expand Down
24 changes: 22 additions & 2 deletions apps/catalog/src/examples/badges.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import type { Example } from "../examples";
import { CatalogSection } from "../section";

/**
* badges.tsx -- every tone with its words, one with a glyph, and badges
* standing on a line of body words, and a long label in a narrow column.
* badges.tsx -- every tone with its words, one with a glyph, a provider's
* labels each wearing its colour as a dot, a dark one and one with no colour
* it can paint among them, badges standing on a line of body words, and a
* long label in a narrow column.
*/

const SAYS = {
Expand All @@ -16,6 +18,14 @@ const SAYS = {
[BADGE_TONE.DANGER]: "Failed",
} as const;

/** GitHub labels as GitHub answers them: six hex digits, or a colour the badge cannot paint. */
const LABELS = [
{ name: "bug", color: "d73a4a" },
{ name: "enhancement", color: "a2eeef" },
{ name: "needs design", color: "1d1d1f" },
{ name: "imported", color: "not-a-colour" },
] as const;

function Badges(): React.JSX.Element {
return (
<>
Expand All @@ -31,6 +41,16 @@ function Badges(): React.JSX.Element {
</Badge>
</Inline>
</CatalogSection>
<CatalogSection id="badge-swatch" title="A provider's colours">
<Inline gap={GAP.ICON}>
{LABELS.map((label) => (
<Badge key={label.name} swatch={label.color}>
{label.name}
</Badge>
))}
<Badge>+3</Badge>
</Inline>
</CatalogSection>
<CatalogSection id="badge-line" title="On a line of words">
<div className="catalog-column">
<Text>
Expand Down
81 changes: 81 additions & 0 deletions apps/catalog/src/examples/banners.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* banners.tsx -- a notice in each look: a strip across a column in the
* attention tone, a card over a document in either tone, and a quiet row in
* its place's frame; then each in a narrow column, where the presses drop
* beneath the words.
*/

import {
AlertIcon,
ArchiveIcon,
BANNER_LOOK,
BANNER_TONE,
Banner,
BUTTON_VARIANT,
Button,
GAP,
Stack,
TEXT_TONE,
TEXT_VARIANT,
Text,
} from "@luke/ui";
import type { Example } from "../examples";
import { CatalogSection } from "../section";

/** Every look, at the width the column gives them. */
function Looks(): React.JSX.Element {
return (
<Stack gap={GAP.FIELD}>
<Banner
look={BANNER_LOOK.STRIP}
tone={BANNER_TONE.ATTENTION}
glyph={<AlertIcon />}
actions={<Button variant={BUTTON_VARIANT.OUTLINE}>Move to Acme</Button>}
>
This plan's repository, acme/relay, belongs to the Acme workspace.
</Banner>
<Banner
look={BANNER_LOOK.CARD}
glyph={<ArchiveIcon />}
actions={<Button variant={BUTTON_VARIANT.PRIMARY}>Unarchive</Button>}
>
<Text variant={TEXT_VARIANT.SECTION}>This plan is archived</Text>
</Banner>
<Banner look={BANNER_LOOK.CARD} tone={BANNER_TONE.ATTENTION} glyph={<AlertIcon />}>
<Text variant={TEXT_VARIANT.SECTION}>Every new plan starts from this template</Text>
<Text tone={TEXT_TONE.MUTED}>Edit it to change what a plan holds when it is made.</Text>
</Banner>
<div className="catalog-frame">
<Banner
look={BANNER_LOOK.ROW}
glyph={<ArchiveIcon />}
actions={
<>
<Button>Stop</Button>
<Button variant={BUTTON_VARIANT.PRIMARY}>Unarchive</Button>
</>
}
>
<Text>Sam archived this agent: superseded by the new flow</Text>
</Banner>
</div>
</Stack>
);
}

function Banners(): React.JSX.Element {
return (
<>
<CatalogSection id="banner-looks" title="Looks and tones">
<Looks />
</CatalogSection>
<CatalogSection id="banner-narrow" title="Narrow column">
<div className="catalog-narrow">
<Looks />
</div>
</CatalogSection>
</>
);
}

export const BANNERS_EXAMPLE: Example = { id: "banners", title: "Banners", Render: Banners };
43 changes: 37 additions & 6 deletions apps/catalog/src/examples/hover-cards.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import {
BUTTON_VARIANT,
Button,
DocumentIcon,
FolderIcon,
GAP,
HOVER_CARD_ROW_LOOK,
HoverCard,
HoverCardHead,
HoverCardList,
HoverCardNote,
HoverCardRow,
HoverCardRule,
Inline,
PinIcon,
TooltipCard,
UserIcon,
} from "@luke/ui";
import { type ReactNode, useEffect, useId, useRef, useState } from "react";
import type { Example } from "../examples";
Expand All @@ -17,8 +23,9 @@ import { CatalogSection } from "../section";
/**
* hover-cards.tsx -- the words of a card hung in a pill's place: a head with
* its glyph, line, and corner; notes wrapped and on one line faded at its
* end; and a rule before the rows it lists, one of them a button the pointer
* crosses onto the card to press. Each card shows once the pointer rests on
* end; quieter rows under the name, one a press and one text; and a rule
* before the rows it lists, counting what it leaves out, each row a button
* the pointer crosses onto the card to press. Each card shows once the pointer rests on
* its control or the keyboard reaches it, and stands while the pointer is on
* the control, on the card, or between them: the catalog's own stand-in for
* the linger an app gives its cards.
Expand Down Expand Up @@ -149,11 +156,35 @@ function HoverCards(): React.JSX.Element {
card={
<HoverCard>
<HoverCardHead name="Teammate invitations" corner="2 working" />
<HoverCardRow
look={HOVER_CARD_ROW_LOOK.DETAIL}
glyph={<FolderIcon />}
label="acme/relay"
onPress={() => undefined}
/>
<HoverCardRow
look={HOVER_CARD_ROW_LOOK.DETAIL}
glyph={<UserIcon />}
label="Created by Sam Rivera · 3d ago"
/>
<HoverCardRule />
<Inline gap={GAP.CONTROL}>
<Button variant={BUTTON_VARIANT.OUTLINE}>Open Invite flow</Button>
<Button variant={BUTTON_VARIANT.OUTLINE}>Open Expiry sweep</Button>
</Inline>
<HoverCardList label="Agents" more="2 more">
<li>
<HoverCardRow
glyph={<DocumentIcon />}
label="Invite flow"
onPress={() => undefined}
/>
</li>
<li>
<HoverCardRow
glyph={<DocumentIcon />}
label="Expire every invitation a week after it was sent, and say so in the email"
end="+120 −14"
onPress={() => undefined}
/>
</li>
</HoverCardList>
</HoverCard>
}
/>
Expand Down
78 changes: 78 additions & 0 deletions apps/catalog/src/examples/one-line.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* one-line.tsx -- words held to one line in a narrow column, ending in an
* ellipsis and in a fade, and a short line that ends on its last letter;
* the pointer resting on a line cut short reads it whole in a pill, the
* catalog's own stand-in for the timing an app gives its hints.
*/

import {
GAP,
ONE_LINE_ELEMENT,
ONE_LINE_END,
OneLine,
type OneLineHint,
Stack,
TooltipPill,
} from "@luke/ui";
import { type ReactNode, useId, useState } from "react";
import type { Example } from "../examples";
import { CatalogSection } from "../section";

const ADDRESS = "sam.rivera+release-notifications@relay.example.com";

/** A pill reading `words` whole over a line, while the pointer rests on it and it is cut short. */
function PillOver({
words,
clipped,
children,
}: {
words: string;
clipped: boolean;
children: ReactNode;
}): React.JSX.Element {
const id = useId();
const [anchor, setAnchor] = useState<HTMLElement | undefined>(undefined);
return (
<>
<span
className="catalog-one-line"
onPointerEnter={(event) => setAnchor(event.currentTarget)}
onPointerLeave={() => setAnchor(undefined)}
>
{children}
</span>
{anchor === undefined || !clipped ? null : (
<TooltipPill id={id} anchor={anchor} instant={false} label={words} />
)}
</>
);
}

/** The catalog's hint for a line saying `words`. */
function pillHint(words: string): OneLineHint {
return (line, clipped) => (
<PillOver words={words} clipped={clipped}>
{line}
</PillOver>
);
}

function OneLines(): React.JSX.Element {
return (
<CatalogSection id="one-line-ends" title="Ends, in a narrow column">
<div className="catalog-narrow">
<Stack gap={GAP.CONTROL}>
<OneLine hint={pillHint(ADDRESS)}>{ADDRESS}</OneLine>
<OneLine as={ONE_LINE_ELEMENT.STRONG} end={ONE_LINE_END.FADE} hint={pillHint(ADDRESS)}>
{ADDRESS}
</OneLine>
<OneLine as={ONE_LINE_ELEMENT.SMALL} hint={pillHint("sam@relay.dev")}>
sam@relay.dev
</OneLine>
</Stack>
</div>
</CatalogSection>
);
}

export const ONE_LINE_EXAMPLE: Example = { id: "one-line", title: "One line", Render: OneLines };
Loading
Loading