@namroodinc/liquid-editorial 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/UPGRADING.md +25 -0
- package/dist/components/AssetCard/index.cjs +2 -0
- package/dist/components/AssetCard/index.cjs.map +1 -0
- package/dist/components/AssetCard/index.d.cts +41 -0
- package/dist/components/AssetCard/index.d.cts.map +1 -0
- package/dist/components/AssetCard/index.d.ts +41 -0
- package/dist/components/AssetCard/index.d.ts.map +1 -0
- package/dist/components/AssetCard/index.js +159 -0
- package/dist/components/AssetCard/index.js.map +1 -0
- package/dist/components/AssetCard/style.css +204 -0
- package/dist/components/AssetGrid/index.cjs +1 -1
- package/dist/components/AssetGrid/index.cjs.map +1 -1
- package/dist/components/AssetGrid/index.d.cts +5 -30
- package/dist/components/AssetGrid/index.d.cts.map +1 -1
- package/dist/components/AssetGrid/index.d.ts +5 -30
- package/dist/components/AssetGrid/index.d.ts.map +1 -1
- package/dist/components/AssetGrid/index.js +20 -107
- package/dist/components/AssetGrid/index.js.map +1 -1
- package/dist/components/AssetGrid/style.css +0 -161
- package/dist/components/Button/index.cjs +1 -1
- package/dist/components/Button/index.cjs.map +1 -1
- package/dist/components/Button/index.js +1 -1
- package/dist/components/Button/index.js.map +1 -1
- package/dist/components/Button/style.css +0 -16
- package/dist/components/Icon/style.css +18 -0
- package/dist/components/ImageCropper/index.cjs +1 -1
- package/dist/components/ImageCropper/index.cjs.map +1 -1
- package/dist/components/ImageCropper/index.d.cts +16 -1
- package/dist/components/ImageCropper/index.d.cts.map +1 -1
- package/dist/components/ImageCropper/index.d.ts +16 -1
- package/dist/components/ImageCropper/index.d.ts.map +1 -1
- package/dist/components/ImageCropper/index.js +143 -107
- package/dist/components/ImageCropper/index.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -33
- package/dist/styles.css +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -180,7 +180,7 @@ function CoverCropper({
|
|
|
180
180
|
|
|
181
181
|
Drag the image or focus the stage and use arrow keys (Shift for a larger step). The image always covers the fixed crop window; panning, zooming out, resizing, and rotation keep the crop inside the image. Zoom defaults to a maximum of `3`; set `maxZoom` to change it. Rotation is enabled by default; set `rotatable={false}` to hide its buttons. Reset clears pan, zoom, and rotation while retaining the supplied aspect.
|
|
182
182
|
|
|
183
|
-
Changing `aspect` resets pan and zoom while preserving rotation. There is no ratio picker or implicit square default. `onChange(crop, state)` reports image-pixel coordinates plus pan, zoom, and quarter-turn rotation. `cropToBlob` supports same-origin or CORS-enabled sources and remains usable after unmounting with the saved crop/state. The forwarded ref and native HTML attributes apply to the outer container.
|
|
183
|
+
Changing `aspect` resets pan and zoom while preserving rotation. There is no ratio picker or implicit square default. `onChange(crop, state)` reports image-pixel coordinates plus pan, zoom, and quarter-turn rotation. `state.cx` and `state.cy` give the crop centre as fractions of the unrotated source. Save the whole `state` and pass it back as `defaultState` to reopen the same crop; the offsets are stage pixels and change with the stage size. `defaultState` is uncontrolled and is applied again whenever `src` changes. `cropToBlob` supports same-origin or CORS-enabled sources and remains usable after unmounting with the saved crop/state. The forwarded ref and native HTML attributes apply to the outer container.
|
|
184
184
|
|
|
185
185
|
Cropper.js has been removed; no third-party cropper initialization or styles are needed. Keep importing the library's `styles.css`. See [UPGRADING.md](UPGRADING.md#unreleased) for migration from the temporary native Cropper.js API or the earlier shape-picker API.
|
|
186
186
|
|
package/UPGRADING.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.9
|
|
6
|
+
|
|
7
|
+
### Changes
|
|
8
|
+
|
|
9
|
+
- Added AssetCard and AssetCardSkeleton for asset libraries, source files, and crop variants. AssetCard shares AssetTile's props and adds optional fit (cover/contain), alt, description, and footer. Thumbnail state resets when the source changes. Contained previews show the complete picture at the largest size that fits an inset stage, scaling small renditions up, with the outline wrapping the picture, centred in either writing direction. Slots render only for truthy content, so `count && …` never leaves a stray `0` or an empty row. A preview that fails to load keeps a text alternative: "<alt> (preview unavailable)", or "Preview unavailable" when decorative.
|
|
10
|
+
- AssetCardSkeleton accepts `description` and `footer` to reserve those rows, and its lines and thumbnail now match a real card's height. AssetGrid passes `loadingCard={{ description, footer }}` to every placeholder.
|
|
11
|
+
- ImageCropper accepts optional `defaultState: { zoom, cx, cy, rotation? }`. `CropState` now also reports `cx` and `cy`, the crop centre as fractions of the unrotated source, so the state from `onChange` can be passed straight back. The selection is kept independent of the stage size: a frame that resizes after opening keeps the same crop instead of drifting, and zooming keeps the framed point centred. Initialization waits for the loaded source and measured frame and never emits a transient centred crop. Duplicate cached-image load events no longer reset edits. `onChange` runs after paint again, so a parent that re-renders on every drag move does not delay the frame.
|
|
12
|
+
- Pending spinners in Button and AssetCard share one `.ds-icon--spin` motion. Under reduced motion the AssetCard spinner now slows, as Button's does, instead of stopping.
|
|
13
|
+
|
|
14
|
+
### Adoption
|
|
15
|
+
|
|
16
|
+
Import AssetCard from the package root or `@namroodinc/liquid-editorial/components/AssetCard`, retaining the aggregate CSS import. Replace AssetTile with AssetCard using the same existing props. Use `fit="contain"` for full crop previews, `alt` for meaningful preview descriptions, `description` for source details, `actions` for thumbnail overlays, and `footer` for controls such as Edit crop. `onSelect` makes the thumbnail a selection toggle; put other actions in `actions` or `footer`. When grid cards use `description` or `footer`, pass the same slots to `AssetGrid`'s `loadingCard` (or to `AssetCardSkeleton`). Pass strings, not numbers, for metadata that may be zero. Keep data loading, permissions, warnings, and publishing rules in the consuming application.
|
|
17
|
+
|
|
18
|
+
Save the `state` from `onChange` (or its `zoom`, `cx`, `cy`, and `rotation`) and pass it back as `defaultState` when reopening the editor; do not persist `offsetX`/`offsetY`, which are stage pixels. Remount for a different crop or aspect; changing `src` applies `defaultState` again. `defaultState` is uncontrolled: changing it on the same loaded source does not reset user edits. Reset still centres at 1× and clears rotation.
|
|
19
|
+
|
|
20
|
+
### Compatibility
|
|
21
|
+
|
|
22
|
+
Existing cropper props, exports, and initial behaviour without defaultState remain supported. Code that builds `CropState` objects itself must add `cx` and `cy`; reading them needs no change. Zooming now holds the framed point rather than the pixel offset, and offsets scale with the stage when it resizes. AssetTile, AssetTileProps, AssetTileSkeleton, and AssetTileSkeletonProps remain aliases from their original entry points. AssetTile keeps announcing its thumbnail as a toggle (`aria-pressed="false"` when unselected) and keeps hiding falsy `meta`. Existing `.ds-asset-tile*` class hooks are still rendered and still style hand-written tile markup on their own. Tokens, cover previews, and action placement remain supported. Images inside `actions` are no longer affected by thumbnail image styles. Button's spinner keeps its `.ds-button__spinner` hook. AssetCard uses the existing aggregate stylesheet; no new dependency or additional CSS import is needed. Release versioning remains owned by the main-branch workflow.
|
|
23
|
+
|
|
24
|
+
### Verification
|
|
25
|
+
|
|
26
|
+
Check asset/source cover previews and contained crop previews in every theme, at narrow and wide widths, and with `dir="rtl"`, including a small rendition and an SVG without intrinsic size. Exercise image loading, cached images (including a cached SVG), broken images with and without `alt`, recovery after source changes, pending state, keyboard selection, independent overlay/footer actions, long names, video/PDF fallbacks, and legacy exports and markup. Compare loading and loaded grids that use `description` and `footer`. Validate a packed build in consuming applications before registry adoption.
|
|
27
|
+
|
|
28
|
+
Save a rotated, panned, and zoomed crop, reopen it from the saved state, and confirm the same crop. Open the editor in a modal or drawer whose frame settles after opening, and confirm the crop does not move. Check cached and delayed image loads, source switching, invalid values, clamping, Reset, rotation, and multiple instances. Run library validation and consuming application checks.
|
|
29
|
+
|
|
5
30
|
## 0.1.8
|
|
6
31
|
|
|
7
32
|
### Changes
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../chunks/Icon-CWsA1W83.cjs"),t=require("../Button/index.cjs"),n=require("../Skeleton/index.cjs");let r=require("react"),i=require("react/jsx-runtime");function a({icon:t,label:n}){return(0,i.jsx)(`span`,{className:`ds-asset-card__placeholder ds-asset-tile__placeholder`,children:(0,i.jsx)(e.t,{name:t,size:24,"aria-label":n})})}function o({src:e,alt:t}){let[o,s]=(0,r.useState)(`loading`),[c,l]=(0,r.useState)(),u=(0,r.useCallback)(e=>{s(`loaded`),e.naturalWidth&&e.naturalHeight&&l(e.naturalWidth/e.naturalHeight)},[]),d=(0,r.useCallback)(e=>{e?.complete&&(e.naturalWidth>0?u(e):e.decode().then(()=>u(e),()=>{}))},[u]);return o===`failed`?(0,i.jsx)(a,{icon:`alert-triangle`,label:t?`${t} (preview unavailable)`:`Preview unavailable`}):(0,i.jsxs)(i.Fragment,{children:[o===`loading`&&(0,i.jsx)(n.Skeleton,{shape:`block`,className:`ds-asset-card__thumb-skeleton ds-asset-tile__thumb-skeleton`}),(0,i.jsx)(`img`,{ref:d,src:e,alt:t,loading:`lazy`,"data-loaded":o===`loaded`||void 0,style:c?{"--asset-ratio":c}:void 0,onLoad:e=>u(e.currentTarget),onError:()=>s(`failed`)})]})}var s=(0,r.forwardRef)(function({name:n,src:r,alt:s=``,icon:c=`image`,fit:l=`cover`,meta:u,description:d,isSelected:f=!1,isPending:p=!1,onSelect:m,actions:h,footer:g,className:_,...v},y){return(0,i.jsxs)(`div`,{ref:y,className:[`ds-asset-card`,`ds-asset-tile`,_].filter(Boolean).join(` `),"data-fit":l,"data-selected":f||void 0,"data-pending":p||void 0,...v,children:[(0,i.jsxs)(`div`,{className:`ds-asset-card__thumb ds-asset-tile__thumb`,children:[r?(0,i.jsx)(o,{src:r,alt:s},r):(0,i.jsx)(a,{icon:c}),p&&(0,i.jsxs)(`span`,{className:`ds-asset-card__pending ds-asset-tile__pending`,role:`status`,children:[(0,i.jsx)(e.t,{name:`spinner`,size:20,className:`ds-asset-card__spinner ds-asset-tile__spinner ds-icon--spin`}),(0,i.jsx)(`span`,{className:`ds-asset-card__pending-label ds-asset-tile__pending-label`,children:`Uploading`})]}),h?(0,i.jsx)(`div`,{className:`ds-asset-card__actions ds-asset-tile__actions`,children:h}):null,m&&(0,i.jsx)(t.Button,{variant:`simple`,className:`ds-asset-card__hit ds-asset-tile__hit`,"aria-pressed":f,"aria-label":n,isDisabled:p,onClick:m})]}),(0,i.jsxs)(`div`,{className:`ds-asset-card__text ds-asset-tile__text`,children:[(0,i.jsx)(`span`,{className:`ds-asset-card__name ds-asset-tile__name`,children:n}),u?(0,i.jsx)(`div`,{className:`ds-asset-card__meta ds-asset-tile__meta`,children:u}):null,d?(0,i.jsx)(`div`,{className:`ds-asset-card__description`,children:d}):null]}),g?(0,i.jsx)(`div`,{className:`ds-asset-card__footer`,children:g}):null]})});function c({index:e=0,description:t=!1,footer:r=!1}){let a={animationDelay:`calc(var(--skeleton-stagger) * ${e})`};return(0,i.jsxs)(`div`,{className:`ds-asset-card ds-asset-tile`,"data-loading":`true`,"aria-hidden":`true`,children:[(0,i.jsx)(n.Skeleton,{shape:`block`,className:`ds-asset-card__thumb ds-asset-tile__thumb`,style:a}),(0,i.jsxs)(`div`,{className:`ds-asset-card__text ds-asset-tile__text`,children:[(0,i.jsx)(n.Skeleton,{shape:`text`,width:`80%`,style:a}),(0,i.jsx)(n.Skeleton,{shape:`text`,width:`45%`,style:a}),t&&(0,i.jsx)(n.Skeleton,{shape:`text`,width:`65%`,style:a})]}),r&&(0,i.jsx)(`div`,{className:`ds-asset-card__footer`,children:(0,i.jsx)(n.Skeleton,{shape:`block`,width:`45%`,style:{...a,blockSize:`var(--field-height-sm)`}})})]})}exports.AssetCard=s,exports.AssetCardSkeleton=c;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["Icon","useState","useCallback","_Fragment","Skeleton","forwardRef","Button","require_Icon","require_components_Button_index","require_components_Skeleton_index","react","react_jsx_runtime","Placeholder","icon","label","AssetImage","status","setStatus","ratio","setRatio","adopt","image","imageRef","alt","event","AssetCard","rest","className","fit","isSelected","isPending","src","actions","onSelect","name","meta","description","footer","AssetCardSkeleton","delay","index"],"sources":["../../../src/components/AssetCard/index.tsx"],"sourcesContent":["import {\n forwardRef,\n useCallback,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { Button } from \"../Button\";\nimport { Icon } from \"../Icon\";\nimport type { IconName } from \"../Icon/icons\";\nimport { Skeleton } from \"../Skeleton\";\nimport \"./style.css\";\n\nexport interface AssetCardProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\" | \"title\"> {\n /** File name or crop label, displayed in full. */\n name: string;\n /** Thumbnail source. Omit to display the fallback icon. */\n src?: string;\n /** Alternative text for the preview; empty when the image is decorative. */\n alt?: string;\n /** Fallback for assets without a thumbnail, such as videos and PDFs. */\n icon?: IconName;\n /** Fill the frame, or show the complete image on an inset preview stage. */\n fit?: \"cover\" | \"contain\";\n /** Dimensions, size, ratio, or other metadata below the name. */\n meta?: ReactNode;\n /** Supporting content below the metadata, such as a source file name. */\n description?: ReactNode;\n /** Paint the selection fill and rail without changing the card's size. */\n isSelected?: boolean;\n /** Show an uploading overlay and disable the thumbnail action. */\n isPending?: boolean;\n /** Makes the thumbnail a selection toggle, independent of overlay and footer controls. */\n onSelect?: () => void;\n /** Badges, buttons, or menus pinned across the top of the thumbnail. */\n actions?: ReactNode;\n /** Actions or content below the card's text. */\n footer?: ReactNode;\n}\n\nfunction Placeholder({ icon, label }: { icon: IconName; label?: string }) {\n return <span className=\"ds-asset-card__placeholder ds-asset-tile__placeholder\"><Icon name={icon} size={24} aria-label={label} /></span>;\n}\n\n/** Remount for each source so failed and loaded states cannot leak to another image. */\nfunction AssetImage({ src, alt }: { src: string; alt: string }) {\n const [status, setStatus] = useState<\"loading\" | \"loaded\" | \"failed\">(\"loading\");\n // The picture's own shape lets a contained preview fill the stage exactly.\n const [ratio, setRatio] = useState<number>();\n const adopt = useCallback((image: HTMLImageElement) => {\n setStatus(\"loaded\");\n if (image.naturalWidth && image.naturalHeight) setRatio(image.naturalWidth / image.naturalHeight);\n }, []);\n // A cached image can finish before onLoad is attached. Only success is read\n // here: an SVG without intrinsic size and a deferred lazy image also report\n // a zero naturalWidth, so failure is left to the error event.\n const imageRef = useCallback((image: HTMLImageElement | null) => {\n if (!image?.complete) return;\n if (image.naturalWidth > 0) adopt(image);\n else image.decode().then(() => adopt(image), () => {});\n }, [adopt]);\n\n if (status === \"failed\") {\n return <Placeholder icon=\"alert-triangle\" label={alt ? `${alt} (preview unavailable)` : \"Preview unavailable\"} />;\n }\n\n return <>\n {status === \"loading\" && <Skeleton shape=\"block\" className=\"ds-asset-card__thumb-skeleton ds-asset-tile__thumb-skeleton\" />}\n <img\n ref={imageRef}\n src={src}\n alt={alt}\n loading=\"lazy\"\n data-loaded={status === \"loaded\" || undefined}\n style={ratio ? ({ \"--asset-ratio\": ratio } as CSSProperties) : undefined}\n onLoad={(event) => adopt(event.currentTarget)}\n onError={() => setStatus(\"failed\")}\n />\n </>;\n}\n\n/** Shared presentation for assets, source files, and crop variants. */\nexport const AssetCard = forwardRef<HTMLDivElement, AssetCardProps>(function AssetCard(\n {\n name,\n src,\n alt = \"\",\n icon = \"image\",\n fit = \"cover\",\n meta,\n description,\n isSelected = false,\n isPending = false,\n onSelect,\n actions,\n footer,\n className,\n ...rest\n },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={[\"ds-asset-card\", \"ds-asset-tile\", className].filter(Boolean).join(\" \")}\n data-fit={fit}\n data-selected={isSelected || undefined}\n data-pending={isPending || undefined}\n {...rest}\n >\n <div className=\"ds-asset-card__thumb ds-asset-tile__thumb\">\n {src ? <AssetImage key={src} src={src} alt={alt} /> : <Placeholder icon={icon} />}\n {isPending && (\n <span className=\"ds-asset-card__pending ds-asset-tile__pending\" role=\"status\">\n <Icon name=\"spinner\" size={20} className=\"ds-asset-card__spinner ds-asset-tile__spinner ds-icon--spin\" />\n <span className=\"ds-asset-card__pending-label ds-asset-tile__pending-label\">Uploading</span>\n </span>\n )}\n {/* Slots render only for truthy content, so `count && …` never leaves a stray 0. */}\n {actions ? <div className=\"ds-asset-card__actions ds-asset-tile__actions\">{actions}</div> : null}\n {onSelect && (\n <Button\n variant=\"simple\"\n className=\"ds-asset-card__hit ds-asset-tile__hit\"\n aria-pressed={isSelected}\n aria-label={name}\n isDisabled={isPending}\n onClick={onSelect}\n />\n )}\n </div>\n <div className=\"ds-asset-card__text ds-asset-tile__text\">\n <span className=\"ds-asset-card__name ds-asset-tile__name\">{name}</span>\n {meta ? <div className=\"ds-asset-card__meta ds-asset-tile__meta\">{meta}</div> : null}\n {description ? <div className=\"ds-asset-card__description\">{description}</div> : null}\n </div>\n {footer ? <div className=\"ds-asset-card__footer\">{footer}</div> : null}\n </div>\n );\n});\n\nexport interface AssetCardSkeletonProps {\n /** Position in the collection, used for the skeleton stagger. */\n index?: number;\n /** Reserve the description line, for cards that pass `description`. */\n description?: boolean;\n /** Reserve a footer row the height of a small Button, for cards that pass `footer`. */\n footer?: boolean;\n}\n\n/** A loading card. Match its slots to the real cards so nothing moves when they arrive. */\nexport function AssetCardSkeleton({ index = 0, description = false, footer = false }: AssetCardSkeletonProps) {\n const delay = { animationDelay: `calc(var(--skeleton-stagger) * ${index})` };\n return (\n <div className=\"ds-asset-card ds-asset-tile\" data-loading=\"true\" aria-hidden=\"true\">\n <Skeleton shape=\"block\" className=\"ds-asset-card__thumb ds-asset-tile__thumb\" style={delay} />\n <div className=\"ds-asset-card__text ds-asset-tile__text\">\n <Skeleton shape=\"text\" width=\"80%\" style={delay} />\n <Skeleton shape=\"text\" width=\"45%\" style={delay} />\n {description && <Skeleton shape=\"text\" width=\"65%\" style={delay} />}\n </div>\n {footer && (\n <div className=\"ds-asset-card__footer\">\n <Skeleton shape=\"block\" width=\"45%\" style={{ ...delay, blockSize: \"var(--field-height-sm)\" }} />\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import type { IconName } from "../Icon/icons.cjs";
|
|
3
|
+
export interface AssetCardProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSelect" | "title"> {
|
|
4
|
+
/** File name or crop label, displayed in full. */
|
|
5
|
+
name: string;
|
|
6
|
+
/** Thumbnail source. Omit to display the fallback icon. */
|
|
7
|
+
src?: string;
|
|
8
|
+
/** Alternative text for the preview; empty when the image is decorative. */
|
|
9
|
+
alt?: string;
|
|
10
|
+
/** Fallback for assets without a thumbnail, such as videos and PDFs. */
|
|
11
|
+
icon?: IconName;
|
|
12
|
+
/** Fill the frame, or show the complete image on an inset preview stage. */
|
|
13
|
+
fit?: "cover" | "contain";
|
|
14
|
+
/** Dimensions, size, ratio, or other metadata below the name. */
|
|
15
|
+
meta?: ReactNode;
|
|
16
|
+
/** Supporting content below the metadata, such as a source file name. */
|
|
17
|
+
description?: ReactNode;
|
|
18
|
+
/** Paint the selection fill and rail without changing the card's size. */
|
|
19
|
+
isSelected?: boolean;
|
|
20
|
+
/** Show an uploading overlay and disable the thumbnail action. */
|
|
21
|
+
isPending?: boolean;
|
|
22
|
+
/** Makes the thumbnail a selection toggle, independent of overlay and footer controls. */
|
|
23
|
+
onSelect?: () => void;
|
|
24
|
+
/** Badges, buttons, or menus pinned across the top of the thumbnail. */
|
|
25
|
+
actions?: ReactNode;
|
|
26
|
+
/** Actions or content below the card's text. */
|
|
27
|
+
footer?: ReactNode;
|
|
28
|
+
}
|
|
29
|
+
/** Shared presentation for assets, source files, and crop variants. */
|
|
30
|
+
export declare const AssetCard: import("react").ForwardRefExoticComponent<AssetCardProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
31
|
+
export interface AssetCardSkeletonProps {
|
|
32
|
+
/** Position in the collection, used for the skeleton stagger. */
|
|
33
|
+
index?: number;
|
|
34
|
+
/** Reserve the description line, for cards that pass `description`. */
|
|
35
|
+
description?: boolean;
|
|
36
|
+
/** Reserve a footer row the height of a small Button, for cards that pass `footer`. */
|
|
37
|
+
footer?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** A loading card. Match its slots to the real cards so nothing moves when they arrive. */
|
|
40
|
+
export declare function AssetCardSkeleton({ index, description, footer }: AssetCardSkeletonProps): import("react").JSX.Element;
|
|
41
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../../src/components/AssetCard/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,aAAa,CAAC;AAErB,MAAM,WAAW,cACf,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;IAClE,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,iEAAiE;IACjE,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,yEAAyE;IACzE,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kEAAkE;IAClE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,wEAAwE;IACxE,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,gDAAgD;IAChD,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AA2CD,uEAAuE;AACvE,eAAO,MAAM,SAAS,2GAyDpB,CAAC;AAEH,MAAM,WAAW,sBAAsB;IACrC,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uFAAuF;IACvF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,2FAA2F;AAC3F,wBAAgB,iBAAiB,CAAC,EAAE,KAAS,EAAE,WAAmB,EAAE,MAAc,EAAE,EAAE,sBAAsB,+BAiB3G"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import type { IconName } from "../Icon/icons.js";
|
|
3
|
+
export interface AssetCardProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSelect" | "title"> {
|
|
4
|
+
/** File name or crop label, displayed in full. */
|
|
5
|
+
name: string;
|
|
6
|
+
/** Thumbnail source. Omit to display the fallback icon. */
|
|
7
|
+
src?: string;
|
|
8
|
+
/** Alternative text for the preview; empty when the image is decorative. */
|
|
9
|
+
alt?: string;
|
|
10
|
+
/** Fallback for assets without a thumbnail, such as videos and PDFs. */
|
|
11
|
+
icon?: IconName;
|
|
12
|
+
/** Fill the frame, or show the complete image on an inset preview stage. */
|
|
13
|
+
fit?: "cover" | "contain";
|
|
14
|
+
/** Dimensions, size, ratio, or other metadata below the name. */
|
|
15
|
+
meta?: ReactNode;
|
|
16
|
+
/** Supporting content below the metadata, such as a source file name. */
|
|
17
|
+
description?: ReactNode;
|
|
18
|
+
/** Paint the selection fill and rail without changing the card's size. */
|
|
19
|
+
isSelected?: boolean;
|
|
20
|
+
/** Show an uploading overlay and disable the thumbnail action. */
|
|
21
|
+
isPending?: boolean;
|
|
22
|
+
/** Makes the thumbnail a selection toggle, independent of overlay and footer controls. */
|
|
23
|
+
onSelect?: () => void;
|
|
24
|
+
/** Badges, buttons, or menus pinned across the top of the thumbnail. */
|
|
25
|
+
actions?: ReactNode;
|
|
26
|
+
/** Actions or content below the card's text. */
|
|
27
|
+
footer?: ReactNode;
|
|
28
|
+
}
|
|
29
|
+
/** Shared presentation for assets, source files, and crop variants. */
|
|
30
|
+
export declare const AssetCard: import("react").ForwardRefExoticComponent<AssetCardProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
31
|
+
export interface AssetCardSkeletonProps {
|
|
32
|
+
/** Position in the collection, used for the skeleton stagger. */
|
|
33
|
+
index?: number;
|
|
34
|
+
/** Reserve the description line, for cards that pass `description`. */
|
|
35
|
+
description?: boolean;
|
|
36
|
+
/** Reserve a footer row the height of a small Button, for cards that pass `footer`. */
|
|
37
|
+
footer?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** A loading card. Match its slots to the real cards so nothing moves when they arrive. */
|
|
40
|
+
export declare function AssetCardSkeleton({ index, description, footer }: AssetCardSkeletonProps): import("react").JSX.Element;
|
|
41
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/AssetCard/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,aAAa,CAAC;AAErB,MAAM,WAAW,cACf,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;IAClE,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,iEAAiE;IACjE,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,yEAAyE;IACzE,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kEAAkE;IAClE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,wEAAwE;IACxE,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,gDAAgD;IAChD,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AA2CD,uEAAuE;AACvE,eAAO,MAAM,SAAS,2GAyDpB,CAAC;AAEH,MAAM,WAAW,sBAAsB;IACrC,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uFAAuF;IACvF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,2FAA2F;AAC3F,wBAAgB,iBAAiB,CAAC,EAAE,KAAS,EAAE,WAAmB,EAAE,MAAc,EAAE,EAAE,sBAAsB,+BAiB3G"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { t as e } from "../../chunks/Icon-CGY1FNXT.js";
|
|
2
|
+
import { Button as t } from "../Button/index.js";
|
|
3
|
+
import { Skeleton as n } from "../Skeleton/index.js";
|
|
4
|
+
import { forwardRef as r, useCallback as i, useState as a } from "react";
|
|
5
|
+
import { Fragment as o, jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
6
|
+
//#region src/components/AssetCard/index.tsx
|
|
7
|
+
function l({ icon: t, label: n }) {
|
|
8
|
+
return /* @__PURE__ */ s("span", {
|
|
9
|
+
className: "ds-asset-card__placeholder ds-asset-tile__placeholder",
|
|
10
|
+
children: /* @__PURE__ */ s(e, {
|
|
11
|
+
name: t,
|
|
12
|
+
size: 24,
|
|
13
|
+
"aria-label": n
|
|
14
|
+
})
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function u({ src: e, alt: t }) {
|
|
18
|
+
let [r, u] = a("loading"), [d, f] = a(), p = i((e) => {
|
|
19
|
+
u("loaded"), e.naturalWidth && e.naturalHeight && f(e.naturalWidth / e.naturalHeight);
|
|
20
|
+
}, []), m = i((e) => {
|
|
21
|
+
e?.complete && (e.naturalWidth > 0 ? p(e) : e.decode().then(() => p(e), () => {}));
|
|
22
|
+
}, [p]);
|
|
23
|
+
return r === "failed" ? /* @__PURE__ */ s(l, {
|
|
24
|
+
icon: "alert-triangle",
|
|
25
|
+
label: t ? `${t} (preview unavailable)` : "Preview unavailable"
|
|
26
|
+
}) : /* @__PURE__ */ c(o, { children: [r === "loading" && /* @__PURE__ */ s(n, {
|
|
27
|
+
shape: "block",
|
|
28
|
+
className: "ds-asset-card__thumb-skeleton ds-asset-tile__thumb-skeleton"
|
|
29
|
+
}), /* @__PURE__ */ s("img", {
|
|
30
|
+
ref: m,
|
|
31
|
+
src: e,
|
|
32
|
+
alt: t,
|
|
33
|
+
loading: "lazy",
|
|
34
|
+
"data-loaded": r === "loaded" || void 0,
|
|
35
|
+
style: d ? { "--asset-ratio": d } : void 0,
|
|
36
|
+
onLoad: (e) => p(e.currentTarget),
|
|
37
|
+
onError: () => u("failed")
|
|
38
|
+
})] });
|
|
39
|
+
}
|
|
40
|
+
var d = r(function({ name: n, src: r, alt: i = "", icon: a = "image", fit: o = "cover", meta: d, description: f, isSelected: p = !1, isPending: m = !1, onSelect: h, actions: g, footer: _, className: v, ...y }, b) {
|
|
41
|
+
return /* @__PURE__ */ c("div", {
|
|
42
|
+
ref: b,
|
|
43
|
+
className: [
|
|
44
|
+
"ds-asset-card",
|
|
45
|
+
"ds-asset-tile",
|
|
46
|
+
v
|
|
47
|
+
].filter(Boolean).join(" "),
|
|
48
|
+
"data-fit": o,
|
|
49
|
+
"data-selected": p || void 0,
|
|
50
|
+
"data-pending": m || void 0,
|
|
51
|
+
...y,
|
|
52
|
+
children: [
|
|
53
|
+
/* @__PURE__ */ c("div", {
|
|
54
|
+
className: "ds-asset-card__thumb ds-asset-tile__thumb",
|
|
55
|
+
children: [
|
|
56
|
+
r ? /* @__PURE__ */ s(u, {
|
|
57
|
+
src: r,
|
|
58
|
+
alt: i
|
|
59
|
+
}, r) : /* @__PURE__ */ s(l, { icon: a }),
|
|
60
|
+
m && /* @__PURE__ */ c("span", {
|
|
61
|
+
className: "ds-asset-card__pending ds-asset-tile__pending",
|
|
62
|
+
role: "status",
|
|
63
|
+
children: [/* @__PURE__ */ s(e, {
|
|
64
|
+
name: "spinner",
|
|
65
|
+
size: 20,
|
|
66
|
+
className: "ds-asset-card__spinner ds-asset-tile__spinner ds-icon--spin"
|
|
67
|
+
}), /* @__PURE__ */ s("span", {
|
|
68
|
+
className: "ds-asset-card__pending-label ds-asset-tile__pending-label",
|
|
69
|
+
children: "Uploading"
|
|
70
|
+
})]
|
|
71
|
+
}),
|
|
72
|
+
g ? /* @__PURE__ */ s("div", {
|
|
73
|
+
className: "ds-asset-card__actions ds-asset-tile__actions",
|
|
74
|
+
children: g
|
|
75
|
+
}) : null,
|
|
76
|
+
h && /* @__PURE__ */ s(t, {
|
|
77
|
+
variant: "simple",
|
|
78
|
+
className: "ds-asset-card__hit ds-asset-tile__hit",
|
|
79
|
+
"aria-pressed": p,
|
|
80
|
+
"aria-label": n,
|
|
81
|
+
isDisabled: m,
|
|
82
|
+
onClick: h
|
|
83
|
+
})
|
|
84
|
+
]
|
|
85
|
+
}),
|
|
86
|
+
/* @__PURE__ */ c("div", {
|
|
87
|
+
className: "ds-asset-card__text ds-asset-tile__text",
|
|
88
|
+
children: [
|
|
89
|
+
/* @__PURE__ */ s("span", {
|
|
90
|
+
className: "ds-asset-card__name ds-asset-tile__name",
|
|
91
|
+
children: n
|
|
92
|
+
}),
|
|
93
|
+
d ? /* @__PURE__ */ s("div", {
|
|
94
|
+
className: "ds-asset-card__meta ds-asset-tile__meta",
|
|
95
|
+
children: d
|
|
96
|
+
}) : null,
|
|
97
|
+
f ? /* @__PURE__ */ s("div", {
|
|
98
|
+
className: "ds-asset-card__description",
|
|
99
|
+
children: f
|
|
100
|
+
}) : null
|
|
101
|
+
]
|
|
102
|
+
}),
|
|
103
|
+
_ ? /* @__PURE__ */ s("div", {
|
|
104
|
+
className: "ds-asset-card__footer",
|
|
105
|
+
children: _
|
|
106
|
+
}) : null
|
|
107
|
+
]
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
function f({ index: e = 0, description: t = !1, footer: r = !1 }) {
|
|
111
|
+
let i = { animationDelay: `calc(var(--skeleton-stagger) * ${e})` };
|
|
112
|
+
return /* @__PURE__ */ c("div", {
|
|
113
|
+
className: "ds-asset-card ds-asset-tile",
|
|
114
|
+
"data-loading": "true",
|
|
115
|
+
"aria-hidden": "true",
|
|
116
|
+
children: [
|
|
117
|
+
/* @__PURE__ */ s(n, {
|
|
118
|
+
shape: "block",
|
|
119
|
+
className: "ds-asset-card__thumb ds-asset-tile__thumb",
|
|
120
|
+
style: i
|
|
121
|
+
}),
|
|
122
|
+
/* @__PURE__ */ c("div", {
|
|
123
|
+
className: "ds-asset-card__text ds-asset-tile__text",
|
|
124
|
+
children: [
|
|
125
|
+
/* @__PURE__ */ s(n, {
|
|
126
|
+
shape: "text",
|
|
127
|
+
width: "80%",
|
|
128
|
+
style: i
|
|
129
|
+
}),
|
|
130
|
+
/* @__PURE__ */ s(n, {
|
|
131
|
+
shape: "text",
|
|
132
|
+
width: "45%",
|
|
133
|
+
style: i
|
|
134
|
+
}),
|
|
135
|
+
t && /* @__PURE__ */ s(n, {
|
|
136
|
+
shape: "text",
|
|
137
|
+
width: "65%",
|
|
138
|
+
style: i
|
|
139
|
+
})
|
|
140
|
+
]
|
|
141
|
+
}),
|
|
142
|
+
r && /* @__PURE__ */ s("div", {
|
|
143
|
+
className: "ds-asset-card__footer",
|
|
144
|
+
children: /* @__PURE__ */ s(n, {
|
|
145
|
+
shape: "block",
|
|
146
|
+
width: "45%",
|
|
147
|
+
style: {
|
|
148
|
+
...i,
|
|
149
|
+
blockSize: "var(--field-height-sm)"
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
]
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
export { d as AssetCard, f as AssetCardSkeleton };
|
|
158
|
+
|
|
159
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["_Fragment","Icon","Fragment$1","Placeholder","icon","label","AssetImage","status","setStatus","useState","ratio","setRatio","adopt","useCallback","image","imageRef","alt","Skeleton","event","AssetCard","forwardRef","rest","className","fit","isSelected","isPending","src","actions","onSelect","Button","name","meta","description","footer","AssetCardSkeleton","delay","index"],"sources":["../../../src/components/AssetCard/index.tsx"],"sourcesContent":["import {\n forwardRef,\n useCallback,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { Button } from \"../Button\";\nimport { Icon } from \"../Icon\";\nimport type { IconName } from \"../Icon/icons\";\nimport { Skeleton } from \"../Skeleton\";\nimport \"./style.css\";\n\nexport interface AssetCardProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\" | \"title\"> {\n /** File name or crop label, displayed in full. */\n name: string;\n /** Thumbnail source. Omit to display the fallback icon. */\n src?: string;\n /** Alternative text for the preview; empty when the image is decorative. */\n alt?: string;\n /** Fallback for assets without a thumbnail, such as videos and PDFs. */\n icon?: IconName;\n /** Fill the frame, or show the complete image on an inset preview stage. */\n fit?: \"cover\" | \"contain\";\n /** Dimensions, size, ratio, or other metadata below the name. */\n meta?: ReactNode;\n /** Supporting content below the metadata, such as a source file name. */\n description?: ReactNode;\n /** Paint the selection fill and rail without changing the card's size. */\n isSelected?: boolean;\n /** Show an uploading overlay and disable the thumbnail action. */\n isPending?: boolean;\n /** Makes the thumbnail a selection toggle, independent of overlay and footer controls. */\n onSelect?: () => void;\n /** Badges, buttons, or menus pinned across the top of the thumbnail. */\n actions?: ReactNode;\n /** Actions or content below the card's text. */\n footer?: ReactNode;\n}\n\nfunction Placeholder({ icon, label }: { icon: IconName; label?: string }) {\n return <span className=\"ds-asset-card__placeholder ds-asset-tile__placeholder\"><Icon name={icon} size={24} aria-label={label} /></span>;\n}\n\n/** Remount for each source so failed and loaded states cannot leak to another image. */\nfunction AssetImage({ src, alt }: { src: string; alt: string }) {\n const [status, setStatus] = useState<\"loading\" | \"loaded\" | \"failed\">(\"loading\");\n // The picture's own shape lets a contained preview fill the stage exactly.\n const [ratio, setRatio] = useState<number>();\n const adopt = useCallback((image: HTMLImageElement) => {\n setStatus(\"loaded\");\n if (image.naturalWidth && image.naturalHeight) setRatio(image.naturalWidth / image.naturalHeight);\n }, []);\n // A cached image can finish before onLoad is attached. Only success is read\n // here: an SVG without intrinsic size and a deferred lazy image also report\n // a zero naturalWidth, so failure is left to the error event.\n const imageRef = useCallback((image: HTMLImageElement | null) => {\n if (!image?.complete) return;\n if (image.naturalWidth > 0) adopt(image);\n else image.decode().then(() => adopt(image), () => {});\n }, [adopt]);\n\n if (status === \"failed\") {\n return <Placeholder icon=\"alert-triangle\" label={alt ? `${alt} (preview unavailable)` : \"Preview unavailable\"} />;\n }\n\n return <>\n {status === \"loading\" && <Skeleton shape=\"block\" className=\"ds-asset-card__thumb-skeleton ds-asset-tile__thumb-skeleton\" />}\n <img\n ref={imageRef}\n src={src}\n alt={alt}\n loading=\"lazy\"\n data-loaded={status === \"loaded\" || undefined}\n style={ratio ? ({ \"--asset-ratio\": ratio } as CSSProperties) : undefined}\n onLoad={(event) => adopt(event.currentTarget)}\n onError={() => setStatus(\"failed\")}\n />\n </>;\n}\n\n/** Shared presentation for assets, source files, and crop variants. */\nexport const AssetCard = forwardRef<HTMLDivElement, AssetCardProps>(function AssetCard(\n {\n name,\n src,\n alt = \"\",\n icon = \"image\",\n fit = \"cover\",\n meta,\n description,\n isSelected = false,\n isPending = false,\n onSelect,\n actions,\n footer,\n className,\n ...rest\n },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={[\"ds-asset-card\", \"ds-asset-tile\", className].filter(Boolean).join(\" \")}\n data-fit={fit}\n data-selected={isSelected || undefined}\n data-pending={isPending || undefined}\n {...rest}\n >\n <div className=\"ds-asset-card__thumb ds-asset-tile__thumb\">\n {src ? <AssetImage key={src} src={src} alt={alt} /> : <Placeholder icon={icon} />}\n {isPending && (\n <span className=\"ds-asset-card__pending ds-asset-tile__pending\" role=\"status\">\n <Icon name=\"spinner\" size={20} className=\"ds-asset-card__spinner ds-asset-tile__spinner ds-icon--spin\" />\n <span className=\"ds-asset-card__pending-label ds-asset-tile__pending-label\">Uploading</span>\n </span>\n )}\n {/* Slots render only for truthy content, so `count && …` never leaves a stray 0. */}\n {actions ? <div className=\"ds-asset-card__actions ds-asset-tile__actions\">{actions}</div> : null}\n {onSelect && (\n <Button\n variant=\"simple\"\n className=\"ds-asset-card__hit ds-asset-tile__hit\"\n aria-pressed={isSelected}\n aria-label={name}\n isDisabled={isPending}\n onClick={onSelect}\n />\n )}\n </div>\n <div className=\"ds-asset-card__text ds-asset-tile__text\">\n <span className=\"ds-asset-card__name ds-asset-tile__name\">{name}</span>\n {meta ? <div className=\"ds-asset-card__meta ds-asset-tile__meta\">{meta}</div> : null}\n {description ? <div className=\"ds-asset-card__description\">{description}</div> : null}\n </div>\n {footer ? <div className=\"ds-asset-card__footer\">{footer}</div> : null}\n </div>\n );\n});\n\nexport interface AssetCardSkeletonProps {\n /** Position in the collection, used for the skeleton stagger. */\n index?: number;\n /** Reserve the description line, for cards that pass `description`. */\n description?: boolean;\n /** Reserve a footer row the height of a small Button, for cards that pass `footer`. */\n footer?: boolean;\n}\n\n/** A loading card. Match its slots to the real cards so nothing moves when they arrive. */\nexport function AssetCardSkeleton({ index = 0, description = false, footer = false }: AssetCardSkeletonProps) {\n const delay = { animationDelay: `calc(var(--skeleton-stagger) * ${index})` };\n return (\n <div className=\"ds-asset-card ds-asset-tile\" data-loading=\"true\" aria-hidden=\"true\">\n <Skeleton shape=\"block\" className=\"ds-asset-card__thumb ds-asset-tile__thumb\" style={delay} />\n <div className=\"ds-asset-card__text ds-asset-tile__text\">\n <Skeleton shape=\"text\" width=\"80%\" style={delay} />\n <Skeleton shape=\"text\" width=\"45%\" style={delay} />\n {description && <Skeleton shape=\"text\" width=\"65%\" style={delay} />}\n </div>\n {footer && (\n <div className=\"ds-asset-card__footer\">\n <Skeleton shape=\"block\" width=\"45%\" style={{ ...delay, blockSize: \"var(--field-height-sm)\" }} />\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
/* Rules that predate AssetCard also match the AssetTile class hooks, so
|
|
3
|
+
* hand-written tile markup keeps its styling. */
|
|
4
|
+
:is(.ds-asset-card, .ds-asset-tile) {
|
|
5
|
+
position: relative;
|
|
6
|
+
min-inline-size: 0;
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
gap: var(--space-3);
|
|
10
|
+
padding: var(--space-3);
|
|
11
|
+
background: var(--color-surface);
|
|
12
|
+
border: var(--control-border-width) solid var(--control-border-color);
|
|
13
|
+
border-radius: var(--radius-md);
|
|
14
|
+
/* The rail exists on every tile, so selecting one never shifts the grid. */
|
|
15
|
+
box-shadow: inset var(--rail-width) 0 0 0 transparent;
|
|
16
|
+
transition:
|
|
17
|
+
background-color var(--duration-fast) var(--ease-standard),
|
|
18
|
+
box-shadow var(--duration-fast) var(--ease-standard);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Loading placeholders are not interactive, so they never take the hover fill. */
|
|
22
|
+
:is(.ds-asset-card, .ds-asset-tile):hover:not([data-pending], [data-loading]) {
|
|
23
|
+
background: var(--field-hover);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
:is(.ds-asset-card, .ds-asset-tile)[data-selected] {
|
|
27
|
+
background: var(--color-primary-soft);
|
|
28
|
+
box-shadow: inset var(--rail-width) 0 0 0 var(--rail-color);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
:is(.ds-asset-card, .ds-asset-tile)[data-pending] {
|
|
32
|
+
opacity: 70%;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
:is(.ds-asset-card__thumb, .ds-asset-tile__thumb) {
|
|
36
|
+
position: relative;
|
|
37
|
+
aspect-ratio: var(--asset-thumb-aspect);
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
border-radius: var(--field-radius);
|
|
40
|
+
background: var(--field-rest);
|
|
41
|
+
display: grid;
|
|
42
|
+
place-content: center;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* A loading card's thumbnail is a block Skeleton; keep the frame's shape
|
|
46
|
+
* rather than the Skeleton's fixed block height. */
|
|
47
|
+
:is(.ds-asset-card, .ds-asset-tile) > :is(.ds-asset-card__thumb, .ds-asset-tile__thumb).ds-skeleton {
|
|
48
|
+
block-size: auto;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Placeholder lines fill the line boxes of the text they stand in for. */
|
|
52
|
+
:is(.ds-asset-card, .ds-asset-tile)[data-loading] :is(.ds-asset-card__text, .ds-asset-tile__text) > .ds-skeleton {
|
|
53
|
+
block-size: calc(var(--font-size-xs) * var(--line-height-body));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
:is(.ds-asset-card, .ds-asset-tile)[data-loading] :is(.ds-asset-card__text, .ds-asset-tile__text) > .ds-skeleton:first-child {
|
|
57
|
+
block-size: calc(var(--font-size-sm) * var(--line-height-body));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Direct children only, so images inside overlay actions keep their own styling. */
|
|
61
|
+
:is(.ds-asset-card__thumb, .ds-asset-tile__thumb) > img {
|
|
62
|
+
position: absolute;
|
|
63
|
+
inset: 0;
|
|
64
|
+
inline-size: 100%;
|
|
65
|
+
block-size: 100%;
|
|
66
|
+
object-fit: cover;
|
|
67
|
+
opacity: 0;
|
|
68
|
+
transition: opacity var(--duration-base) var(--ease-standard);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
:is(.ds-asset-card__thumb, .ds-asset-tile__thumb) > img[data-loaded] {
|
|
72
|
+
opacity: 100%;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
:is(.ds-asset-card__thumb-skeleton, .ds-asset-tile__thumb-skeleton) {
|
|
76
|
+
position: absolute;
|
|
77
|
+
inset: 0;
|
|
78
|
+
block-size: 100%;
|
|
79
|
+
border-radius: 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
:is(.ds-asset-card__placeholder, .ds-asset-tile__placeholder) {
|
|
83
|
+
display: grid;
|
|
84
|
+
place-content: center;
|
|
85
|
+
color: var(--color-text-muted);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
:is(.ds-asset-card__pending, .ds-asset-tile__pending) {
|
|
89
|
+
position: absolute;
|
|
90
|
+
inset: 0;
|
|
91
|
+
display: flex;
|
|
92
|
+
flex-direction: column;
|
|
93
|
+
align-items: center;
|
|
94
|
+
justify-content: center;
|
|
95
|
+
gap: var(--space-2);
|
|
96
|
+
background: color-mix(in srgb, var(--color-surface) 72%, transparent);
|
|
97
|
+
color: var(--color-text-muted);
|
|
98
|
+
font-size: var(--font-size-xs);
|
|
99
|
+
font-weight: var(--font-weight-bold);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
:is(.ds-asset-card__actions, .ds-asset-tile__actions) {
|
|
103
|
+
position: absolute;
|
|
104
|
+
inset-block-start: var(--space-2);
|
|
105
|
+
inset-inline: var(--space-2);
|
|
106
|
+
z-index: 1;
|
|
107
|
+
display: flex;
|
|
108
|
+
justify-content: space-between;
|
|
109
|
+
gap: var(--space-2);
|
|
110
|
+
flex-wrap: wrap;
|
|
111
|
+
pointer-events: none;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
:is(.ds-asset-card__actions, .ds-asset-tile__actions) > * {
|
|
115
|
+
pointer-events: auto;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* The whole thumbnail is the selection target; the name stays readable text
|
|
119
|
+
* underneath rather than being swallowed by the button. */
|
|
120
|
+
:is(.ds-asset-card, .ds-asset-tile) :is(.ds-asset-card__hit, .ds-asset-tile__hit).ds-button {
|
|
121
|
+
position: absolute;
|
|
122
|
+
inset: 0;
|
|
123
|
+
inline-size: 100%;
|
|
124
|
+
height: 100%;
|
|
125
|
+
padding: 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
:is(.ds-asset-card__hit, .ds-asset-tile__hit).ds-button:focus-visible {
|
|
129
|
+
outline-offset: -2px;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* Selection reads on the tile, so Button's hover and pressed fills never
|
|
133
|
+
* paint over the thumbnail. */
|
|
134
|
+
:is(.ds-asset-card, .ds-asset-tile) :is(.ds-asset-card__hit, .ds-asset-tile__hit).ds-button:is(:hover, :active):not(:disabled) {
|
|
135
|
+
background: transparent;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
:is(.ds-asset-card__text, .ds-asset-tile__text) {
|
|
139
|
+
display: flex;
|
|
140
|
+
flex-direction: column;
|
|
141
|
+
gap: var(--space-1);
|
|
142
|
+
min-inline-size: 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
:is(.ds-asset-card__name, .ds-asset-tile__name) {
|
|
146
|
+
font-size: var(--font-size-sm);
|
|
147
|
+
font-weight: var(--font-weight-bold);
|
|
148
|
+
line-height: var(--line-height-body);
|
|
149
|
+
overflow-wrap: anywhere;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
:is(.ds-asset-card__meta, .ds-asset-tile__meta) {
|
|
153
|
+
font-family: var(--font-mono);
|
|
154
|
+
font-size: var(--font-size-xs);
|
|
155
|
+
line-height: var(--line-height-body);
|
|
156
|
+
color: var(--color-text-muted);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.ds-asset-card__meta,
|
|
160
|
+
.ds-asset-card__description {
|
|
161
|
+
overflow-wrap: anywhere;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.ds-asset-card__description {
|
|
165
|
+
font-size: var(--font-size-xs);
|
|
166
|
+
line-height: var(--line-height-body);
|
|
167
|
+
color: var(--color-text-muted);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.ds-asset-card__footer {
|
|
171
|
+
margin-block-start: auto;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.ds-asset-card[data-fit="contain"] .ds-asset-card__thumb {
|
|
175
|
+
background: repeating-linear-gradient(135deg, var(--color-canvas) 0 12px, var(--color-surface) 12px 24px);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* The picture takes its own shape (`--asset-ratio`, set once it loads) at the
|
|
179
|
+
* largest size that fits inside the inset stage, scaling small renditions up.
|
|
180
|
+
* Equal logical insets with auto margins centre it in either direction. */
|
|
181
|
+
.ds-asset-card[data-fit="contain"] .ds-asset-card__thumb > img {
|
|
182
|
+
/* Sizing the content box lets the outline wrap the picture without a sliver. */
|
|
183
|
+
box-sizing: content-box;
|
|
184
|
+
inset: var(--space-3);
|
|
185
|
+
margin: auto;
|
|
186
|
+
inline-size: min(
|
|
187
|
+
100% - 2 * (var(--space-3) + var(--control-border-width)),
|
|
188
|
+
(100% / (var(--asset-thumb-aspect)) - 2 * (var(--space-3) + var(--control-border-width)))
|
|
189
|
+
* (var(--asset-ratio, var(--asset-thumb-aspect)))
|
|
190
|
+
);
|
|
191
|
+
block-size: auto;
|
|
192
|
+
aspect-ratio: var(--asset-ratio, var(--asset-thumb-aspect));
|
|
193
|
+
object-fit: contain;
|
|
194
|
+
border: var(--control-border-width) solid var(--color-text);
|
|
195
|
+
box-shadow: var(--shadow-md);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@media (prefers-reduced-motion: reduce) {
|
|
199
|
+
:is(.ds-asset-card, .ds-asset-tile),
|
|
200
|
+
:is(.ds-asset-card__thumb, .ds-asset-tile__thumb) > img {
|
|
201
|
+
transition: none;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../chunks/Icon-CWsA1W83.cjs"),t=require("../
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../chunks/Icon-CWsA1W83.cjs"),t=require("../Grid/index.cjs"),n=require("../AssetCard/index.cjs");let r=require("react"),i=require("react/jsx-runtime");function a(...e){return e.filter(Boolean).join(` `)}var o=(0,r.forwardRef)(function({isLoading:e=!1,loadingCount:r=8,loadingCard:o,className:s,children:c,...l},u){return(0,i.jsx)(t.Grid,{ref:u,columns:`auto`,gap:`md`,minColumnWidth:`var(--asset-tile-min-width)`,className:a(`ds-asset-grid`,s),"aria-busy":e||void 0,...l,children:e?Array.from({length:r},(e,t)=>(0,i.jsx)(n.AssetCardSkeleton,{...o,index:t},t)):c})});function s({title:t,description:n,action:r}){return(0,i.jsxs)(`div`,{className:`ds-asset-empty`,children:[(0,i.jsx)(e.t,{name:`image`,size:24}),(0,i.jsx)(`span`,{className:`ds-asset-empty__title`,children:t}),n&&(0,i.jsx)(`span`,{className:`ds-asset-empty__description`,children:n}),r]})}exports.AssetGrid=o,exports.AssetGridEmpty=s,exports.AssetTile=n.AssetCard,exports.AssetTileSkeleton=n.AssetCardSkeleton;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["forwardRef","Grid","
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["forwardRef","Grid","AssetCardSkeleton","Icon","require_Icon","require_components_Grid_index","require_components_AssetCard_index","react","react_jsx_runtime","cx","parts","AssetGrid","rest","className","isLoading","loadingCount","loadingCard","i","children","AssetGridEmpty","title","description","action"],"sources":["../../../src/components/AssetGrid/index.tsx"],"sourcesContent":["import {\n forwardRef,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { Grid } from \"../Grid\";\nimport { Icon } from \"../Icon\";\nimport { AssetCardSkeleton, type AssetCardSkeletonProps } from \"../AssetCard\";\nimport \"./style.css\";\n\nfunction cx(...parts: (string | undefined | false)[]) {\n return parts.filter(Boolean).join(\" \");\n}\n\nexport interface AssetGridProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Renders placeholder tiles instead of children. The grid keeps its\n * geometry, so nothing moves when the real assets arrive.\n */\n isLoading?: boolean;\n /** How many placeholder tiles to show while loading. */\n loadingCount?: number;\n /** Slots each placeholder reserves; match the cards that replace them. */\n loadingCard?: Omit<AssetCardSkeletonProps, \"index\">;\n children?: ReactNode;\n}\n\n/**\n * The media library surface: an auto-filling grid of asset tiles. Sits inside\n * a `TableFrame` when it is the page's collection, bare inside a drawer.\n */\nexport const AssetGrid = forwardRef<HTMLDivElement, AssetGridProps>(function AssetGrid(\n { isLoading = false, loadingCount = 8, loadingCard, className, children, ...rest },\n ref,\n) {\n return (\n <Grid\n ref={ref}\n columns=\"auto\"\n gap=\"md\"\n minColumnWidth=\"var(--asset-tile-min-width)\"\n className={cx(\"ds-asset-grid\", className)}\n aria-busy={isLoading || undefined}\n {...rest}\n >\n {isLoading\n ? Array.from({ length: loadingCount }, (_, i) => <AssetCardSkeleton key={i} {...loadingCard} index={i} />)\n : children}\n </Grid>\n );\n});\n\n// Existing entry points remain aliases to the shared card implementation.\nexport { AssetCard as AssetTile, AssetCardSkeleton as AssetTileSkeleton } from \"../AssetCard\";\nexport type { AssetCardProps as AssetTileProps, AssetCardSkeletonProps as AssetTileSkeletonProps } from \"../AssetCard\";\n\nexport interface AssetGridEmptyProps {\n title: string;\n description?: ReactNode;\n action?: ReactNode;\n}\n\n/** Nothing in the library yet — one action, the one that fills it. */\nexport function AssetGridEmpty({ title, description, action }: AssetGridEmptyProps) {\n return (\n <div className=\"ds-asset-empty\">\n <Icon name=\"image\" size={24} />\n <span className=\"ds-asset-empty__title\">{title}</span>\n {description && <span className=\"ds-asset-empty__description\">{description}</span>}\n {action}\n </div>\n );\n}\n"],"mappings":"A,8OAUA,SAASS,EAAG,GAAGC,EAAuC,CACpD,OAAOA,EAAM,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG,CACvC,CAmBA,IAAaC,GAAA,EAAYX,EAAAA,WAAAA,CAA2C,SAClE,CAAE,YAAY,GAAO,eAAe,EAAG,cAAa,YAAW,WAAU,GAAGY,GAC5E,EACA,CACA,OACE,EAAAJ,EAAA,IAAA,CAACP,EAAAA,KAAD,CACO,MACL,QAAQ,OACR,IAAI,KACJ,eAAe,8BACf,UAAWQ,EAAG,gBAAiBI,CAAS,EACxC,YAAWC,GAAa,IAAA,GACxB,GAAIF,EAEH,SAAAE,EACG,MAAM,KAAK,CAAE,OAAQC,CAAa,GAAI,EAAG,KAAM,EAAAP,EAAA,IAAA,CAACN,EAAAA,kBAAD,CAA2B,GAAIc,EAAa,MAAOC,CAAI,EAA/BA,CAA+B,CAAC,EACvGC,CACA,CAAA,CAEV,CAAC,EAaD,SAAgBC,EAAe,CAAE,QAAO,cAAa,UAA+B,CAClF,OACE,EAAAX,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,iBAAf,SAAA,EACE,EAAAA,EAAA,IAAA,CAACL,EAAAA,EAAD,CAAM,KAAK,QAAQ,KAAM,EAAK,CAAA,GAC9B,EAAAK,EAAA,IAAA,CAAC,OAAD,CAAM,UAAU,wBAAyB,SAAAY,CAAY,CAAA,EACpDC,IAAe,EAAAb,EAAA,IAAA,CAAC,OAAD,CAAM,UAAU,8BAA+B,SAAAa,CAAkB,CAAA,EAChFC,CACE,C,EAET,C"}
|