astro-component-kit 0.4.0 → 0.4.1
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 +105 -27
- package/package.json +16 -3
- package/src/components/index.ts +2 -0
- package/src/components/lib/Galaxy3D.astro +249 -137
- package/src/components/lib/app/AppSidebar.astro +249 -0
- package/src/components/lib/app/AppTopbar.astro +284 -0
- package/src/components/lib/app/ArrivalsBoard.astro +176 -0
- package/src/components/lib/app/AuthCard.astro +213 -0
- package/src/components/lib/app/DataTable.astro +346 -0
- package/src/components/lib/app/GuestProfileCard.astro +133 -0
- package/src/components/lib/app/HousekeepingBoard.astro +189 -0
- package/src/components/lib/app/PageHeader.astro +157 -0
- package/src/components/lib/app/PlanUsageCard.astro +112 -0
- package/src/components/lib/app/RoomRack.astro +207 -0
- package/src/components/lib/app/SettingsSection.astro +96 -0
- package/src/components/lib/app/SwitchRow.astro +71 -0
- package/src/components/lib/app/TeamMembers.astro +190 -0
- package/src/components/lib/app/app-icons.ts +52 -0
- package/src/components/lib/app/index.ts +19 -0
- package/src/components/lib/charts/AreaChart.astro +4 -4
- package/src/components/lib/charts/BarChart.astro +3 -3
- package/src/components/lib/charts/ChartFrame.astro +10 -10
- package/src/components/lib/charts/DonutChart.astro +2 -2
- package/src/components/lib/charts/DumbbellChart.astro +1 -1
- package/src/components/lib/charts/ForecastLineChart.astro +1 -1
- package/src/components/lib/charts/GaugeChart.astro +1 -1
- package/src/components/lib/charts/HeatmapChart.astro +1 -1
- package/src/components/lib/charts/HorizontalBarChart.astro +2 -2
- package/src/components/lib/charts/KpiStatTile.astro +4 -4
- package/src/components/lib/charts/LinearProgressGauge.astro +4 -4
- package/src/components/lib/charts/MetricDeltaTable.astro +3 -3
- package/src/components/lib/charts/RadarMetricChart.astro +4 -4
- package/src/components/lib/charts/RadialBarChart.astro +1 -1
- package/src/components/lib/charts/RetentionCohortTable.astro +2 -2
- package/src/components/lib/charts/TreemapChart.astro +1 -1
- package/src/components/lib/ecommerce/CartDrawer.astro +483 -0
- package/src/components/lib/ecommerce/CartPageList.astro +290 -0
- package/src/components/lib/ecommerce/CartToast.astro +276 -0
- package/src/components/lib/ecommerce/CategoryTiles.astro +143 -0
- package/src/components/lib/ecommerce/CheckoutForm.astro +423 -0
- package/src/components/lib/ecommerce/CountdownBanner.astro +230 -0
- package/src/components/lib/ecommerce/FilterBar.astro +278 -0
- package/src/components/lib/ecommerce/FilterSidebar.astro +405 -0
- package/src/components/lib/ecommerce/LiveActivityToast.astro +213 -0
- package/src/components/lib/ecommerce/OrderSummary.astro +295 -0
- package/src/components/lib/ecommerce/ProductBuyBox.astro +247 -0
- package/src/components/lib/ecommerce/ProductCardMinimal.astro +229 -0
- package/src/components/lib/ecommerce/ProductGallery.astro +145 -0
- package/src/components/lib/ecommerce/ProductGrid.astro +118 -0
- package/src/components/lib/ecommerce/ProductMedia.astro +113 -0
- package/src/components/lib/ecommerce/ProductRating.astro +78 -0
- package/src/components/lib/ecommerce/ProductToolbar.astro +93 -0
- package/src/components/lib/ecommerce/PromoBar.astro +145 -0
- package/src/components/lib/ecommerce/StockIndicator.astro +127 -0
- package/src/components/lib/ecommerce/StoreHeader.astro +446 -0
- package/src/components/lib/ecommerce/StoreHero.astro +260 -0
- package/src/components/lib/ecommerce/StoreProductCard.astro +307 -0
- package/src/components/lib/ecommerce/TrustBadges.astro +101 -0
- package/src/components/lib/ecommerce/VariantPicker.astro +145 -0
- package/src/components/lib/ecommerce/cart-store.ts +151 -0
- package/src/components/lib/ecommerce/catalog-filter.ts +62 -0
- package/src/components/lib/ecommerce/index.ts +34 -0
- package/src/components/lib/ecommerce/product-icons.ts +28 -0
- package/src/components/lib/ecommerce/types.ts +70 -0
- package/src/components/lib/forms/AnimatedInput.astro +4 -2
- package/src/components/lib/forms/CustomRadioGroup.astro +3 -1
- package/src/components/lib/index.ts +2 -0
- package/src/components/lib/layout/BentoGrid3.astro +6 -4
- package/src/components/lib/layout/CenterFixedBox.astro +2 -2
- package/src/components/lib/layout/CenteredContainer.astro +4 -2
- package/src/components/lib/layout/ClusterHorizontal.astro +2 -2
- package/src/components/lib/layout/FeatureHero.astro +4 -2
- package/src/components/lib/layout/FooterCenteredMinimal.astro +4 -2
- package/src/components/lib/layout/FullBleedSection.astro +2 -2
- package/src/components/lib/layout/GalleryGrid4.astro +2 -2
- package/src/components/lib/layout/HolyGrailGrid.astro +11 -9
- package/src/components/lib/layout/LandingSectionSplit.astro +5 -3
- package/src/components/lib/layout/MasonryGrid.astro +2 -2
- package/src/components/lib/layout/MinimalFooterGrid.astro +1 -1
- package/src/components/lib/layout/ScrollSnapSection.astro +1 -1
- package/src/components/lib/layout/SidebarLayoutWrapper.astro +4 -2
- package/src/components/lib/layout/SplitScreenLayout.astro +3 -3
- package/src/components/lib/layout/StepLayoutVertical.astro +1 -1
- package/src/components/lib/layout/StickySidebarWrapper.astro +4 -2
- package/src/components/lib/sections/AgencyContactBrief.astro +194 -0
- package/src/components/lib/sections/AgencyFooterCta.astro +94 -0
- package/src/components/lib/sections/AgencyHeader.astro +161 -0
- package/src/components/lib/sections/AgencyHeroKinetic.astro +172 -0
- package/src/components/lib/sections/AgencyServicesList.astro +113 -0
- package/src/components/lib/sections/AgencyTeamGrid.astro +111 -0
- package/src/components/lib/sections/AgencyWorkGrid.astro +147 -0
- package/src/components/lib/sections/BlogGridSection.astro +9 -7
- package/src/components/lib/sections/ButtonsShowcase.astro +20 -18
- package/src/components/lib/sections/CardsShowcase.astro +28 -26
- package/src/components/lib/sections/CareersOpenings.astro +10 -8
- package/src/components/lib/sections/ChangelogSection.astro +14 -7
- package/src/components/lib/sections/ContactGlassForm.astro +10 -8
- package/src/components/lib/sections/ContactSplitMap.astro +11 -9
- package/src/components/lib/sections/CtaAppDownload.astro +13 -11
- package/src/components/lib/sections/CtaGlowBanner.astro +12 -10
- package/src/components/lib/sections/CtaNewsletterSection.astro +13 -11
- package/src/components/lib/sections/CtaSplitImage.astro +13 -11
- package/src/components/lib/sections/DataShowcase.astro +23 -21
- package/src/components/lib/sections/FaqAccordionSection.astro +8 -8
- package/src/components/lib/sections/FaqTwoColumn.astro +9 -7
- package/src/components/lib/sections/FeatureAlternatingRows.astro +10 -8
- package/src/components/lib/sections/FeatureBentoGrid.astro +13 -11
- package/src/components/lib/sections/FeatureIconGrid.astro +6 -6
- package/src/components/lib/sections/FeatureTabsShowcase.astro +13 -11
- package/src/components/lib/sections/FeatureTimelineFlow.astro +8 -6
- package/src/components/lib/sections/FeedbackShowcase.astro +15 -13
- package/src/components/lib/sections/FooterMegaSection.astro +13 -11
- package/src/components/lib/sections/FormsShowcase.astro +22 -20
- package/src/components/lib/sections/HeroDashboardPreview.astro +11 -9
- package/src/components/lib/sections/HeroGlass.astro +22 -20
- package/src/components/lib/sections/HeroGradient.astro +15 -13
- package/src/components/lib/sections/HeroMinimalCentered.astro +10 -10
- package/src/components/lib/sections/HeroShowcaseSplit.astro +18 -16
- package/src/components/lib/sections/HeroSplit.astro +13 -11
- package/src/components/lib/sections/HeroTerminal.astro +14 -12
- package/src/components/lib/sections/HeroWaitlist.astro +10 -8
- package/src/components/lib/sections/HowItWorksSteps.astro +12 -10
- package/src/components/lib/sections/IntegrationsGrid.astro +7 -7
- package/src/components/lib/sections/LayoutShowcase.astro +21 -19
- package/src/components/lib/sections/LogoCloudSection.astro +4 -4
- package/src/components/lib/sections/LogoWallGrid.astro +6 -4
- package/src/components/lib/sections/NavigationShowcase.astro +15 -13
- package/src/components/lib/sections/OverlaysShowcase.astro +21 -19
- package/src/components/lib/sections/PricingComparisonMatrix.astro +15 -13
- package/src/components/lib/sections/PricingSinglePlan.astro +13 -11
- package/src/components/lib/sections/PricingTierCards.astro +11 -9
- package/src/components/lib/sections/PricingToggleCards.astro +13 -13
- package/src/components/lib/sections/ShopCategoryHero.astro +103 -0
- package/src/components/lib/sections/ShopCollectionMosaic.astro +131 -0
- package/src/components/lib/sections/ShopFooterWordmark.astro +100 -0
- package/src/components/lib/sections/ShopHeaderEditorial.astro +396 -0
- package/src/components/lib/sections/ShopHeroEditorial.astro +151 -0
- package/src/components/lib/sections/ShopLookbook.astro +250 -0
- package/src/components/lib/sections/ShopNewsletterEditorial.astro +128 -0
- package/src/components/lib/sections/ShopProductRail.astro +150 -0
- package/src/components/lib/sections/ShopReviewsEditorial.astro +133 -0
- package/src/components/lib/sections/ShopValuesMarquee.astro +91 -0
- package/src/components/lib/sections/StatsCounterBand.astro +9 -7
- package/src/components/lib/sections/StatsDashboardSection.astro +4 -2
- package/src/components/lib/sections/StatsMetricShowcase.astro +9 -7
- package/src/components/lib/sections/TeamGridSection.astro +12 -10
- package/src/components/lib/sections/TestimonialCarousel.astro +10 -8
- package/src/components/lib/sections/TestimonialFeatured.astro +7 -5
- package/src/components/lib/sections/TestimonialGridSection.astro +7 -7
- package/src/components/lib/sections/TestimonialWallGlass.astro +11 -9
- package/src/components/lib/sections/TypographyShowcase.astro +10 -8
- package/src/components/lib/sections/index.ts +10 -0
- package/src/components/lib/templates/ecommerce/StoreCategoryPage.astro +160 -0
- package/src/components/lib/templates/ecommerce/StoreCheckoutPage.astro +98 -0
- package/src/components/lib/templates/ecommerce/StoreHomePage.astro +191 -0
- package/src/components/lib/templates/ecommerce/StoreLayout.astro +169 -0
- package/src/components/lib/templates/ecommerce/StoreProductPage.astro +165 -0
- package/src/components/lib/templates/ecommerce/data.ts +187 -0
- package/src/components/lib/templates/ecommerce/index.ts +14 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialBagPage.astro +52 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialCheckoutPage.astro +123 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialHomePage.astro +102 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialLayout.astro +216 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialProductPage.astro +205 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialShopPage.astro +112 -0
- package/src/components/lib/templates/ecommerce-editorial/data.ts +125 -0
- package/src/components/lib/templates/ecommerce-editorial/index.ts +16 -0
- package/src/components/lib/templates/saas-hotel/AppLayout.astro +83 -0
- package/src/components/lib/templates/saas-hotel/DashboardPage.astro +66 -0
- package/src/components/lib/templates/saas-hotel/GuestsPage.astro +65 -0
- package/src/components/lib/templates/saas-hotel/LandingPage.astro +213 -0
- package/src/components/lib/templates/saas-hotel/ReportsPage.astro +63 -0
- package/src/components/lib/templates/saas-hotel/ReservationsPage.astro +49 -0
- package/src/components/lib/templates/saas-hotel/RoomsPage.astro +48 -0
- package/src/components/lib/templates/saas-hotel/SettingsPage.astro +115 -0
- package/src/components/lib/templates/saas-hotel/SignInPage.astro +50 -0
- package/src/components/lib/templates/saas-hotel/data.ts +229 -0
- package/src/components/lib/templates/saas-hotel/index.ts +19 -0
- package/src/components/lib/templates/saas-hotel/lobby-theme.css +90 -0
- package/src/styles/global.css +95 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* AppSidebar — SaaS app navigation: workspace switcher, grouped nav items with icons and
|
|
4
|
+
* count badges, collapsible to an icon rail, and an upgrade/plan card at the bottom.
|
|
5
|
+
*
|
|
6
|
+
* @param {{name: string, plan?: string, initials?: string}} workspace - Workspace shown at the top.
|
|
7
|
+
* @param {Array<{label?: string, items: {label: string, href: string, icon?: string, badge?: string|number}[]}>} groups - Nav groups.
|
|
8
|
+
* @param {string} currentHref - Marks the matching item with aria-current.
|
|
9
|
+
* @param {{title: string, text?: string, href: string, cta?: string, progress?: number}} promo - Card at the bottom (optional).
|
|
10
|
+
* @param {boolean} fullHeight - Full-viewport sticky sidebar (app shell). false renders it inline (docs, cards). Default true.
|
|
11
|
+
* @param {boolean} collapsible - Show the collapse toggle (remembered in localStorage). Default true.
|
|
12
|
+
* @param {string} variant - Style (surface|transparent|accent). Default 'surface'.
|
|
13
|
+
* @param {string} size - Width preset (sm|md|lg). Default 'md'.
|
|
14
|
+
* @param {string} id - Element id (AppTopbar's menu button opens it on mobile). Default 'app-sidebar'.
|
|
15
|
+
* @param {string} className - Extra class.
|
|
16
|
+
*
|
|
17
|
+
* Accessibility: a labelled nav landmark; the current page uses aria-current; when collapsed,
|
|
18
|
+
* labels stay available to screen readers and appear as tooltips on hover/focus.
|
|
19
|
+
*/
|
|
20
|
+
import { appIcon } from './app-icons';
|
|
21
|
+
|
|
22
|
+
interface Item { label: string; href: string; icon?: string; badge?: string | number }
|
|
23
|
+
interface Group { label?: string; items: Item[] }
|
|
24
|
+
interface Props {
|
|
25
|
+
workspace?: { name: string; plan?: string; initials?: string };
|
|
26
|
+
groups?: Group[];
|
|
27
|
+
currentHref?: string;
|
|
28
|
+
promo?: { title: string; text?: string; href: string; cta?: string; progress?: number };
|
|
29
|
+
fullHeight?: boolean;
|
|
30
|
+
collapsible?: boolean;
|
|
31
|
+
variant?: 'surface' | 'transparent' | 'accent';
|
|
32
|
+
size?: 'sm' | 'md' | 'lg';
|
|
33
|
+
id?: string;
|
|
34
|
+
className?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
workspace = { name: 'Hotel Mirador', plan: 'Pro plan', initials: 'HM' },
|
|
39
|
+
groups = [
|
|
40
|
+
{ items: [
|
|
41
|
+
{ label: 'Dashboard', href: '#', icon: 'dashboard' },
|
|
42
|
+
{ label: 'Reservations', href: '#reservations', icon: 'calendar', badge: 12 },
|
|
43
|
+
{ label: 'Rooms', href: '#rooms', icon: 'bed' },
|
|
44
|
+
{ label: 'Guests', href: '#guests', icon: 'users' },
|
|
45
|
+
] },
|
|
46
|
+
{ label: 'Insights', items: [
|
|
47
|
+
{ label: 'Reports', href: '#reports', icon: 'chart' },
|
|
48
|
+
{ label: 'Revenue', href: '#revenue', icon: 'wallet' },
|
|
49
|
+
] },
|
|
50
|
+
{ label: 'Account', items: [{ label: 'Settings', href: '#settings', icon: 'settings' }] },
|
|
51
|
+
],
|
|
52
|
+
currentHref = '#',
|
|
53
|
+
promo = { title: 'Channel manager', text: '3 of 5 channels connected', href: '#', cta: 'Connect more', progress: 60 },
|
|
54
|
+
fullHeight = true,
|
|
55
|
+
collapsible = true,
|
|
56
|
+
variant = 'surface',
|
|
57
|
+
size = 'md',
|
|
58
|
+
id = 'app-sidebar',
|
|
59
|
+
className = '',
|
|
60
|
+
} = Astro.props;
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
<aside class:list={['app-sb', `app-sb--${variant}`, `app-sb--${size}`, { 'app-sb--inline': !fullHeight }, className]} id={id} data-app-sidebar aria-label="Application">
|
|
64
|
+
<div class="app-sb__top">
|
|
65
|
+
<a class="app-sb__ws" href={groups[0]?.items[0]?.href ?? '#'}>
|
|
66
|
+
<span class="app-sb__ws-logo" aria-hidden="true">{workspace.initials ?? workspace.name.slice(0, 2).toUpperCase()}</span>
|
|
67
|
+
<span class="app-sb__ws-text app-sb__label">
|
|
68
|
+
<strong>{workspace.name}</strong>
|
|
69
|
+
{workspace.plan && <small>{workspace.plan}</small>}
|
|
70
|
+
</span>
|
|
71
|
+
</a>
|
|
72
|
+
{collapsible && (
|
|
73
|
+
<button class="app-sb__collapse" type="button" aria-label="Collapse sidebar" aria-pressed="false" data-collapse>
|
|
74
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v18"/></svg>
|
|
75
|
+
</button>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<nav class="app-sb__nav" aria-label="Main">
|
|
80
|
+
{groups.map((g) => (
|
|
81
|
+
<div class="app-sb__group">
|
|
82
|
+
{g.label && <p class="app-sb__group-label app-sb__label">{g.label}</p>}
|
|
83
|
+
<ul>
|
|
84
|
+
{g.items.map((item) => {
|
|
85
|
+
const active = item.href === currentHref;
|
|
86
|
+
return (
|
|
87
|
+
<li>
|
|
88
|
+
<a class:list={['app-sb__link', { 'is-active': active }]} href={item.href} aria-current={active ? 'page' : undefined} data-tip={item.label}>
|
|
89
|
+
<span class="app-sb__icon" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><Fragment set:html={appIcon(item.icon)} /></svg></span>
|
|
90
|
+
<span class="app-sb__text app-sb__label">{item.label}</span>
|
|
91
|
+
{item.badge !== undefined && <span class="app-sb__badge app-sb__label">{item.badge}</span>}
|
|
92
|
+
</a>
|
|
93
|
+
</li>
|
|
94
|
+
);
|
|
95
|
+
})}
|
|
96
|
+
</ul>
|
|
97
|
+
</div>
|
|
98
|
+
))}
|
|
99
|
+
</nav>
|
|
100
|
+
|
|
101
|
+
{promo && (
|
|
102
|
+
<a class="app-sb__promo app-sb__label" href={promo.href}>
|
|
103
|
+
<strong>{promo.title}</strong>
|
|
104
|
+
{promo.text && <span>{promo.text}</span>}
|
|
105
|
+
{promo.progress !== undefined && <span class="app-sb__meter" aria-hidden="true"><i style={`width: ${promo.progress}%`}></i></span>}
|
|
106
|
+
{promo.cta && <em>{promo.cta} →</em>}
|
|
107
|
+
</a>
|
|
108
|
+
)}
|
|
109
|
+
</aside>
|
|
110
|
+
<div class="app-sb-backdrop" data-sidebar-backdrop hidden></div>
|
|
111
|
+
|
|
112
|
+
<script>
|
|
113
|
+
document.querySelectorAll<HTMLElement>('[data-app-sidebar]:not([data-bound])').forEach((sb) => {
|
|
114
|
+
sb.dataset.bound = '';
|
|
115
|
+
const btn = sb.querySelector<HTMLButtonElement>('[data-collapse]');
|
|
116
|
+
const key = `${sb.id}:collapsed`;
|
|
117
|
+
const set = (on: boolean) => {
|
|
118
|
+
sb.classList.toggle('is-collapsed', on);
|
|
119
|
+
btn?.setAttribute('aria-pressed', String(on));
|
|
120
|
+
btn?.setAttribute('aria-label', on ? 'Expand sidebar' : 'Collapse sidebar');
|
|
121
|
+
};
|
|
122
|
+
try { set(window.localStorage.getItem(key) === '1'); } catch { /* ignore */ }
|
|
123
|
+
btn?.addEventListener('click', () => {
|
|
124
|
+
const on = !sb.classList.contains('is-collapsed');
|
|
125
|
+
set(on);
|
|
126
|
+
try { window.localStorage.setItem(key, on ? '1' : '0'); } catch { /* ignore */ }
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Mobile drawer: opened by any [data-sidebar-open="<id>"] button (AppTopbar's menu button).
|
|
130
|
+
const backdrop = sb.nextElementSibling as HTMLElement | null;
|
|
131
|
+
const open = (v: boolean) => {
|
|
132
|
+
sb.classList.toggle('is-open', v);
|
|
133
|
+
if (backdrop) backdrop.hidden = !v;
|
|
134
|
+
document.querySelectorAll(`[data-sidebar-open="${sb.id}"]`).forEach((b) => b.setAttribute('aria-expanded', String(v)));
|
|
135
|
+
if (v) sb.querySelector<HTMLElement>('a, button')?.focus();
|
|
136
|
+
};
|
|
137
|
+
document.querySelectorAll<HTMLElement>(`[data-sidebar-open="${sb.id}"]`).forEach((b) => b.addEventListener('click', () => open(!sb.classList.contains('is-open'))));
|
|
138
|
+
backdrop?.addEventListener('click', () => open(false));
|
|
139
|
+
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && sb.classList.contains('is-open')) open(false); });
|
|
140
|
+
});
|
|
141
|
+
</script>
|
|
142
|
+
|
|
143
|
+
<style>
|
|
144
|
+
.app-sb {
|
|
145
|
+
--sb-w: 252px;
|
|
146
|
+
position: sticky;
|
|
147
|
+
top: 0;
|
|
148
|
+
display: flex;
|
|
149
|
+
flex-direction: column;
|
|
150
|
+
gap: var(--sp-4, 1rem);
|
|
151
|
+
width: var(--sb-w);
|
|
152
|
+
height: 100dvh;
|
|
153
|
+
padding: var(--sp-4, 1rem) var(--sp-3, 0.75rem);
|
|
154
|
+
border-right: 1px solid var(--c-border, rgba(255, 255, 255, 0.08));
|
|
155
|
+
background: var(--c-bg-1, #0a0a0a);
|
|
156
|
+
overflow-y: auto;
|
|
157
|
+
transition: width 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
|
158
|
+
z-index: 40;
|
|
159
|
+
}
|
|
160
|
+
.app-sb--inline { position: relative; height: auto; min-height: 520px; border: 1px solid var(--c-border, rgba(255, 255, 255, 0.08)); border-radius: var(--r-lg, 16px); }
|
|
161
|
+
.app-sb--sm { --sb-w: 220px; }
|
|
162
|
+
.app-sb--lg { --sb-w: 280px; }
|
|
163
|
+
.app-sb--transparent { background: transparent; }
|
|
164
|
+
.app-sb--accent { background: var(--app-accent, var(--c-primary-light, #d4a017)); --c-text-1: #fff; --c-text-2: rgba(255, 255, 255, 0.8); }
|
|
165
|
+
.app-sb.is-collapsed { --sb-w: 72px; }
|
|
166
|
+
.app-sb.is-collapsed .app-sb__label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
167
|
+
.app-sb.is-collapsed .app-sb__top { flex-direction: column; }
|
|
168
|
+
|
|
169
|
+
.app-sb__top { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2, 0.5rem); }
|
|
170
|
+
.app-sb__ws { display: flex; align-items: center; gap: var(--sp-3, 0.75rem); min-width: 0; padding: 4px; border-radius: var(--r-md, 12px); color: var(--c-text-1, #fff); text-decoration: none; }
|
|
171
|
+
.app-sb__ws:hover { background: rgb(var(--ink-rgb, 255 255 255) / 0.05); color: var(--c-text-1, #fff); }
|
|
172
|
+
.app-sb__ws-logo { display: grid; place-items: center; flex-shrink: 0; width: 36px; height: 36px; border-radius: 10px; background: var(--app-accent, var(--c-primary-light, #d4a017)); color: var(--c-on-primary, #050505); font-size: 0.8rem; font-weight: 800; letter-spacing: 0.02em; }
|
|
173
|
+
.app-sb__ws-text { display: grid; min-width: 0; line-height: 1.2; }
|
|
174
|
+
.app-sb__ws-text strong { font-size: 0.9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
175
|
+
.app-sb__ws-text small { font-size: 0.72rem; color: var(--c-text-2, #a3a3a3); }
|
|
176
|
+
.app-sb__collapse { display: grid; place-items: center; flex-shrink: 0; width: 36px; height: 36px; border: 0; border-radius: 10px; background: transparent; color: var(--c-text-2, #a3a3a3); cursor: pointer; }
|
|
177
|
+
.app-sb__collapse:hover { background: rgb(var(--ink-rgb, 255 255 255) / 0.06); color: var(--c-text-1, #fff); }
|
|
178
|
+
|
|
179
|
+
.app-sb__nav { display: grid; gap: var(--sp-4, 1rem); }
|
|
180
|
+
.app-sb__group ul { display: grid; gap: 2px; margin: 0; padding: 0; list-style: none; }
|
|
181
|
+
.app-sb__group-label { margin: 0 0 var(--sp-1, 0.25rem); padding: 0 var(--sp-3, 0.75rem); font-size: 0.68rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--c-text-3, #737373); }
|
|
182
|
+
.app-sb__link {
|
|
183
|
+
position: relative;
|
|
184
|
+
display: flex;
|
|
185
|
+
align-items: center;
|
|
186
|
+
gap: var(--sp-3, 0.75rem);
|
|
187
|
+
min-height: 40px;
|
|
188
|
+
padding: 0 var(--sp-3, 0.75rem);
|
|
189
|
+
border-radius: 10px;
|
|
190
|
+
color: var(--c-text-2, #a3a3a3);
|
|
191
|
+
font-size: 0.88rem;
|
|
192
|
+
font-weight: 500;
|
|
193
|
+
text-decoration: none;
|
|
194
|
+
transition: background 0.15s ease, color 0.15s ease;
|
|
195
|
+
}
|
|
196
|
+
.app-sb__link:hover { background: rgb(var(--ink-rgb, 255 255 255) / 0.05); color: var(--c-text-1, #fff); }
|
|
197
|
+
.app-sb__link.is-active { background: color-mix(in srgb, var(--app-accent, var(--c-primary-light, #d4a017)) 14%, transparent); color: var(--c-text-1, #fff); font-weight: 600; }
|
|
198
|
+
.app-sb__link.is-active .app-sb__icon { color: var(--app-accent, var(--c-primary-light, #d4a017)); }
|
|
199
|
+
.app-sb__icon { display: grid; place-items: center; flex-shrink: 0; width: 20px; }
|
|
200
|
+
.app-sb__text { flex: 1; white-space: nowrap; }
|
|
201
|
+
.app-sb__badge { min-width: 22px; padding: 1px 7px; border-radius: 999px; background: rgb(var(--ink-rgb, 255 255 255) / 0.08); color: var(--c-text-1, #fff); font-size: 0.7rem; font-weight: 700; text-align: center; }
|
|
202
|
+
.app-sb__link.is-active .app-sb__badge { background: var(--app-accent, var(--c-primary-light, #d4a017)); color: var(--c-on-primary, #050505); }
|
|
203
|
+
.app-sb.is-collapsed .app-sb__link { justify-content: center; padding: 0; }
|
|
204
|
+
.app-sb.is-collapsed .app-sb__link:is(:hover, :focus-visible)::after {
|
|
205
|
+
content: attr(data-tip);
|
|
206
|
+
position: absolute;
|
|
207
|
+
left: calc(100% + 10px);
|
|
208
|
+
top: 50%;
|
|
209
|
+
transform: translateY(-50%);
|
|
210
|
+
padding: 4px 8px;
|
|
211
|
+
border-radius: 6px;
|
|
212
|
+
background: var(--c-text-1, #fff);
|
|
213
|
+
color: var(--c-bg, #050505);
|
|
214
|
+
font-size: 0.75rem;
|
|
215
|
+
white-space: nowrap;
|
|
216
|
+
z-index: 5;
|
|
217
|
+
}
|
|
218
|
+
.app-sb :is(a, button):focus-visible { outline: 2px solid var(--app-accent, var(--c-primary-light, #d4a017)); outline-offset: 2px; }
|
|
219
|
+
|
|
220
|
+
.app-sb__promo {
|
|
221
|
+
display: grid;
|
|
222
|
+
gap: 6px;
|
|
223
|
+
margin-top: auto;
|
|
224
|
+
padding: var(--sp-4, 1rem);
|
|
225
|
+
border-radius: 14px;
|
|
226
|
+
border: 1px solid var(--c-border, rgba(255, 255, 255, 0.08));
|
|
227
|
+
background: rgb(var(--ink-rgb, 255 255 255) / 0.03);
|
|
228
|
+
color: var(--c-text-2, #a3a3a3);
|
|
229
|
+
font-size: 0.8rem;
|
|
230
|
+
text-decoration: none;
|
|
231
|
+
}
|
|
232
|
+
.app-sb__promo strong { color: var(--c-text-1, #fff); font-size: 0.85rem; }
|
|
233
|
+
.app-sb__promo em { font-style: normal; font-weight: 600; color: var(--app-accent, var(--c-primary-light, #d4a017)); }
|
|
234
|
+
.app-sb__meter { height: 6px; border-radius: 999px; background: rgb(var(--ink-rgb, 255 255 255) / 0.08); overflow: hidden; }
|
|
235
|
+
.app-sb__meter i { display: block; height: 100%; border-radius: inherit; background: var(--app-accent, var(--c-primary-light, #d4a017)); }
|
|
236
|
+
.app-sb.is-collapsed .app-sb__promo { display: none; }
|
|
237
|
+
|
|
238
|
+
.app-sb-backdrop { position: fixed; inset: 0; z-index: 39; background: rgba(0, 0, 0, 0.5); }
|
|
239
|
+
.app-sb-backdrop[hidden] { display: none; }
|
|
240
|
+
|
|
241
|
+
@media (max-width: 960px) {
|
|
242
|
+
.app-sb:not(.app-sb--inline) { position: fixed; left: 0; top: 0; transform: translateX(-100%); transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); --sb-w: 280px; box-shadow: 20px 0 60px -20px rgba(0, 0, 0, 0.5); }
|
|
243
|
+
.app-sb.is-open { transform: none; }
|
|
244
|
+
.app-sb.is-collapsed { --sb-w: 280px; }
|
|
245
|
+
.app-sb.is-collapsed .app-sb__label { position: static; width: auto; height: auto; clip: auto; }
|
|
246
|
+
.app-sb__collapse { display: none; }
|
|
247
|
+
}
|
|
248
|
+
@media (prefers-reduced-motion: reduce) { .app-sb { transition: none; } }
|
|
249
|
+
</style>
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* AppTopbar — SaaS app top bar: mobile menu button, search (⌘K / Ctrl+K), notifications dropdown
|
|
4
|
+
* with unread count and "mark all read", light/dark toggle and a user menu.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} searchPlaceholder - Search placeholder. Default 'Search guests, bookings, rooms…'.
|
|
7
|
+
* @param {string} searchAction - Search form action. Default '#'.
|
|
8
|
+
* @param {Array<{title: string, text?: string, time: string, unread?: boolean, tone?: 'info'|'success'|'warning'|'danger'}>} notifications - Dropdown items.
|
|
9
|
+
* @param {{name: string, role?: string, email?: string}} user - Signed-in user.
|
|
10
|
+
* @param {Array<{label: string, href: string}>} userLinks - User menu links.
|
|
11
|
+
* @param {string} signOutHref - Sign-out link. Default '#'.
|
|
12
|
+
* @param {string} sidebarId - id of the AppSidebar the menu button opens on mobile. Default 'app-sidebar'.
|
|
13
|
+
* @param {boolean} themeToggle - Show the light/dark toggle. Default true.
|
|
14
|
+
* @param {string} variant - Style (blur|solid|transparent). Default 'blur'.
|
|
15
|
+
* @param {string} className - Extra class.
|
|
16
|
+
*
|
|
17
|
+
* Slots: `start` (e.g. page title or breadcrumb next to the menu button), `actions` (extra buttons).
|
|
18
|
+
*
|
|
19
|
+
* Accessibility: dropdowns are disclosure buttons closed with Escape or an outside click;
|
|
20
|
+
* the unread count is part of the bell's label; ⌘K moves focus to the search field.
|
|
21
|
+
*/
|
|
22
|
+
import { appIcon, initials, hueFrom } from './app-icons';
|
|
23
|
+
|
|
24
|
+
interface Notice { title: string; text?: string; time: string; unread?: boolean; tone?: 'info' | 'success' | 'warning' | 'danger' }
|
|
25
|
+
interface Props {
|
|
26
|
+
searchPlaceholder?: string;
|
|
27
|
+
searchAction?: string;
|
|
28
|
+
notifications?: Notice[];
|
|
29
|
+
user?: { name: string; role?: string; email?: string };
|
|
30
|
+
userLinks?: Array<{ label: string; href: string }>;
|
|
31
|
+
signOutHref?: string;
|
|
32
|
+
sidebarId?: string;
|
|
33
|
+
themeToggle?: boolean;
|
|
34
|
+
variant?: 'blur' | 'solid' | 'transparent';
|
|
35
|
+
className?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const {
|
|
39
|
+
searchPlaceholder = 'Search guests, bookings, rooms…',
|
|
40
|
+
searchAction = '#',
|
|
41
|
+
notifications = [
|
|
42
|
+
{ title: 'New booking from Booking.com', text: 'Deluxe King · 3 nights · €612', time: '2 min ago', unread: true, tone: 'success' },
|
|
43
|
+
{ title: 'Room 204 reported maintenance', text: 'Air conditioning not cooling', time: '18 min ago', unread: true, tone: 'warning' },
|
|
44
|
+
{ title: 'Payout sent', text: '€18,240 to your bank account', time: '1 h ago', tone: 'info' },
|
|
45
|
+
],
|
|
46
|
+
user = { name: 'Lucía Ortega', role: 'General manager', email: 'lucia@mirador.com' },
|
|
47
|
+
userLinks = [{ label: 'Your profile', href: '#' }, { label: 'Settings', href: '#' }, { label: 'Billing', href: '#' }],
|
|
48
|
+
signOutHref = '#',
|
|
49
|
+
sidebarId = 'app-sidebar',
|
|
50
|
+
themeToggle = true,
|
|
51
|
+
variant = 'blur',
|
|
52
|
+
className = '',
|
|
53
|
+
} = Astro.props;
|
|
54
|
+
|
|
55
|
+
const unread = notifications.filter((n) => n.unread).length;
|
|
56
|
+
const hue = hueFrom(user.name);
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
<header class:list={['app-tb', `app-tb--${variant}`, className]} data-app-topbar>
|
|
60
|
+
<button class="app-tb__icon app-tb__menu" type="button" aria-label="Open navigation" aria-controls={sidebarId} aria-expanded="false" data-sidebar-open={sidebarId}>
|
|
61
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><Fragment set:html={appIcon('menu')} /></svg>
|
|
62
|
+
</button>
|
|
63
|
+
<div class="app-tb__start"><slot name="start" /></div>
|
|
64
|
+
|
|
65
|
+
<form class="app-tb__search" role="search" action={searchAction}>
|
|
66
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><Fragment set:html={appIcon('search')} /></svg>
|
|
67
|
+
<label class="sr-only" for="app-tb-q">{searchPlaceholder}</label>
|
|
68
|
+
<input id="app-tb-q" name="q" type="search" placeholder={searchPlaceholder} autocomplete="off" data-search />
|
|
69
|
+
<kbd aria-hidden="true">⌘K</kbd>
|
|
70
|
+
</form>
|
|
71
|
+
|
|
72
|
+
<div class="app-tb__actions">
|
|
73
|
+
<slot name="actions" />
|
|
74
|
+
{themeToggle && (
|
|
75
|
+
<button class="app-tb__icon" type="button" aria-label="Switch theme" data-theme-toggle>
|
|
76
|
+
<svg class="i-sun" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><Fragment set:html={appIcon('sun')} /></svg>
|
|
77
|
+
<svg class="i-moon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><Fragment set:html={appIcon('moon')} /></svg>
|
|
78
|
+
</button>
|
|
79
|
+
)}
|
|
80
|
+
|
|
81
|
+
<div class="app-tb__menu-wrap">
|
|
82
|
+
<button class="app-tb__icon" type="button" aria-expanded="false" aria-controls="app-tb-notes" aria-label={unread ? `Notifications, ${unread} unread` : 'Notifications'} data-dd>
|
|
83
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><Fragment set:html={appIcon('bell')} /></svg>
|
|
84
|
+
{unread > 0 && <span class="app-tb__dot" aria-hidden="true" data-unread>{unread}</span>}
|
|
85
|
+
</button>
|
|
86
|
+
<div class="app-tb__panel app-tb__panel--notes" id="app-tb-notes" hidden data-panel>
|
|
87
|
+
<div class="app-tb__panel-head">
|
|
88
|
+
<strong>Notifications</strong>
|
|
89
|
+
<button type="button" class="app-tb__link" data-mark-read>Mark all as read</button>
|
|
90
|
+
</div>
|
|
91
|
+
<ul>
|
|
92
|
+
{notifications.map((n) => (
|
|
93
|
+
<li class:list={['app-tb__note', `app-tb__note--${n.tone ?? 'info'}`, { 'is-unread': n.unread }]}>
|
|
94
|
+
<span class="app-tb__note-dot" aria-hidden="true"></span>
|
|
95
|
+
<span class="app-tb__note-body">
|
|
96
|
+
<strong>{n.title}</strong>
|
|
97
|
+
{n.text && <span>{n.text}</span>}
|
|
98
|
+
<time>{n.time}</time>
|
|
99
|
+
</span>
|
|
100
|
+
</li>
|
|
101
|
+
))}
|
|
102
|
+
</ul>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div class="app-tb__menu-wrap">
|
|
107
|
+
<button class="app-tb__user" type="button" aria-expanded="false" aria-controls="app-tb-user" data-dd>
|
|
108
|
+
<span class="app-tb__avatar" style={`--h: ${hue}`} aria-hidden="true">{initials(user.name)}</span>
|
|
109
|
+
<span class="app-tb__who"><strong>{user.name}</strong>{user.role && <small>{user.role}</small>}</span>
|
|
110
|
+
<span class="sr-only">Account menu</span>
|
|
111
|
+
</button>
|
|
112
|
+
<div class="app-tb__panel app-tb__panel--user" id="app-tb-user" hidden data-panel>
|
|
113
|
+
<div class="app-tb__panel-head app-tb__panel-head--user">
|
|
114
|
+
<strong>{user.name}</strong>
|
|
115
|
+
{user.email && <span>{user.email}</span>}
|
|
116
|
+
</div>
|
|
117
|
+
<ul>
|
|
118
|
+
{userLinks.map((l) => <li><a href={l.href}>{l.label}</a></li>)}
|
|
119
|
+
<li class="app-tb__sep"><a href={signOutHref}>
|
|
120
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><Fragment set:html={appIcon('logout')} /></svg>
|
|
121
|
+
Sign out</a></li>
|
|
122
|
+
</ul>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</header>
|
|
127
|
+
|
|
128
|
+
<script>
|
|
129
|
+
document.querySelectorAll<HTMLElement>('[data-app-topbar]:not([data-bound])').forEach((bar) => {
|
|
130
|
+
bar.dataset.bound = '';
|
|
131
|
+
const toggles = Array.from(bar.querySelectorAll<HTMLButtonElement>('[data-dd]'));
|
|
132
|
+
const panel = (b: HTMLButtonElement) => document.getElementById(b.getAttribute('aria-controls') || '');
|
|
133
|
+
const closeAll = (except?: HTMLButtonElement) => toggles.forEach((b) => {
|
|
134
|
+
if (b === except) return;
|
|
135
|
+
b.setAttribute('aria-expanded', 'false');
|
|
136
|
+
const p = panel(b);
|
|
137
|
+
if (p) p.hidden = true;
|
|
138
|
+
});
|
|
139
|
+
toggles.forEach((b) => b.addEventListener('click', (e) => {
|
|
140
|
+
e.stopPropagation();
|
|
141
|
+
const open = b.getAttribute('aria-expanded') !== 'true';
|
|
142
|
+
closeAll(b);
|
|
143
|
+
b.setAttribute('aria-expanded', String(open));
|
|
144
|
+
const p = panel(b);
|
|
145
|
+
if (p) p.hidden = !open;
|
|
146
|
+
}));
|
|
147
|
+
document.addEventListener('click', (e) => { if (!bar.contains(e.target as Node)) closeAll(); });
|
|
148
|
+
document.addEventListener('keydown', (e) => {
|
|
149
|
+
if (e.key === 'Escape') {
|
|
150
|
+
const open = toggles.find((b) => b.getAttribute('aria-expanded') === 'true');
|
|
151
|
+
closeAll();
|
|
152
|
+
open?.focus();
|
|
153
|
+
}
|
|
154
|
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
bar.querySelector<HTMLInputElement>('[data-search]')?.focus();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
bar.querySelector('[data-mark-read]')?.addEventListener('click', () => {
|
|
161
|
+
bar.querySelectorAll('.app-tb__note.is-unread').forEach((n) => n.classList.remove('is-unread'));
|
|
162
|
+
bar.querySelector('[data-unread]')?.remove();
|
|
163
|
+
bar.querySelector('[aria-controls="app-tb-notes"]')?.setAttribute('aria-label', 'Notifications');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
bar.querySelector('[data-theme-toggle]')?.addEventListener('click', () => {
|
|
167
|
+
const root = document.documentElement;
|
|
168
|
+
const next = root.dataset.theme === 'light' ? 'dark' : 'light';
|
|
169
|
+
root.dataset.theme = next;
|
|
170
|
+
try { window.localStorage.setItem('ack-theme', next); } catch { /* ignore */ }
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
</script>
|
|
174
|
+
|
|
175
|
+
<style>
|
|
176
|
+
.app-tb {
|
|
177
|
+
position: sticky;
|
|
178
|
+
top: 0;
|
|
179
|
+
z-index: 30;
|
|
180
|
+
display: flex;
|
|
181
|
+
align-items: center;
|
|
182
|
+
gap: var(--sp-3, 0.75rem);
|
|
183
|
+
min-height: 64px;
|
|
184
|
+
padding: 0 var(--sp-6, 1.5rem);
|
|
185
|
+
border-bottom: 1px solid var(--c-border, rgba(255, 255, 255, 0.08));
|
|
186
|
+
}
|
|
187
|
+
.app-tb--blur { background: rgb(var(--paper-rgb, 5 5 5) / 0.8); backdrop-filter: blur(14px) saturate(1.3); }
|
|
188
|
+
.app-tb--solid { background: var(--c-bg-1, #0a0a0a); }
|
|
189
|
+
.app-tb--transparent { background: transparent; border-bottom-color: transparent; }
|
|
190
|
+
.app-tb__start { min-width: 0; }
|
|
191
|
+
.app-tb__start:empty { display: none; }
|
|
192
|
+
|
|
193
|
+
.app-tb__search {
|
|
194
|
+
position: relative;
|
|
195
|
+
display: flex;
|
|
196
|
+
align-items: center;
|
|
197
|
+
gap: var(--sp-2, 0.5rem);
|
|
198
|
+
flex: 1;
|
|
199
|
+
max-width: 440px;
|
|
200
|
+
height: 40px;
|
|
201
|
+
padding: 0 var(--sp-3, 0.75rem);
|
|
202
|
+
border-radius: 10px;
|
|
203
|
+
border: 1px solid var(--c-border, rgba(255, 255, 255, 0.08));
|
|
204
|
+
background: rgb(var(--ink-rgb, 255 255 255) / 0.03);
|
|
205
|
+
color: var(--c-text-2, #a3a3a3);
|
|
206
|
+
}
|
|
207
|
+
.app-tb__search:focus-within { border-color: var(--app-accent, var(--c-primary-light, #d4a017)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--app-accent, var(--c-primary-light, #d4a017)) 18%, transparent); }
|
|
208
|
+
.app-tb__search input { flex: 1; min-width: 0; border: 0; outline: 0; background: none; color: var(--c-text-1, #fff); font: inherit; font-size: 0.85rem; }
|
|
209
|
+
.app-tb__search input::placeholder { color: var(--c-text-3, #737373); }
|
|
210
|
+
.app-tb__search kbd { padding: 1px 6px; border-radius: 6px; border: 1px solid var(--c-border, rgba(255, 255, 255, 0.1)); font-family: var(--font-mono, monospace); font-size: 0.7rem; color: var(--c-text-3, #737373); }
|
|
211
|
+
|
|
212
|
+
.app-tb__actions { display: flex; align-items: center; gap: var(--sp-1, 0.25rem); margin-left: auto; }
|
|
213
|
+
.app-tb__icon {
|
|
214
|
+
position: relative;
|
|
215
|
+
display: grid;
|
|
216
|
+
place-items: center;
|
|
217
|
+
width: 40px;
|
|
218
|
+
height: 40px;
|
|
219
|
+
border: 0;
|
|
220
|
+
border-radius: 10px;
|
|
221
|
+
background: transparent;
|
|
222
|
+
color: var(--c-text-2, #a3a3a3);
|
|
223
|
+
cursor: pointer;
|
|
224
|
+
}
|
|
225
|
+
.app-tb__icon:hover, .app-tb__icon[aria-expanded='true'] { background: rgb(var(--ink-rgb, 255 255 255) / 0.06); color: var(--c-text-1, #fff); }
|
|
226
|
+
.app-tb__menu { display: none; }
|
|
227
|
+
.app-tb .i-moon { display: none; }
|
|
228
|
+
:global([data-theme='light']) .app-tb .i-sun { display: none; }
|
|
229
|
+
:global([data-theme='light']) .app-tb .i-moon { display: block; }
|
|
230
|
+
.app-tb__dot { position: absolute; top: 4px; right: 4px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px; background: var(--c-danger, #f87171); color: #fff; font-size: 0.62rem; font-weight: 800; line-height: 16px; text-align: center; box-shadow: 0 0 0 2px var(--c-bg-1, #0a0a0a); }
|
|
231
|
+
|
|
232
|
+
.app-tb__menu-wrap { position: relative; }
|
|
233
|
+
.app-tb__user { display: flex; align-items: center; gap: var(--sp-2, 0.5rem); min-height: 44px; padding: 0 var(--sp-2, 0.5rem) 0 4px; margin-left: var(--sp-1, 0.25rem); border: 0; border-radius: 999px; background: transparent; color: var(--c-text-1, #fff); font: inherit; cursor: pointer; text-align: left; }
|
|
234
|
+
.app-tb__user:hover, .app-tb__user[aria-expanded='true'] { background: rgb(var(--ink-rgb, 255 255 255) / 0.06); }
|
|
235
|
+
.app-tb__avatar { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%; background: hsl(var(--h) 55% 45%); color: #fff; font-size: 0.75rem; font-weight: 700; }
|
|
236
|
+
.app-tb__who { display: grid; line-height: 1.2; }
|
|
237
|
+
.app-tb__who strong { font-size: 0.82rem; }
|
|
238
|
+
.app-tb__who small { font-size: 0.7rem; color: var(--c-text-2, #a3a3a3); }
|
|
239
|
+
.app-tb :is(a, button, input):focus-visible { outline: 2px solid var(--app-accent, var(--c-primary-light, #d4a017)); outline-offset: 2px; }
|
|
240
|
+
|
|
241
|
+
.app-tb__panel {
|
|
242
|
+
position: absolute;
|
|
243
|
+
right: 0;
|
|
244
|
+
top: calc(100% + 8px);
|
|
245
|
+
width: 340px;
|
|
246
|
+
max-width: calc(100vw - 24px);
|
|
247
|
+
border-radius: 14px;
|
|
248
|
+
border: 1px solid var(--c-border, rgba(255, 255, 255, 0.1));
|
|
249
|
+
background: var(--c-bg-1, #0a0a0a);
|
|
250
|
+
box-shadow: 0 24px 60px -20px rgba(0, 0, 0, 0.5);
|
|
251
|
+
overflow: hidden;
|
|
252
|
+
animation: tb-pop 0.16s ease-out;
|
|
253
|
+
}
|
|
254
|
+
.app-tb__panel--user { width: 240px; }
|
|
255
|
+
.app-tb__panel[hidden] { display: none; }
|
|
256
|
+
@keyframes tb-pop { from { opacity: 0; transform: translateY(-4px); } }
|
|
257
|
+
.app-tb__panel-head { display: flex; align-items: center; justify-content: space-between; padding: var(--sp-3, 0.75rem) var(--sp-4, 1rem); border-bottom: 1px solid var(--c-border, rgba(255, 255, 255, 0.08)); color: var(--c-text-1, #fff); font-size: 0.88rem; }
|
|
258
|
+
.app-tb__panel-head--user { display: grid; gap: 2px; }
|
|
259
|
+
.app-tb__panel-head--user span { font-size: 0.78rem; color: var(--c-text-2, #a3a3a3); }
|
|
260
|
+
.app-tb__link { border: 0; background: none; color: var(--app-accent, var(--c-primary-light, #d4a017)); font: inherit; font-size: 0.78rem; font-weight: 600; cursor: pointer; min-height: 32px; }
|
|
261
|
+
.app-tb__panel ul { margin: 0; padding: 4px; list-style: none; }
|
|
262
|
+
.app-tb__note { display: flex; gap: var(--sp-3, 0.75rem); padding: var(--sp-3, 0.75rem); border-radius: 10px; }
|
|
263
|
+
.app-tb__note:hover { background: rgb(var(--ink-rgb, 255 255 255) / 0.04); }
|
|
264
|
+
.app-tb__note-dot { flex-shrink: 0; width: 8px; height: 8px; margin-top: 6px; border-radius: 50%; background: transparent; }
|
|
265
|
+
.app-tb__note.is-unread .app-tb__note-dot { background: var(--tone, var(--app-accent, var(--c-primary-light, #d4a017))); }
|
|
266
|
+
.app-tb__note--success { --tone: var(--c-success, #34d399); }
|
|
267
|
+
.app-tb__note--warning { --tone: var(--c-warning, #fbbf24); }
|
|
268
|
+
.app-tb__note--danger { --tone: var(--c-danger, #f87171); }
|
|
269
|
+
.app-tb__note-body { display: grid; gap: 2px; font-size: 0.8rem; color: var(--c-text-2, #a3a3a3); }
|
|
270
|
+
.app-tb__note-body strong { font-size: 0.85rem; color: var(--c-text-1, #fff); font-weight: 600; }
|
|
271
|
+
.app-tb__note-body time { font-size: 0.72rem; color: var(--c-text-3, #737373); }
|
|
272
|
+
.app-tb__panel--user a { display: flex; align-items: center; gap: var(--sp-2, 0.5rem); min-height: 40px; padding: 0 var(--sp-3, 0.75rem); border-radius: 8px; color: var(--c-text-1, #fff); font-size: 0.85rem; text-decoration: none; }
|
|
273
|
+
.app-tb__panel--user a:hover { background: rgb(var(--ink-rgb, 255 255 255) / 0.05); color: var(--c-text-1, #fff); }
|
|
274
|
+
.app-tb__sep { margin-top: 4px; padding-top: 4px; border-top: 1px solid var(--c-border, rgba(255, 255, 255, 0.08)); }
|
|
275
|
+
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
276
|
+
|
|
277
|
+
@media (max-width: 960px) { .app-tb__menu { display: grid; } }
|
|
278
|
+
@media (max-width: 720px) {
|
|
279
|
+
.app-tb { padding: 0 var(--sp-3, 0.75rem); }
|
|
280
|
+
.app-tb__search kbd, .app-tb__who { display: none; }
|
|
281
|
+
}
|
|
282
|
+
@media (max-width: 480px) { .app-tb__search { display: none; } }
|
|
283
|
+
@media (prefers-reduced-motion: reduce) { .app-tb__panel { animation: none; } }
|
|
284
|
+
</style>
|