Skip to content
Open
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
55 changes: 54 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,67 @@
* 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';

/**
* @slot - The content to render when no `variant` is provided (typically an <img> element)
Copy link
Collaborator

Choose a reason for hiding this comment

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

There is another JSDoc comment in swc/components/asset/Asset.ts about the @slot that is almost identical to this. Is there a reason to have both? It looks like the content in the swc directory's Asset.ts file is the one being used in the control table when storybook renders.

I'm not opposed to keeping both of them, but I was just curious if we need both, or if some other components should have both.

Copy link
Contributor

Choose a reason for hiding this comment

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

The base class should focus on implementation details and internal APIs, while the concrete classes should provide user-facing documentation and examples.
I agree here that this can be removed since implementation in swc/Asset.ts is the appropriate place for customer-facing documentation that gets rendered in Storybook controls.

*/
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 style="block-size: 128px">
* <img class="spectrum-Asset-image" src="example.png" alt="Example image" />
* </swc-asset>
*
* @example
* <swc-asset variant="file" style="min-inline-size: 150px; block-size: 128px"></swc-asset>
*
* @example
* <swc-asset variant="folder" style="min-inline-size: 150px; block-size: 128px"></swc-asset>
Comment on lines +61 to +69
Copy link
Collaborator

Choose a reason for hiding this comment

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

For these examples, do we want to encourage setting inline styles with the style attribute? Or is that more often what consumers would do? Maybe we could show just one example with inline styles.

I don't think this is a blocker, but I just noticed each one had inline styles.

*/
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: 19 additions & 61 deletions 2nd-gen/packages/swc/components/asset/asset.css
Original file line number Diff line number Diff line change
@@ -1,91 +1,49 @@
/**
/*!
* 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
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

: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)
);

.spectrum-Asset {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The CSS here looks great- matches the S2 code we have in the spectrum-two branch. 👍 🥳

My only question is do we need a selector for :host, giving it the display: flex property? I believe the default display for :host is inline.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
.spectrum-Asset {
:host {
display: flex;
}
.spectrum-Asset {
display: flex; // we would also need this here for the `align-items` & `justify-content`
align-items: center;
justify-content: center;
inline-size: 100%;
block-size: 100%;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch @marissahuysentruyt ! Both display values serve different purposes and are both necessary.

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);
}
79 changes: 64 additions & 15 deletions 2nd-gen/packages/swc/components/asset/stories/asset.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,83 @@
*/

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'],
};

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> `,
render: (args) => template(args),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just for consistency, I believe the preference Gray was trying to establish is to include this render in the Meta object. So this line would move up under argTypes (line 42).

Then, we can clean up each story by just redefining args:

Suggested change
render: (args) => template(args),
// @ line 42
render: (args) => template(args),
... // the rest of the metadata and the default export...
export const Default: Story = {
args: {
variant: undefined,
},
};
export const File: Story = {
args: {
variant: 'file',
},
tags: ['!dev'],
};
export const Folder: Story = {
args: {
variant: 'folder',
},
tags: ['!dev'],
};

That does however, remove the inline styles you have on the stories, but are those inline styles necessary? Does this so acceptable to you?

Copy link
Contributor

Choose a reason for hiding this comment

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

The inline styles aren't strictly necessary if the component's default sizing is providing a reasonable display in Storybook. Since file/folder icons should size themselves based on their container, I feel removing them should be fine. The stories will just show the icons at their natural/intrinsic size.

};

// render: () => html`
// <swc-asset style="height: 128px">
// <img src=${portrait} alt="Demo Graphic" />
// </swc-asset>
// `,
export const File: Story = {
args: {
variant: 'file',
},
render: (args) =>
html`<swc-asset
style="min-inline-size: 150px; block-size: 128px"
variant=${args.variant as 'file'}
></swc-asset>`,
tags: ['!dev'],
};

export const Folder: Story = {
args: {
variant: 'folder',
},
render: (args) =>
html`<swc-asset
style="min-inline-size: 150px; block-size: 128px"
variant=${args.variant as 'folder'}
></swc-asset>`,
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