@use 'sprucecss/scss/spruce' as *;
.file-manager {
background-color: color('surface');
border: config('border-width-base', $display) solid color('border');
border-radius: radius('lg');
padding: spacer('m');
@include layout-stack('m');
&__header {
align-items: center;
display: flex;
flex-wrap: wrap;
gap: spacer('s');
justify-content: space-between;
}
&__path ol {
display: flex;
flex-wrap: wrap;
font-weight: 600;
gap: spacer('xxs');
list-style: none;
margin: 0;
padding: 0;
li {
margin: 0;
}
li + li::before {
color: color('muted');
content: '/';
margin-inline-end: spacer('xxs');
}
[aria-current] {
color: color('heading');
}
}
&__tools,
&__view {
align-items: center;
display: flex;
gap: spacer('xs');
}
&__view {
gap: 0;
&[hidden] {
display: none;
}
[aria-pressed='true'] {
background-color: color-alpha('primary', 0.12);
}
}
&__items {
display: grid;
gap: spacer('s');
grid-template-columns: repeat(auto-fill, minmax(min(9.5rem, 100%), 1fr));
list-style: none;
padding: 0;
> li {
margin-block: 0;
}
}
&__item {
@include a11y-card-link('.file-manager__name', true);
align-items: center;
border: config('border-width-base', $display) solid color('border');
border-radius: radius('md');
display: flex;
flex-direction: column;
gap: spacer('xxs');
padding: spacer('s');
text-align: center;
&:hover {
background-color: color('surface-muted');
}
&:focus-within {
outline: 2px solid color('focus-ring');
outline-offset: 2px;
}
}
&__icon {
--icon-color: #{color('muted')};
color: var(--icon-color);
display: grid;
margin-block-end: spacer('xxs');
place-items: center;
> * {
grid-area: 1 / 1;
}
svg {
block-size: 3rem;
inline-size: 3rem;
}
}
&__item--folder &__icon { --icon-color: #{color('warning', 'alert')}; }
&__item--image &__icon { --icon-color: #{color('info', 'alert')}; }
&__item--pdf &__icon { --icon-color: #{color('danger', 'alert')}; }
&__item--doc &__icon { --icon-color: #{color('primary')}; }
&__item--sheet &__icon { --icon-color: #{color('success', 'alert')}; }
&__item--video &__icon { --icon-color: #{color('secondary')}; }
&__ext {
font-size: 0.55rem;
font-weight: 800;
letter-spacing: 0.04em;
padding-block-start: 0.6rem;
}
&__name {
color: color('heading');
font-size: config('font-size-sm', $typography);
font-weight: 600;
inline-size: 100%;
overflow: hidden;
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;
}
&__meta,
&__modified {
color: color('muted');
font-size: config('font-size-sm', $typography);
}
&__modified {
display: none;
}
&__items--list {
gap: 0;
grid-template-columns: minmax(0, 1fr);
.file-manager__item {
border-radius: 0;
border-width: 0 0 config('border-width-base', $display);
display: grid;
gap: spacer('s');
grid-template-columns: 2rem minmax(0, 1fr) 6rem 6rem;
padding: spacer('xs') spacer('xxs');
text-align: start;
}
.file-manager__icon {
margin: 0;
svg {
block-size: 1.75rem;
inline-size: 1.75rem;
}
}
.file-manager__ext {
display: none;
}
.file-manager__modified {
display: block;
}
}
&__storage {
align-items: center;
display: flex;
flex-wrap: wrap;
font-size: config('font-size-sm', $typography);
gap: spacer('xxs') spacer('s');
label {
color: color('heading');
font-weight: 600;
}
meter {
block-size: 0.5rem;
flex: 1 1 8rem;
}
span {
color: color('muted');
}
}
}
<section class="file-manager" aria-labelledby="file-manager-title" data-file-manager>
<header class="file-manager__header">
<h2 class="sr-only" id="file-manager-title">Files</h2>
<nav class="file-manager__path" aria-label="Folder path">
<ol role="list">
<li><a href="#">Workspace</a></li>
<li><span aria-current="location">Marketing</span></li>
</ol>
</nav>
<div class="file-manager__tools">
<div class="file-manager__view" role="group" aria-label="View" hidden data-file-manager-view>
<button class="btn btn--sm btn--icon btn--ghost-primary" type="button" aria-pressed="true" data-view="grid"><svg class="btn__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg><span class="sr-only">Grid</span></button>
<button class="btn btn--sm btn--icon btn--ghost-primary" type="button" aria-pressed="false" data-view="list"><svg class="btn__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M8 6h13"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M3 6h.01"/><path d="M3 12h.01"/><path d="M3 18h.01"/></svg><span class="sr-only">List</span></button>
</div>
<a class="btn btn--primary btn--sm" href="#">Upload</a>
</div>
</header>
<ul class="file-manager__items" role="list" data-file-manager-items>
<li class="file-manager__item file-manager__item--folder">
<span class="file-manager__icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" focusable="false"><path d="M3 6.5A2.5 2.5 0 0 1 5.5 4h3.6a2 2 0 0 1 1.5.7L12 6.2h6.5A2.5 2.5 0 0 1 21 8.7v8.8a2.5 2.5 0 0 1-2.5 2.5h-13A2.5 2.5 0 0 1 3 17.5z"/></svg></span>
<a class="file-manager__name" href="#">Brand assets</a>
<span class="file-manager__meta">48 files</span>
<span class="file-manager__modified"><span class="sr-only">Modified: </span>Oct 2</span>
</li>
<li class="file-manager__item file-manager__item--folder">
<span class="file-manager__icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" focusable="false"><path d="M3 6.5A2.5 2.5 0 0 1 5.5 4h3.6a2 2 0 0 1 1.5.7L12 6.2h6.5A2.5 2.5 0 0 1 21 8.7v8.8a2.5 2.5 0 0 1-2.5 2.5h-13A2.5 2.5 0 0 1 3 17.5z"/></svg></span>
<a class="file-manager__name" href="#">Campaigns</a>
<span class="file-manager__meta">12 files</span>
<span class="file-manager__modified"><span class="sr-only">Modified: </span>Sep 30</span>
</li>
<li class="file-manager__item file-manager__item--image">
<span class="file-manager__icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linejoin="round" focusable="false"><path d="M14 2.5H7A2.5 2.5 0 0 0 4.5 5v14A2.5 2.5 0 0 0 7 21.5h10a2.5 2.5 0 0 0 2.5-2.5V8z"/><path d="M14 2.5V8h5.5"/></svg><span class="file-manager__ext">JPG</span></span>
<a class="file-manager__name" href="#">hero-autumn.jpg</a>
<span class="file-manager__meta">2.4 MB</span>
<span class="file-manager__modified"><span class="sr-only">Modified: </span>Today</span>
</li>
<li class="file-manager__item file-manager__item--pdf">
<span class="file-manager__icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linejoin="round" focusable="false"><path d="M14 2.5H7A2.5 2.5 0 0 0 4.5 5v14A2.5 2.5 0 0 0 7 21.5h10a2.5 2.5 0 0 0 2.5-2.5V8z"/><path d="M14 2.5V8h5.5"/></svg><span class="file-manager__ext">PDF</span></span>
<a class="file-manager__name" href="#">Q4 plan.pdf</a>
<span class="file-manager__meta">860 KB</span>
<span class="file-manager__modified"><span class="sr-only">Modified: </span>Yesterday</span>
</li>
<li class="file-manager__item file-manager__item--doc">
<span class="file-manager__icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linejoin="round" focusable="false"><path d="M14 2.5H7A2.5 2.5 0 0 0 4.5 5v14A2.5 2.5 0 0 0 7 21.5h10a2.5 2.5 0 0 0 2.5-2.5V8z"/><path d="M14 2.5V8h5.5"/></svg><span class="file-manager__ext">DOCX</span></span>
<a class="file-manager__name" href="#">Launch copy.docx</a>
<span class="file-manager__meta">48 KB</span>
<span class="file-manager__modified"><span class="sr-only">Modified: </span>Oct 1</span>
</li>
<li class="file-manager__item file-manager__item--sheet">
<span class="file-manager__icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linejoin="round" focusable="false"><path d="M14 2.5H7A2.5 2.5 0 0 0 4.5 5v14A2.5 2.5 0 0 0 7 21.5h10a2.5 2.5 0 0 0 2.5-2.5V8z"/><path d="M14 2.5V8h5.5"/></svg><span class="file-manager__ext">XLSX</span></span>
<a class="file-manager__name" href="#">Budget 2027.xlsx</a>
<span class="file-manager__meta">112 KB</span>
<span class="file-manager__modified"><span class="sr-only">Modified: </span>Sep 29</span>
</li>
<li class="file-manager__item file-manager__item--video">
<span class="file-manager__icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linejoin="round" focusable="false"><path d="M14 2.5H7A2.5 2.5 0 0 0 4.5 5v14A2.5 2.5 0 0 0 7 21.5h10a2.5 2.5 0 0 0 2.5-2.5V8z"/><path d="M14 2.5V8h5.5"/></svg><span class="file-manager__ext">MP4</span></span>
<a class="file-manager__name" href="#">Product tour.mp4</a>
<span class="file-manager__meta">84 MB</span>
<span class="file-manager__modified"><span class="sr-only">Modified: </span>Sep 21</span>
</li>
<li class="file-manager__item file-manager__item--zip">
<span class="file-manager__icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linejoin="round" focusable="false"><path d="M14 2.5H7A2.5 2.5 0 0 0 4.5 5v14A2.5 2.5 0 0 0 7 21.5h10a2.5 2.5 0 0 0 2.5-2.5V8z"/><path d="M14 2.5V8h5.5"/></svg><span class="file-manager__ext">ZIP</span></span>
<a class="file-manager__name" href="#">Press kit.zip</a>
<span class="file-manager__meta">16 MB</span>
<span class="file-manager__modified"><span class="sr-only">Modified: </span>Sep 12</span>
</li>
</ul>
<p class="file-manager__storage">
<label for="file-manager-storage">Storage</label>
<meter id="file-manager-storage" min="0" max="100" low="75" high="90" optimum="0" value="64">6.4 of 10 GB</meter>
<span>6.4 GB of 10 GB used</span>
</p>
</section>
(() => {
const storageKey = 'file-manager-view';
document.querySelectorAll('[data-file-manager]').forEach((manager) => {
const view = manager.querySelector('[data-file-manager-view]');
const items = manager.querySelector('[data-file-manager-items]');
const setView = (value) => {
items.classList.toggle('file-manager__items--list', value === 'list');
view.querySelectorAll('[data-view]').forEach((button) => {
button.setAttribute('aria-pressed', String(button.dataset.view === value));
});
try {
localStorage.setItem(storageKey, value);
} catch (error) {}
};
view.hidden = false;
view.addEventListener('click', (event) => {
const button = event.target.closest('[data-view]');
if (button) setView(button.dataset.view);
});
try {
setView(localStorage.getItem(storageKey) || 'grid');
} catch (error) {
setView('grid');
}
});
})();