Skip to content
52 changes: 51 additions & 1 deletion 2nd-gen/packages/core/components/asset/Asset.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,64 @@
* governing permissions and limitations under the License.
*/

import { PropertyValues } from 'lit';
import { property } from 'lit/decorators.js';

import { SpectrumElement } from '@spectrum-web-components/core/shared/base/index.js';

import { ASSET_VARIANTS, type AssetVariant } from './Asset.types.js';

export abstract class AssetBase extends SpectrumElement {
// ─────────────────────────
// API TO OVERRIDE
// ─────────────────────────

/**
* @internal
*
* A readonly array of all valid variants for the asset.
*
* This is an actual internal property, intended not for customer use
*/
static readonly VARIANTS: readonly AssetVariant[] = ASSET_VARIANTS;

// ─────────────────
// SHARED API
// ─────────────────

/**
* The variant of the asset. When not provided, slot content is rendered (e.g., an image).
*/
@property({ type: String, reflect: true })
public variant: 'file' | 'folder' | undefined;
public variant: AssetVariant | undefined;

/**
* Accessible label for the asset’s file or folder variant.
*/
@property()
public label = '';

// ──────────────────────
// IMPLEMENTATION
// ──────────────────────

protected override updated(changes: PropertyValues): void {
super.updated(changes);
if (window.__swc?.DEBUG) {
const constructor = this.constructor as typeof AssetBase;
if (
typeof this.variant !== 'undefined' &&
!constructor.VARIANTS.includes(this.variant)
) {
window.__swc.warn(
this,
`<${this.localName}> element expects the "variant" attribute to be one of the following:`,
'https://opensource.adobe.com/spectrum-web-components/components/asset/',
{
issues: [...constructor.VARIANTS],
}
);
}
}
}
}
15 changes: 15 additions & 0 deletions 2nd-gen/packages/core/components/asset/Asset.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Copyright 2025 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

export const ASSET_VARIANTS = ['file', 'folder'] as const;

export type AssetVariant = (typeof ASSET_VARIANTS)[number];
1 change: 1 addition & 0 deletions 2nd-gen/packages/core/components/asset/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
* governing permissions and limitations under the License.
*/
export * from './Asset.base.js';
export * from './Asset.types.js';
49 changes: 36 additions & 13 deletions 2nd-gen/packages/swc/components/asset/Asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,62 +11,85 @@
*/

import { CSSResultArray, html, TemplateResult } from 'lit';
import { classMap } from 'lit/directives/class-map.js';

import { AssetBase } from '@spectrum-web-components/core/components/asset';

import styles from './asset.css';

const file = (label: string): TemplateResult => html`
<svg
class="file"
class="spectrum-Asset-file"
role="img"
viewBox="0 0 128 128"
aria-label=${label || 'File'}
>
<path
class="fileBackground"
class="spectrum-Asset-fileBackground"
d="M24,126c-5.5,0-10-4.5-10-10V12c0-5.5,4.5-10,10-10h61.5c2.1,0,4.1,0.8,5.6,2.3l20.5,20.4c1.5,1.5,2.4,3.5,2.4,5.7V116c0,5.5-4.5,10-10,10H24z"
></path>
<path
class="fileOutline"
class="spectrum-Asset-fileOutline"
d="M113.1,23.3L92.6,2.9C90.7,1,88.2,0,85.5,0H24c-6.6,0-12,5.4-12,12v104c0,6.6,5.4,12,12,12h80c6.6,0,12-5.4,12-12V30.4C116,27.8,114.9,25.2,113.1,23.3z M90,6l20.1,20H92c-1.1,0-2-0.9-2-2V6z M112,116c0,4.4-3.6,8-8,8H24c-4.4,0-8-3.6-8-8V12c0-4.4,3.6-8,8-8h61.5c0.2,0,0.3,0,0.5,0v20c0,3.3,2.7,6,6,6h20c0,0.1,0,0.3,0,0.4V116z"
></path>
</svg>
`;

