Compare commits

...

9 Commits

Author SHA1 Message Date
unkn0wn 027cec577d feat: updating package to version 1.00.0
Publish to npm / check-and-publish (push) Successful in 22s
2026-06-23 12:36:10 +02:00
unkn0wn 6e077aaeed style: update Navbar and Select component styles for improved consistency and usability 2026-06-23 12:35:23 +02:00
unkn0wn 1d9001705f feat: adding Sidebar and SidebarItem 2026-06-23 12:35:12 +02:00
unkn0wn 1878ec3a0e feat: add Card and Navbar components with styles
- Implemented Card component with cover, title, subtitle, content, and footer slots.
- Added styles for Card component in _card.scss.
- Created Navbar component with brand and links, including styles in _navbar.scss.
- Updated index.astro to include new Card and Navbar components.
2026-06-23 11:40:04 +02:00
unkn0wn b48581b089 feat: implement Modal component with overlay and dialog structure 2026-06-23 11:25:39 +02:00
unkn0wn 3593305512 feat: add Tooltip component and update styles in Button and index page 2026-06-23 11:08:24 +02:00
unkn0wn 6967f3c7cd refactor: reorganize component imports and enhance index page layout
- Grouped component imports into categories for better organization.
- Updated the layout of the index page to include a gallery structure.
- Added new sections for Button, Badge, Selection Controls, TextField, Select, Numeric Stepper, Avatar, Loading Bar, List Item, Link, Tabs, Breadcrumb, Pagination, Notification, Tooltip, Card, Navbar, Sidebar, Modal, and Icons.
- Improved styling for better visual hierarchy and spacing.
- Implemented a theme toggle feature with persistent state using localStorage.
2026-06-23 10:51:40 +02:00
unkn0wn dea517fbe5 feat: adding Badge, Breadcrumb, Checkbox et Radio components 2026-06-23 10:12:04 +02:00
unkn0wn cc54ae46ff style: refactor Button and numericStepper components for improved accessibility and styling 2026-06-23 10:07:39 +02:00
45 changed files with 3122 additions and 785 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@unkn0wndo3s/nova-design-system",
"type": "module",
"version": "0.06.1",
"version": "1.00.0",
"main": "./src/index.ts",
"description": "Nova Design System — Astro component library",
"engines": {
+58
View File
@@ -0,0 +1,58 @@
@use "../../styles/tokens/typography" as *;
.badge {
display: inline-flex;
align-items: center;
gap: var(--nds-spacing-2xs);
padding: 3px var(--nds-spacing-xs);
border-radius: var(--nds-radius-full);
@include text-sm;
font-weight: 600;
line-height: 1.2;
width: fit-content;
white-space: nowrap;
&__primary {
--c: var(--nds-primary);
--on: var(--nds-on-primary);
--soft: var(--nds-primary-soft);
}
&__neutral {
--c: var(--nds-neutral);
--on: var(--nds-surface);
--soft: color-mix(in srgb, var(--nds-neutral) 16%, transparent);
}
&__success {
--c: var(--nds-success-medium);
--on: #fff;
--soft: var(--nds-success-low);
}
&__warning {
--c: var(--nds-warning-medium);
--on: #fff;
--soft: var(--nds-warning-low);
}
&__error {
--c: var(--nds-error-medium);
--on: #fff;
--soft: var(--nds-error-low);
}
&__info {
--c: var(--nds-info-medium);
--on: #fff;
--soft: var(--nds-info-low);
}
&--soft {
background-color: var(--soft);
color: var(--c);
}
&--solid {
background-color: var(--c);
color: var(--on);
}
&__dot {
width: 6px;
height: 6px;
border-radius: var(--nds-radius-full);
background-color: currentColor;
}
}
+15
View File
@@ -0,0 +1,15 @@
---
export interface Props {
type?: "primary" | "neutral" | "success" | "warning" | "error" | "info";
variant?: "soft" | "solid";
}
const { type = "primary", variant = "soft" } = Astro.props;
---
<span class={`badge badge__${type} badge--${variant}`}>
<slot />
</span>
<style lang="scss">
@use "./_badge.scss";
</style>
@@ -0,0 +1,27 @@
@use "../../styles/tokens/typography" as *;
.breadcrumb {
display: flex;
align-items: center;
gap: var(--nds-spacing-xs);
@include text-base;
&__item {
text-decoration: none;
color: var(--nds-neutral);
transition: color 120ms ease;
&:hover {
color: var(--nds-text);
}
&:not(:first-child)::before {
content: "/";
color: var(--nds-disabled);
margin-right: var(--nds-spacing-xs);
}
&--current {
color: var(--nds-text);
font-weight: 600;
pointer-events: none;
}
}
}
@@ -0,0 +1,11 @@
---
export interface Props {}
---
<nav class="breadcrumb" aria-label="Breadcrumb">
<slot />
</nav>
<style lang="scss">
@use "./_breadcrumb.scss";
</style>
@@ -0,0 +1,19 @@
---
export interface Props {
href?: string;
current?: boolean;
}
const { href = "#", current = false } = Astro.props;
---
<a
href={href}
class={`breadcrumb__item ${current ? "breadcrumb__item--current" : ""}`}
aria-current={current ? "page" : undefined}
>
<slot />
</a>
<style lang="scss">
@use "./_breadcrumb.scss";
</style>
+77 -20
View File
@@ -1,40 +1,97 @@
@use '../../styles/tokens/typography' as *;
@use "../../styles/tokens/typography" as *;
.button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--nds-spacing-xs);
width: fit-content;
padding: var(--nds-spacing-sm) var(--nds-spacing-md);
border-radius: var(--nds-radius-sm);
@include text-base;
border: var(--nds-border-width-thin) solid transparent;
border-radius: var(--nds-radius-md);
@include text-label;
text-decoration: none;
white-space: nowrap;
cursor: pointer;
&__primary {
background-color: var(--nds-primary);
color: var(--nds-text);
&:hover:not(.button--disabled) {
background-color: var(--nds-secondary);
user-select: none;
transition:
background-color 140ms ease,
border-color 140ms ease,
color 140ms ease,
box-shadow 140ms ease,
transform 80ms ease;
&:focus-visible {
outline: none;
box-shadow: 0 0 0 3px var(--nds-ring);
}
&:active:not(.button--disabled) {
background-color: var(--nds-accent);
color: var(--nds-background);
transform: translateY(0.5px);
}
&--sm {
height: 30px;
padding: 0 var(--nds-spacing-sm);
}
&--md {
height: 38px;
padding: 0 var(--nds-spacing-md);
}
&--lg {
height: 46px;
padding: 0 var(--nds-spacing-lg);
font-size: 0.9375rem;
}
&--icon {
padding: 0;
aspect-ratio: 1;
}
&--icon.button--sm {
width: 30px;
}
&--icon.button--md {
width: 38px;
}
&--icon.button--lg {
width: 46px;
}
&__primary {
background-color: var(--nds-primary);
color: var(--nds-on-primary);
border-color: var(--nds-primary);
&:hover:not(.button--disabled) {
background-color: color-mix(in srgb, var(--nds-primary) 88%, #000);
border-color: color-mix(in srgb, var(--nds-primary) 88%, #000);
}
}
&__secondary {
border: var(--nds-border-width-medium) solid;
border-color: var(--nds-primary);
background-color: transparent;
color: var(--nds-primary);
border-color: var(--nds-border-strong);
&:hover:not(.button--disabled) {
border-color: var(--nds-secondary);
border-color: var(--nds-primary);
background-color: var(--nds-primary-soft);
}
&:active:not(.button--disabled) {
border-color: var(--nds-accent);
color: var(--nds-accent);
}
&__ghost {
background-color: transparent;
color: var(--nds-text);
border-color: transparent;
&:hover:not(.button--disabled) {
background-color: var(--nds-surface-hover);
}
}
&__danger {
background-color: var(--nds-error-medium);
color: #fff;
border-color: var(--nds-error-medium);
&:hover:not(.button--disabled) {
background-color: color-mix(in srgb, var(--nds-error-medium) 88%, #000);
border-color: color-mix(in srgb, var(--nds-error-medium) 88%, #000);
}
}
&--disabled {
cursor: not-allowed;
background-color: var(--nds-disabled);
color: var(--nds-neutral);
}
&--icon {
padding: var(--nds-spacing-2xs) var(--nds-spacing-xs);
border-color: transparent;
box-shadow: none;
}
}
+37 -6
View File
@@ -1,16 +1,47 @@
---
export interface Props {
type: 'primary' | 'secondary';
type?: "primary" | "secondary" | "ghost" | "danger";
size?: "sm" | "md" | "lg";
disabled?: boolean;
icon?: boolean;
href?: string;
htmlType?: "button" | "submit" | "reset";
}
const { type, disabled = false, icon = false } = Astro.props;
const {
type = "primary",
size = "md",
disabled = false,
icon = false,
href,
htmlType = "button",
...rest
} = Astro.props;
const classes = [
"button",
`button__${type}`,
`button--${size}`,
icon ? "button--icon" : "",
disabled ? "button--disabled" : "",
]
.filter(Boolean)
.join(" ");
const Tag = href ? "a" : "button";
---
<div class={`button button__${type} ${disabled ? 'button--disabled' : ''} ${icon ? 'button--icon' : ''}`}>
<slot/>
</div>
<Tag
class={classes}
href={href}
type={href ? undefined : htmlType}
aria-disabled={disabled ? "true" : undefined}
{...rest}
>
<slot name="icon-left" />
<slot />
<slot name="icon-right" />
</Tag>
<style lang="scss">
@use './_button.scss';
@use "./_button.scss";
</style>
+53
View File
@@ -0,0 +1,53 @@
@use "../../styles/tokens/typography" as *;
.card {
display: flex;
flex-direction: column;
background-color: var(--nds-surface);
border: var(--nds-border-width-thin) solid var(--nds-border);
border-radius: var(--nds-radius-lg);
overflow: hidden;
box-shadow: var(--nds-shadow-sm);
width: fit-content;
transition:
box-shadow 160ms ease,
border-color 160ms ease;
&:hover {
box-shadow: var(--nds-shadow-md);
}
&__cover {
height: 150px;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
}
&__body {
display: flex;
flex-direction: column;
gap: var(--nds-spacing-xs);
padding: var(--nds-spacing-md);
}
&__title {
@include text-xl;
color: var(--nds-text);
margin: 0;
}
&__subtitle {
@include text-base;
color: var(--nds-neutral);
margin: 0;
}
&__content {
@include text-base;
color: var(--nds-text);
&:empty {
display: none;
}
}
&__footer {
margin-top: var(--nds-spacing-2xs);
display: flex;
gap: var(--nds-spacing-xs);
}
}
+33
View File
@@ -0,0 +1,33 @@
---
export interface Props {
title?: string;
subtitle?: string;
cover?: string;
}
const { title, subtitle, cover } = Astro.props;
const hasFooter = Astro.slots.has("footer");
---
<div class="card">
{
cover && (
<div class="card__cover" style={`background-image: url(${cover})`} />
)
}
<div class="card__body">
{title && <p class="card__title">{title}</p>}
{subtitle && <p class="card__subtitle">{subtitle}</p>}
<div class="card__content"><slot /></div>
{
hasFooter && (
<div class="card__footer">
<slot name="footer" />
</div>
)
}
</div>
</div>
<style lang="scss">
@use "./_card.scss";
</style>
+51
View File
@@ -0,0 +1,51 @@
@use "../../styles/tokens/typography" as *;
.checkbox {
display: inline-flex;
align-items: center;
gap: var(--nds-spacing-xs);
cursor: pointer;
@include text-base;
color: var(--nds-text);
&__input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
&__box {
width: 18px;
height: 18px;
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
border: var(--nds-border-width-medium) solid var(--nds-border-strong);
border-radius: var(--nds-radius-sm);
background-color: var(--nds-surface);
color: var(--nds-on-primary);
transition: all 130ms ease;
svg {
width: 12px;
height: 12px;
opacity: 0;
transform: scale(0.6);
transition: all 130ms ease;
}
}
&__input:checked + &__box {
background-color: var(--nds-primary);
border-color: var(--nds-primary);
svg {
opacity: 1;
transform: scale(1);
}
}
&__input:focus-visible + &__box {
box-shadow: 0 0 0 3px var(--nds-ring);
}
&--disabled {
cursor: not-allowed;
opacity: 0.5;
}
}
+37
View File
@@ -0,0 +1,37 @@
---
export interface Props {
id: string;
name?: string;
checked?: boolean;
disabled?: boolean;
}
const { id, name, checked = false, disabled = false } = Astro.props;
---
<label class={`checkbox ${disabled ? "checkbox--disabled" : ""}`} for={id}>
<input
type="checkbox"
id={id}
name={name}
checked={checked}
disabled={disabled}
class="checkbox__input"
/>
<span class="checkbox__box" aria-hidden="true">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M5 13l4 4L19 7"></path>
</svg>
</span>
<span class="checkbox__label"><slot /></span>
</label>
<style lang="scss">
@use "./_checkbox.scss";
</style>
+18 -20
View File
@@ -1,30 +1,28 @@
@use '../../styles/tokens/typography' as *;
@use "../../styles/tokens/typography" as *;
.link__container {
width: fit-content;
display: inline-flex;
align-items: center;
}
.link {
text-decoration: none;
color: var(--nds-primary);
@include text-base;
&:hover {
color: var(--nds-text);
opacity: 0.25;
}
&:active {
color: var(--nds-text);
opacity: 0.25;
}
&__container {
width: fit-content;
display: flex;
font-weight: 600;
display: inline-flex;
align-items: center;
justify-content: center;
border-bottom: var(--nds-border-width-medium) dashed var(--nds-secondary);
gap: var(--nds-spacing-3xs);
border-bottom: 1px solid transparent;
transition:
border-color 130ms ease,
color 130ms ease;
&:hover {
background-color: var(--nds-secondary);
border-bottom: var(--nds-border-width-medium) dashed var(--nds-accent);
}
&:active {
background-color: var(--nds-primary);
border-bottom: var(--nds-border-width-medium) dashed var(--nds-secondary);
border-bottom-color: var(--nds-primary);
}
&:focus-visible {
outline: none;
box-shadow: 0 0 0 3px var(--nds-ring);
border-radius: var(--nds-radius-xs);
}
}
+18 -57
View File
@@ -1,70 +1,31 @@
@use '../../styles/tokens/typography' as *;
@use "../../styles/tokens/typography" as *;
.list-item {
width: 350px;
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--nds-spacing-md);
border-radius: var(--nds-radius-sm);
background: var(--nds-background, #040B0B);
flex-shrink: 0;
position: relative;
cursor: pointer;
&:hover {
background: color-mix(in srgb, var(--nds-secondary) 25%, transparent);
}
&:active {
background: color-mix(in srgb, var(--nds-accent) 25%, transparent);
}
&::after {
content: '';
position: absolute;
inset: 0;
border-right: 1px solid;
border-bottom: 1px solid;
gap: var(--nds-spacing-sm);
padding: var(--nds-spacing-sm) var(--nds-spacing-md);
border-radius: var(--nds-radius-md);
border-color: transparent;
background:
linear-gradient(var(--nds-background, #040B0B), var(--nds-background, #040B0B)) padding-box,
linear-gradient(
135deg,
transparent 0%,
transparent 20%,
rgba(245, 250, 250, 0.08) 50%,
rgba(245, 250, 250, 0.28) 100%
) border-box;
-webkit-mask:
linear-gradient(#fff 0 0) padding-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: destination-out;
mask-composite: exclude;
pointer-events: none;
transition: background-color 120ms ease;
&:hover {
background-color: var(--nds-surface-hover);
}
&::before {
content: '';
position: absolute;
top: 0;
right: -1px;
width: 1px;
height: 100%;
pointer-events: none;
}
&__content {
&__body {
display: flex;
flex-direction: column;
gap: var(--nds-spacing-md);
gap: var(--nds-spacing-3xs);
flex: 1;
min-width: 0;
}
&__subTitle {
@include text-base;
}
&__title {
@include text-xl;
@include text-label;
color: var(--nds-text);
margin: 0;
}
&__subtitle {
@include text-sm;
color: var(--nds-neutral);
margin: 0;
}
}
+5 -9
View File
@@ -1,15 +1,11 @@
---
import Arrow2 from "../Icons/Arrow2/Arrow2.astro"
export interface Props {}
---
<div class='list-item'>
<div class='list-item__content'>
<slot/>
</div>
<Arrow2
size={24}
orientation='right' />
<div class="list-item">
<slot />
</div>
<style lang="scss">
@use './listItem';
@use "./_listItem.scss";
</style>
@@ -1,7 +1,11 @@
<div class='list-item__subTitle'>
<slot/>
</div>
---
export interface Props {}
---
<p class="list-item__subtitle">
<slot />
</p>
<style lang="scss">
@use './listItem';
@use "./_listItem.scss";
</style>
+8 -4
View File
@@ -1,7 +1,11 @@
<div class='list-item__title'>
<slot/>
</div>
---
export interface Props {}
---
<p class="list-item__title">
<slot />
</p>
<style lang="scss">
@use './listItem';
@use "./_listItem.scss";
</style>
+17 -20
View File
@@ -1,37 +1,34 @@
@use '../../styles/tokens/typography' as *;
@use "../../styles/tokens/typography" as *;
.loading-bar {
position: relative;
width: 100%;
height: 6px;
background-color: var(--nds-text);
background-color: var(--nds-surface-2);
overflow: hidden;
border-radius: var(--nds-radius-sm);
border-radius: var(--nds-radius-full);
&__progress {
height: 100%;
background-color: var(--nds-primary);
transition: width 0.3s ease;
border-radius: var(--nds-radius-sm);
border-radius: var(--nds-radius-full);
transition: width 0.35s ease;
}
&__unknown {
position: absolute;
top: 0;
left: -50%;
width: 50%;
left: 0;
width: 40%;
height: 100%;
background-color: var(--nds-primary);
border-radius: var(--nds-radius-sm);
animation: loadingBarIndeterminate 1.5s infinite;
}
@keyframes loadingBarIndeterminate {
from {
left: -50%;
}
to {
left: 100%;
}
border-radius: var(--nds-radius-full);
animation: nds-lb 1.3s ease-in-out infinite;
}
}
@keyframes nds-lb {
0% {
left: -40%;
}
100% {
left: 100%;
}
}
+13 -7
View File
@@ -1,19 +1,25 @@
---
export interface Props {
type: 'known' | 'unknown';
type: "known" | "unknown";
percentage?: number;
width?: string;
}
const { type, percentage, width } = Astro.props;
---
<div class='loading-bar loading-bar--${type}' style={width ? `width: ${width}` : undefined}>
{type === 'known' && percentage !== undefined ? (
<div class='loading-bar__progress' style={`width: ${percentage}%`}></div>
<div
class={`loading-bar loading-bar--${type}`}
style={width ? `width: ${width}` : undefined}
>
{
type === "known" && percentage !== undefined ? (
<div class="loading-bar__progress" style={`width: ${percentage}%`} />
) : (
<div class='loading-bar__unknown'></div>
)}
<div class="loading-bar__unknown" />
)
}
</div>
<style lang="scss">
@use './loadingBar';
@use "./_loadingBar.scss";
</style>
+78
View File
@@ -0,0 +1,78 @@
@use "../../styles/tokens/typography" as *;
.modal {
&__overlay {
position: fixed;
inset: 0;
background-color: rgba(2, 12, 12, 0.55);
backdrop-filter: blur(3px);
display: none;
align-items: center;
justify-content: center;
z-index: 200;
padding: var(--nds-spacing-md);
}
&__dialog {
width: min(460px, 100%);
background-color: var(--nds-surface);
border: var(--nds-border-width-thin) solid var(--nds-border);
border-radius: var(--nds-radius-xl);
overflow: hidden;
box-shadow: var(--nds-shadow-lg);
animation: nds-modal-in 180ms cubic-bezier(0.2, 0.8, 0.3, 1);
}
&__head {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--nds-spacing-md) var(--nds-spacing-lg);
border-bottom: var(--nds-border-width-thin) solid var(--nds-border);
}
&__title {
@include text-xl;
color: var(--nds-text);
}
&__close {
background: transparent;
border: none;
color: var(--nds-neutral);
cursor: pointer;
display: flex;
padding: var(--nds-spacing-3xs);
border-radius: var(--nds-radius-sm);
transition:
color 120ms ease,
background-color 120ms ease;
&:hover {
color: var(--nds-text);
background-color: var(--nds-surface-hover);
}
}
&__body {
padding: var(--nds-spacing-lg);
@include text-base;
color: var(--nds-text);
}
&__footer {
display: flex;
justify-content: flex-end;
gap: var(--nds-spacing-xs);
padding: var(--nds-spacing-md) var(--nds-spacing-lg);
border-top: var(--nds-border-width-thin) solid var(--nds-border);
}
}
nds-modal[data-open="true"] .modal__overlay {
display: flex;
}
@keyframes nds-modal-in {
from {
opacity: 0;
transform: translateY(8px) scale(0.98);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
+84
View File
@@ -0,0 +1,84 @@
---
import { CloseIcon } from "../Icons";
export interface Props {
id: string;
title?: string;
open?: boolean;
}
const { id, title, open = false } = Astro.props;
const hasFooter = Astro.slots.has("footer");
---
<nds-modal id={id} class="modal" data-open={open ? "true" : undefined}>
<div class="modal__overlay" data-modal-close>
<div
class="modal__dialog"
role="dialog"
aria-modal="true"
aria-label={title}
>
<div class="modal__head">
<span class="modal__title">{title}</span>
<button class="modal__close" data-modal-close aria-label="Close">
<CloseIcon size={18} label="Close" />
</button>
</div>
<div class="modal__body"><slot /></div>
{
hasFooter && (
<div class="modal__footer">
<slot name="footer" />
</div>
)
}
</div>
</div>
</nds-modal>
<script>
class NdsModal extends HTMLElement {
connectedCallback() {
this.addEventListener("click", (e) => {
const target = e.target as HTMLElement;
if (
target.closest("[data-modal-close]") ===
target.closest(".modal__dialog")
) {
// clicked dialog interior, ignore
}
if (
target.matches("[data-modal-close]") ||
target.classList.contains("modal__overlay")
) {
this.close();
}
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && this.dataset.open === "true") this.close();
});
}
open() {
this.dataset.open = "true";
this.dispatchEvent(new CustomEvent("nds:open", { bubbles: true }));
}
close() {
delete this.dataset.open;
this.dispatchEvent(new CustomEvent("nds:close", { bubbles: true }));
}
}
customElements.define("nds-modal", NdsModal);
document.addEventListener("click", (e) => {
const trigger = (e.target as HTMLElement).closest<HTMLElement>(
"[data-modal-open]",
);
if (!trigger) return;
const modal = document.getElementById(trigger.dataset.modalOpen!) as any;
modal?.open?.();
});
</script>
<style lang="scss">
@use "./_modal.scss";
</style>
+58
View File
@@ -0,0 +1,58 @@
@use "../../styles/tokens/typography" as *;
.navbar {
display: flex;
align-items: center;
gap: var(--nds-spacing-lg);
height: 60px;
padding: 0 var(--nds-spacing-lg);
background-color: var(--nds-surface);
border-bottom: var(--nds-border-width-thin) solid var(--nds-border);
&__brand {
display: flex;
align-items: center;
gap: var(--nds-spacing-xs);
}
&__mark {
width: 24px;
height: 24px;
border-radius: var(--nds-radius-md);
background: linear-gradient(135deg, var(--nds-primary), var(--nds-accent));
}
&__title {
@include text-xl;
font-size: 1rem;
color: var(--nds-text);
}
&__links {
display: flex;
align-items: center;
gap: var(--nds-spacing-xs);
flex: 1;
:global(.navbar__link) {
padding: var(--nds-spacing-2xs) var(--nds-spacing-sm);
border-radius: var(--nds-radius-md);
@include text-label;
color: var(--nds-text);
text-decoration: none !important;
transition: all 120ms ease;
}
:global(.navbar__link:hover) {
background-color: var(--nds-surface-hover);
}
// On cible la variante active globale séparément
:global(.navbar__link--active) {
color: var(--nds-primary);
}
}
&__right {
display: flex;
align-items: center;
gap: var(--nds-spacing-sm);
}
}
+19
View File
@@ -0,0 +1,19 @@
---
export interface Props {
brand?: string;
}
const { brand = "Nova" } = Astro.props;
---
<header class="navbar">
<div class="navbar__brand">
<span class="navbar__mark"></span>
<span class="navbar__title">{brand}</span>
</div>
<nav class="navbar__links"><slot /></nav>
<div class="navbar__right"><slot name="right" /></div>
</header>
<style lang="scss">
@use "./_navbar.scss";
</style>
+39 -50
View File
@@ -1,61 +1,50 @@
@use '../../styles/tokens/typography' as *;
@use "../../styles/tokens/typography" as *;
.notification {
display: flex;
align-items: flex-start;
justify-content: space-between;
align-items: center;
padding: var(--nds-spacing-sm) var(--nds-spacing-xs) ;
border-radius: var(--nds-radius-md);
@include text-sm;
&__error {
background-color: var(--nds-error-low);
&__close {
background: transparent;
border: none;
color: var(--nds-error-medium);
cursor: pointer;
}
&__content {
color: var(--nds-error-high);
}
}
&__success {
background-color: var(--nds-success-low);
&__close {
background: transparent;
border: none;
color: var(--nds-success-medium);
cursor: pointer;
}
&__content {
color: var(--nds-success-high);
}
}
&__warning {
background-color: var(--nds-warning-low);
&__close {
background: transparent;
border: none;
color: var(--nds-warning-medium);
cursor: pointer;
}
&__content {
color: var(--nds-warning-high);
}
}
gap: var(--nds-spacing-sm);
padding: var(--nds-spacing-sm) var(--nds-spacing-md);
border-radius: var(--nds-radius-lg);
background-color: var(--nds-surface);
box-shadow: var(--nds-shadow-md);
border-left: 3px solid var(--nds-info-medium);
@include text-base;
max-width: 400px;
&__info {
background-color: var(--nds-info-low);
&__close {
border-left-color: var(--nds-info-medium);
}
&__success {
border-left-color: var(--nds-success-medium);
}
&__warning {
border-left-color: var(--nds-warning-medium);
}
&__error {
border-left-color: var(--nds-error-medium);
}
&__info__content,
&__success__content,
&__warning__content,
&__error__content {
color: var(--nds-text);
flex: 1;
}
&__info__close,
&__success__close,
&__warning__close,
&__error__close {
background: transparent;
border: none;
color: var(--nds-info-medium);
cursor: pointer;
}
&__content {
color: var(--nds-info-high);
display: flex;
color: var(--nds-neutral);
padding: 0;
border-radius: var(--nds-radius-sm);
transition: color 120ms ease;
&:hover {
color: var(--nds-text);
}
}
}
@@ -1,23 +1,30 @@
---
import { CloseIcon } from "../Icons";
export interface Props {
type: 'info' | 'success' | 'warning' | 'error';
type: "info" | "success" | "warning" | "error";
}
const notificationUUID = `notification-${Math.random().toString(36).substr(2, 9)}`;
const notificationUUID = `notification-${Math.random().toString(36).slice(2, 11)}`;
const { type } = Astro.props;
---
<div class={`notification notification__${type}`} role="alert" id={notificationUUID}>
<div class=`notification__${type}__content`>
<div
class={`notification notification__${type}`}
role="alert"
id={notificationUUID}
>
<div class={`notification__${type}__content`}>
<slot />
</div>
<button class={`notification__${type}__close`} aria-label="Close notification" onclick={`document.getElementById('${notificationUUID}')?.remove()`}>
<button
class={`notification__${type}__close`}
aria-label="Close notification"
onclick={`document.getElementById('${notificationUUID}')?.remove()`}
>
<CloseIcon size={16} label="Close notification" />
</button>
</div>
<style lang="scss">
@use './_notification.scss';
@use "./_notification.scss";
</style>
+50
View File
@@ -0,0 +1,50 @@
@use "../../styles/tokens/typography" as *;
.radio {
display: inline-flex;
align-items: center;
gap: var(--nds-spacing-xs);
cursor: pointer;
@include text-base;
color: var(--nds-text);
&__input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
&__dot {
width: 18px;
height: 18px;
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
border: var(--nds-border-width-medium) solid var(--nds-border-strong);
border-radius: var(--nds-radius-full);
background-color: var(--nds-surface);
transition: border-color 130ms ease;
&::after {
content: "";
width: 8px;
height: 8px;
border-radius: var(--nds-radius-full);
background-color: var(--nds-primary);
transform: scale(0);
transition: transform 130ms cubic-bezier(0.34, 1.4, 0.6, 1);
}
}
&__input:checked + &__dot {
border-color: var(--nds-primary);
&::after {
transform: scale(1);
}
}
&__input:focus-visible + &__dot {
box-shadow: 0 0 0 3px var(--nds-ring);
}
&--disabled {
cursor: not-allowed;
opacity: 0.5;
}
}
+28
View File
@@ -0,0 +1,28 @@
---
export interface Props {
id: string;
name: string;
value?: string;
checked?: boolean;
disabled?: boolean;
}
const { id, name, value, checked = false, disabled = false } = Astro.props;
---
<label class={`radio ${disabled ? "radio--disabled" : ""}`} for={id}>
<input
type="radio"
id={id}
name={name}
value={value}
checked={checked}
disabled={disabled}
class="radio__input"
/>
<span class="radio__dot" aria-hidden="true"></span>
<span class="radio__label"><slot /></span>
</label>
<style lang="scss">
@use "./_radio.scss";
</style>
+64 -45
View File
@@ -1,71 +1,90 @@
@use '../../styles/tokens/typography' as *;
@use "../../styles/tokens/typography" as *;
.select {
position: relative;
display: inline-flex;
flex-direction: column;
width: 100%;
min-width: 200px;
&__trigger {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--nds-spacing-xs) var(--nds-spacing-md);
background-color: var(--nds-neutral);
color: var(--nds-text);
border: none;
border-radius: var(--nds-radius-sm);
cursor: pointer;
width: 100%;
gap: var(--nds-spacing-xs);
height: 38px;
padding: 0 var(--nds-spacing-sm);
background-color: var(--nds-surface);
border: var(--nds-border-width-thin) solid var(--nds-border-strong);
border-radius: var(--nds-radius-md);
cursor: pointer;
@include text-base;
color: var(--nds-text);
transition:
border-color 130ms ease,
box-shadow 130ms ease;
&:hover {
background-color: color-mix(in srgb, var(--nds-neutral) 85%, var(--nds-text));
border-color: var(--nds-neutral);
}
&:focus-visible {
outline: none;
border-color: var(--nds-primary);
box-shadow: 0 0 0 3px var(--nds-ring);
}
&__label {
flex: 1;
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&__arrow {
svg {
color: var(--nds-neutral);
transition: transform 160ms ease;
flex-shrink: 0;
}
}
&__placeholder {
color: var(--nds-neutral);
}
&[data-open="true"] &__trigger {
border-color: var(--nds-primary);
}
&[data-open="true"] &__trigger svg {
transform: rotate(180deg);
}
&__menu {
position: absolute;
top: calc(100% + 6px);
left: 0;
right: 0;
z-index: 40;
background-color: var(--nds-surface);
border: var(--nds-border-width-thin) solid var(--nds-border);
border-radius: var(--nds-radius-md);
box-shadow: var(--nds-shadow-md);
padding: var(--nds-spacing-3xs);
max-height: 240px;
overflow-y: auto;
display: none;
}
&__arrow {
display: flex;
align-items: center;
justify-content: center;
&--hidden {
display: none;
}
}
&__dropdown {
position: absolute;
top: calc(100% + var(--nds-spacing-2xs));
left: 0;
right: 0;
background-color: var(--nds-neutral);
border-radius: var(--nds-radius-sm);
list-style: none;
margin: 0;
padding: 0;
z-index: 100;
overflow: hidden;
&[hidden] {
display: none;
}
}
&--disabled {
opacity: 0.5;
.select__trigger {
background-color: var(--nds-disabled);
cursor: not-allowed;
background-color: var(--nds-surface);
color: var(--nds-neutral);
border-color: var(--nds-border-strong);
opacity: 0.55;
& .select__trigger {
cursor: not-allowed;
&:hover {
border-color: var(--nds-border-strong);
}
}
}
&[data-open="true"] &__menu {
display: block;
}
}
}
+16 -20
View File
@@ -1,32 +1,28 @@
@use '../../styles/tokens/typography' as *;
@use "../../styles/tokens/typography" as *;
.select-option {
padding: var(--nds-spacing-xs) var(--nds-spacing-md);
display: flex;
align-items: center;
gap: var(--nds-spacing-xs);
padding: var(--nds-spacing-xs) var(--nds-spacing-sm);
cursor: pointer;
border-radius: var(--nds-radius-sm);
@include text-base;
color: var(--nds-text);
background-color: var(--nds-neutral);
transition: background-color 0.15s ease;
&:hover:not(.select-option__disabled) {
background-color: var(--nds-secondary);
}
&:active {
background-color: var(--nds-accent);
}
&__selected {
background-color: var(--nds-primary);
color: var(--nds-text);
transition: background-color 110ms ease;
&:hover {
background-color: color-mix(in srgb, var(--nds-primary) 85%, var(--nds-background));
background-color: var(--nds-surface-hover);
}
&[aria-selected="true"] {
color: var(--nds-primary);
font-weight: 600;
}
&__disabled {
background-color: var(--nds-disabled);
color: color-mix(in srgb, var(--nds-text) 40%, transparent);
color: var(--nds-disabled);
cursor: not-allowed;
&:hover {
background: transparent;
}
}
}
+67
View File
@@ -0,0 +1,67 @@
@use "../../styles/tokens/typography" as *;
.sidebar {
display: flex;
flex-direction: column;
width: 240px;
height: 100%;
box-sizing: border-box;
padding: var(--nds-spacing-sm);
background-color: var(--nds-surface);
border-right: var(--nds-border-width-thin) solid var(--nds-border);
&__head {
display: flex;
align-items: center;
gap: var(--nds-spacing-xs);
padding: var(--nds-spacing-xs) var(--nds-spacing-xs) var(--nds-spacing-md);
}
&__mark {
width: 24px;
height: 24px;
border-radius: var(--nds-radius-md);
background: linear-gradient(135deg, var(--nds-primary), var(--nds-accent));
}
&__title {
@include text-xl;
font-size: 1rem;
color: var(--nds-text);
}
&__nav {
display: flex;
flex-direction: column;
gap: var(--nds-spacing-3xs);
flex: 1;
}
&__item {
display: flex;
align-items: center;
gap: var(--nds-spacing-xs);
padding: var(--nds-spacing-xs) var(--nds-spacing-sm);
border-radius: var(--nds-radius-md);
text-decoration: none;
color: var(--nds-text);
@include text-label;
font-weight: 500;
transition:
background-color 120ms ease,
color 120ms ease;
&:hover {
background-color: var(--nds-surface-hover);
}
&--active {
background-color: var(--nds-primary-soft);
color: var(--nds-primary);
font-weight: 600;
}
}
&__item-icon {
display: flex;
flex-shrink: 0;
}
&__footer {
padding-top: var(--nds-spacing-sm);
}
}
+23
View File
@@ -0,0 +1,23 @@
---
export interface Props {
header?: string;
}
const { header = "Nova" } = Astro.props;
---
<aside class="sidebar">
<div class="sidebar__head">
<span class="sidebar__mark"></span>
<span class="sidebar__title">{header}</span>
</div>
<nav class="sidebar__nav">
<slot />
</nav>
<div class="sidebar__footer">
<slot name="footer" />
</div>
</aside>
<style lang="scss">
@use "./_sidebar.scss";
</style>
+23
View File
@@ -0,0 +1,23 @@
---
export interface Props {
href?: string;
active?: boolean;
}
const { href = "#", active = false } = Astro.props;
const hasIcon = Astro.slots.has("icon");
---
<a href={href} class={`sidebar__item ${active ? "sidebar__item--active" : ""}`}>
{
hasIcon && (
<span class="sidebar__item-icon">
<slot name="icon" />
</span>
)
}
<span class="sidebar__item-label"><slot /></span>
</a>
<style lang="scss">
@use "./_sidebar.scss";
</style>
+29 -22
View File
@@ -1,33 +1,40 @@
@use '../../styles/tokens/typography' as *;
@use "../../styles/tokens/typography" as *;
.tab {
display: flex;
align-items: flex-start;
gap: var(--nds-spacing-xs);
justify-content: center;
align-items: stretch;
gap: var(--nds-spacing-2xs);
border-bottom: 1px solid var(--nds-border);
width: fit-content;
&__item {
display: flex;
flex-direction: column;
display: inline-flex;
align-items: center;
justify-content: center;
height: fit-content;
gap: var(--nds-spacing-2xs);
padding: var(--nds-spacing-xs) var(--nds-spacing-sm);
cursor: pointer;
padding: var(--nds-spacing-2xs);
@include text-base;
&--active {
color: var(--nds-primary);
&::after {
content: '';
display: block;
width: 120%;
height: 1px;
background-color: var(--nds-primary);
border-radius: var(--nds-radius-sm);
@include text-label;
color: var(--nds-neutral);
background: transparent;
border: none;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
transition:
color 130ms ease,
border-color 130ms ease;
&:hover {
color: var(--nds-text);
}
&--active {
color: var(--nds-text);
border-bottom-color: var(--nds-primary);
}
}
}
.tab__content {
padding-top: var(--nds-spacing-md);
@include text-base;
color: var(--nds-text);
&[hidden] {
display: none;
}
}
+11 -31
View File
@@ -1,33 +1,13 @@
.toggle {
background-color: var(--nds-disabled);
border-radius: var(--nds-radius-full);
padding: var(--nds-spacing-2xs);
width: 50px;
height: 25px;
display: flex;
align-items: center;
justify-content: left;
cursor: pointer;
transition: background-color 160ms ease;
position: relative;
&-switch {
color: var(--nds-text);
width: 15px;
height: 15px;
background-color: var(--nds-text);
border-radius: var(--nds-radius-full);
position: absolute;
left: var(--nds-spacing-2xs);
top: 50%;
transform: translateY(-50%);
transition: left 160ms ease, background-color 160ms ease, transform 160ms ease;
}
&-active {
display: flex;
justify-content: right;
background-color: var(--nds-primary);
.toggle-switch {
left: calc(100% - var(--nds-spacing-2xs) - 15px);
}
}
--w: 44px; --h: 24px; --pad: 3px;
position: relative; width: var(--w); height: var(--h);
background-color: var(--nds-disabled); border-radius: var(--nds-radius-full);
padding: var(--pad); display: flex; align-items: center; cursor: pointer;
transition: background-color 180ms ease;
&-switch { width: calc(var(--h) - var(--pad) * 2); height: calc(var(--h) - var(--pad) * 2);
background-color: #fff; border-radius: var(--nds-radius-full); box-shadow: var(--nds-shadow-sm);
transition: transform 180ms cubic-bezier(.34,1.4,.6,1); }
&-active { background-color: var(--nds-primary); }
&-active .toggle-switch { transform: translateX(calc(var(--w) - var(--h))); }
}
toggle-switch:focus-visible .toggle { box-shadow: 0 0 0 3px var(--nds-ring); outline: none; }
+2 -4
View File
@@ -1,8 +1,6 @@
---
import { boolean } from 'astro:schema';
export interface Props {
'data-checked': boolean;
'data-checked'?: boolean | string;
name?: string;
id: string;
}
@@ -10,7 +8,7 @@ export interface Props {
const { 'data-checked': dataChecked = 'false', name = 'toggle', id } = Astro.props;
---
<toggle-switch data-checked={dataChecked} data-name={name} id={id}>
<toggle-switch data-checked={String(dataChecked)} data-name={name} id={id}>
<div class="toggle">
<div class="toggle-switch"></div>
</div>
+45
View File
@@ -0,0 +1,45 @@
@use "../../styles/tokens/typography" as *;
.tooltip {
position: relative;
display: inline-flex;
&__bubble {
position: absolute;
white-space: nowrap;
padding: var(--nds-spacing-2xs) var(--nds-spacing-xs);
background-color: var(--nds-text);
color: var(--nds-background);
border-radius: var(--nds-radius-sm);
@include text-sm;
font-weight: 500;
box-shadow: var(--nds-shadow-md);
z-index: 100;
opacity: 0;
pointer-events: none;
transition: opacity 130ms ease;
}
&:hover &__bubble,
&:focus-within &__bubble {
opacity: 1;
}
&--top &__bubble {
bottom: 100%;
left: 50%;
transform: translate(-50%, -6px);
}
&--bottom &__bubble {
top: 100%;
left: 50%;
transform: translate(-50%, 6px);
}
&--left &__bubble {
right: 100%;
top: 50%;
transform: translate(-6px, -50%);
}
&--right &__bubble {
left: 100%;
top: 50%;
transform: translate(6px, -50%);
}
}
+16
View File
@@ -0,0 +1,16 @@
---
export interface Props {
label: string;
position?: "top" | "bottom" | "left" | "right";
}
const { label, position = "top" } = Astro.props;
---
<span class={`tooltip tooltip--${position}`}>
<slot />
<span class="tooltip__bubble" role="tooltip">{label}</span>
</span>
<style lang="scss">
@use "./_tooltip.scss";
</style>
@@ -1,14 +1,44 @@
@use '../../styles/tokens/typography' as *;
@use "../../styles/tokens/typography" as *;
.numeric-stepper {
display: flex;
flex-direction: column;
display: inline-flex;
align-items: center;
gap: var(--nds-spacing-xs);
gap: 0;
border: var(--nds-border-width-thin) solid var(--nds-border-strong);
border-radius: var(--nds-radius-md);
background-color: var(--nds-surface);
overflow: hidden;
width: fit-content;
& p {
@include text-xl;
margin: 0;
height: fit-content;
&__button {
width: 36px;
height: 36px;
display: inline-flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
cursor: pointer;
color: var(--nds-text);
transition: background-color 120ms ease;
&:hover:not(:disabled) {
background-color: var(--nds-surface-hover);
}
&:disabled {
color: var(--nds-disabled);
cursor: not-allowed;
}
}
&__value {
width: 48px;
text-align: center;
@include text-label;
color: var(--nds-text);
padding: 0;
margin: 0;
border: none;
outline: none;
background: transparent;
}
}
@@ -1,6 +1,6 @@
---
import Button from '../Button/button.astro';
import { Arrow2Icon } from '../Icons';
import { MinusIcon, PlusIcon } from '../Icons';
export interface Props {
id: string;
@@ -23,16 +23,16 @@ const { id, min, max, value = 0, step = 1 } = Astro.props;
data-step={step}
>
<span class="numeric-stepper__control" data-action="increment">
<Button type="secondary" icon={true}>
<Arrow2Icon size={24} orientation="up" />
<Button type="ghost" icon={true}>
<MinusIcon size={24} />
</Button>
</span>
<p class="numeric-stepper__value">{value}</p>
<span class="numeric-stepper__control" data-action="decrement">
<Button type="secondary" icon={true}>
<Arrow2Icon size={24} orientation="down" />
<Button type="ghost" icon={true}>
<PlusIcon size={24} />
</Button>
</span>
</div>
@@ -113,5 +113,5 @@ const { id, min, max, value = 0, step = 1 } = Astro.props;
</script>
<style lang="scss">
@use './numericStepper';
@use "./numericStepper";
</style>
+3 -2
View File
@@ -1,6 +1,7 @@
@use '../../styles/tokens/typography' as *;
@use "../../styles/tokens/typography" as *;
.pagination {
display: flex;
gap: var(--nds-spacing-sm);
align-items: center;
gap: var(--nds-spacing-2xs);
}
@@ -1,26 +1,30 @@
@use '../../styles/tokens/typography' as *;
@use "../../styles/tokens/typography" as *;
.pagination-number {
width: 30px;
height: 30px;
display: flex;
min-width: 32px;
height: 32px;
padding: 0 var(--nds-spacing-2xs);
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--nds-radius-full);
border-radius: var(--nds-radius-md);
cursor: pointer;
@include text-label;
color: var(--nds-text);
background: transparent;
border: 1px solid transparent;
transition: all 120ms ease;
user-select: none;
&:hover:not(.pagination-number__disabled):not(.pagination-number__selected) {
background-color: var(--nds-secondary);
cursor: pointer;
}
&:active:not(.pagination-number__disabled):not(.pagination-number__selected) {
background-color: var(--nds-accent);
cursor: pointer;
}
&__disabled {
background-color: var(--nds-disabled);
cursor: not-allowed;
border-color: var(--nds-border-strong);
background-color: var(--nds-surface-hover);
}
&__selected {
background-color: var(--nds-primary);
cursor: pointer;
color: var(--nds-on-primary);
}
&__disabled {
color: var(--nds-disabled);
cursor: not-allowed;
}
}
+1
View File
@@ -42,6 +42,7 @@
min-height: 84px;
padding: var(--nds-spacing-sm);
resize: vertical;
width: 100% !important;
line-height: 1.5;
}
&__charCount {
+6 -18
View File
@@ -1,29 +1,17 @@
---
import '../styles/index.scss';
interface Props { title?: string; }
const { title = 'Nova Design System' } = Astro.props;
---
<!doctype html>
<html lang="en">
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" href="/favicon.ico" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="generator" content={Astro.generator} />
<title>Nova Design System</title>
<title>{title}</title>
</head>
<body>
<slot />
</body>
</html>
<style>
html,
body {
margin: 0;
width: 100%;
min-height: 100%;
font-family: var(--nds-font-family-base);
background-color: var(--nds-page-low);
color: var(--nds-default);
}
</style>
File diff suppressed because it is too large Load Diff
+672 -348
View File
File diff suppressed because it is too large Load Diff