const folder = (label: string): TemplateResult => html`
<svg
class="folder"
class="spectrum-Asset-folder"
role="img"
viewBox="0 0 32 32"
aria-label=${label || 'Folder'}
>
<path
class="folderBackground"
class="spectrum-Asset-folderBackground"
d="M3,29.5c-1.4,0-2.5-1.1-2.5-2.5V5c0-1.4,1.1-2.5,2.5-2.5h10.1c0.5,0,1,0.2,1.4,0.6l3.1,3.1c0.2,0.2,0.4,0.3,0.7,0.3H29c1.4,0,2.5,1.1,2.5,2.5v18c0,1.4-1.1,2.5-2.5,2.5H3z"
></path>
<path
class="folderOutline"
class="spectrum-Asset-folderOutline"
d="M29,6H18.3c-0.1,0-0.2,0-0.4-0.2l-3.1-3.1C14.4,2.3,13.8,2,13.1,2H3C1.3,2,0,3.3,0,5v22c0,1.6,1.3,3,3,3h26c1.7,0,3-1.4,3-3V9C32,7.3,30.7,6,29,6z M31,27c0,1.1-0.9,2-2,2H3c-1.1,0-2-0.9-2-2V7h28c1.1,0,2,0.9,2,2V27z"
></path>
</svg>
`;

/**
* @element swc-asset
* @slot - content to be displayed in the asset when an acceptable value for `file` is not present
* @slot - content to be displayed when no `variant` is set (typically an `<img>` element)
*
* @example
* <swc-asset>
* <img class="spectrum-Asset-image" src="example.png" alt="Example image" />
* </swc-asset>
*
* @example
* <swc-asset variant="file"></swc-asset>
*
* @example
* <swc-asset variant="folder"></swc-asset>
*/
export class Asset extends AssetBase {
// ──────────────────────────────
// RENDERING & STYLING
// ──────────────────────────────

public static override get styles(): CSSResultArray {
return [styles];
}

protected override render(): TemplateResult {
if (this.variant === 'file') {
return file(this.label);
} else if (this.variant === 'folder') {
return folder(this.label);
}
return html` <slot></slot> `;
return html`
<div
class=${classMap({
['spectrum-Asset']: true,
})}
>
${this.variant === 'file'
? file(this.label)
: this.variant === 'folder'
? folder(this.label)
: html` <slot></slot> `}
</div>
`;
}
}
80 changes: 21 additions & 59 deletions 2nd-gen/packages/swc/components/asset/asset.css
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/**
/*!
* Copyright 2025 Adobe. All rights reserved.
*
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
* of the License at <http://www.apache.org/licenses/LICENSE-2.0>
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
Expand All @@ -11,81 +12,42 @@
*/

:host {
--spectrum-asset-folder-background: var(
--highcontrast-asset-icon-background-color,
var(
--mod-asset-folder-background-color,
var(--spectrum-asset-folder-background-color)
)
);
--spectrum-asset-file-background: var(
--highcontrast-asset-icon-background-color,
var(
--mod-asset-file-background-color,
var(--spectrum-asset-file-background-color)
)
);
--spectrum-asset-folder-outline: var(
--mod-asset-icon-outline-color,
var(--spectrum-asset-icon-outline-color)
);
--spectrum-asset-file-outline: var(
--mod-asset-icon-outline-color,
var(--spectrum-asset-icon-outline-color)
);
display: flex;
}

.spectrum-Asset {
display: flex;
align-items: center;
justify-content: center;
inline-size: 100%;
block-size: 100%;
justify-content: center;
align-items: center;
display: flex;

--spectrum-asset-folder-background-color: var(
--system-asset-folder-background-color
);
--spectrum-asset-file-background-color: var(
--system-asset-file-background-color
);
--spectrum-asset-icon-outline-color: var(--system-asset-icon-outline-color);
}

::slotted(*) {
.spectrum-Asset-image {
Copy link
Contributor

@Rajdeepc Rajdeepc Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change for the existing behaviour if consumers are slotting an <img> without class.
You can keep this class with a migration shim.

::slotted() { /* same rules */ }
.spectrum-Asset-image { /* same rules */ }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but can we not break this for spectrum-two?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should try not to treat S2 as a chance to introduce breaking changes unless they’re really necessary. These can have a big impact on adoption for product teams, so it’s worth being mindful about when and why we make them. If a change truly adds more value than the potential disruption, we can definitely go for it

max-inline-size: 100%;
max-block-size: 100%;
object-fit: contain;
transition: opacity var(--spectrum-animation-duration-100);
}

.file,
.folder {
inline-size: max(48px, min(100%, 80px));
inline-size: max(
var(--mod-asset-icon-min-width, 48px),
min(100%, var(--mod-asset-icon-max-width, 80px))
);
.spectrum-Asset-folder,
.spectrum-Asset-file {
inline-size: clamp(48px, 100%, 80px);
block-size: 100%;
margin: 20px;
margin: var(--mod-asset-icon-margin, 20px);
}

.folderBackground {
fill: var(--spectrum-asset-folder-background);
}

.fileBackground {
fill: var(--spectrum-asset-file-background);
.spectrum-Asset-folderBackground {
fill: var(--spectrum-gray-200);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these map to the same value? Can you surface up these changes in the PR documentation too for quick reference by reviewers. Its hard to visualize from here

Copy link
Collaborator

@marissahuysentruyt marissahuysentruyt Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be the same values, yes (and they look to be mapped correctly).

Before Cassondra left, she started removing some of the complexity from a chunk of components, asset being one of them. You can see the changes (and remove of the --spectrum-asset- variables) here: https://github.com/adobe/spectrum-css/pull/4257/files#diff-3a87a9f21648ac03bf4532993e3cdf48604b9862efd1c0ea68c3f6c2d44b32b3

In a different PR (that does the same and removes mods and some of the --spectrum prefixed variables for asset list and miller), Cassondra left her reasoning as to why: adobe/spectrum-css#4260 (comment)

Hopefully that's useful! I absolutely agree with @Rajdeepc that we should surface the changes in a changeset. None of these migrations have had a changeset yet. (I asked a similar question in Slack when I was working on status light). Do we have the changeset management situation figured out? I've been out, so it might be and we need a changeset!

}

.folderOutline {
fill: var(--spectrum-asset-folder-outline);
.spectrum-Asset-fileBackground {
fill: var(--spectrum-gray-25);
}

.fileOutline {
fill: var(--spectrum-asset-file-outline);
.spectrum-Asset-folderOutline {
fill: var(--spectrum-gray-500);
}

@media (forced-colors: active) {
:host {
--highcontrast-asset-icon-background-color: currentcolor;
}
.spectrum-Asset-fileOutline {
fill: var(--spectrum-gray-500);
}
73 changes: 57 additions & 16 deletions 2nd-gen/packages/swc/components/asset/stories/asset.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,76 @@
* governing permissions and limitations under the License.
*/

import { html } from 'lit';
import type { Meta, StoryObj } from '@storybook/web-components';
import type { Meta, StoryObj as Story } from '@storybook/web-components';
import { getStorybookHelpers } from '@wc-toolkit/storybook-helpers';

import { Asset } from '@adobe/swc/asset';

import '@adobe/swc/asset';

// ────────────────
// METADATA
// ────────────────

const { events, args, argTypes, template } = getStorybookHelpers('swc-asset');

argTypes.variant = {
...argTypes.variant,
control: { type: 'select' },
options: [undefined, ...Asset.VARIANTS],
};

/**
* Use an asset element to visually represent a file, folder, or image.
* File and folder representations center themselves within the available space.
* Images are contained to the element’s size and centered.
*/
const meta: Meta = {
title: 'Asset',
component: 'swc-asset',
argTypes: {
variant: {
control: { type: 'select' },
options: ['file', 'folder', undefined],
args,
argTypes,
parameters: {
actions: {
handles: events,
},
},
tags: ['migrated'],
render: (args) => template(args),
};

export default meta;
type Story = StoryObj;

// export const Default: Story = {
// render: (args) => html` <swc-asset variant="${args.variant}"></swc-asset> `,
// };
// ───────────────
// STORIES
// ───────────────

args['default-slot'] = IMAGE_PLACEHOLDER_STRING();

export const Default: Story = {
render: (args) => html` <swc-asset variant="${args.variant}"></swc-asset> `,
args: {
variant: undefined,
},
};

export const File: Story = {
args: {
variant: 'file',
},
tags: ['!dev'],
};

// render: () => html`
// <swc-asset style="height: 128px">
// <img src=${portrait} alt="Demo Graphic" />
// </swc-asset>
// `,
export const Folder: Story = {
args: {
variant: 'folder',
},
tags: ['!dev'],
};

// ────────────────────────
// HELPER FUNCTIONS
// ────────────────────────

function IMAGE_PLACEHOLDER_STRING(): string {
return `<img class="spectrum-Asset-image" alt="Example image" src="https://cdn2.thecatapi.com/images/d4i.jpg" height="128">`;
}
Loading