mirror of
https://github.com/AnmolSaini16/mapcn
synced 2026-04-25 16:14:54 +02:00
Refine popup and controls
This commit is contained in:
92
AGENTS.md
Normal file
92
AGENTS.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
**mapcn** is a free, open-source library of ready-to-use, customizable map components for React. Built on [MapLibre GL](https://maplibre.org/), styled with [Tailwind CSS](https://tailwindcss.com/), works seamlessly with [shadcn/ui](https://ui.shadcn.com/).
|
||||||
|
|
||||||
|
The repo serves dual purpose: the core map component registry (`src/registry/`) and the documentation site at [mapcn.dev](https://mapcn.dev).
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **Framework**: Next.js 16 (App Router, React 19)
|
||||||
|
- **Language**: TypeScript (strict)
|
||||||
|
- **Styling**: Tailwind CSS v4, shadcn/ui, Radix UI
|
||||||
|
- **Map Engine**: MapLibre GL JS v5
|
||||||
|
- **Fonts**: Geist (sans) + Geist Mono
|
||||||
|
- **Code Highlighting**: Shiki (dual light/dark theme)
|
||||||
|
- **Registry**: shadcn CLI for building distributable components
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── app/
|
||||||
|
│ ├── layout.tsx # Root layout (fonts, metadata, providers)
|
||||||
|
│ ├── (main)/ # Main layout group (header + content)
|
||||||
|
│ │ ├── layout.tsx # Header wrapper
|
||||||
|
│ │ ├── (home)/ # Landing page
|
||||||
|
│ │ │ └── _components/ # Homepage examples grid
|
||||||
|
│ │ ├── docs/ # Documentation pages
|
||||||
|
│ │ │ └── _components/ # Docs layout, sidebar, TOC, code blocks
|
||||||
|
│ │ └── blocks/ # Full-page block demos
|
||||||
|
│ └── (view)/ # Standalone block viewer
|
||||||
|
├── components/ # Shared app components (header, footer, logo, nav)
|
||||||
|
│ └── ui/ # shadcn/ui primitives
|
||||||
|
├── registry/ # Core map components (the library itself)
|
||||||
|
│ ├── map.tsx # Main map component + all sub-components
|
||||||
|
│ └── blocks/ # Full-page block examples
|
||||||
|
├── lib/ # Utilities, navigation config, helpers
|
||||||
|
└── styles/
|
||||||
|
└── globals.css # Theme tokens, animations, base styles
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Conventions
|
||||||
|
|
||||||
|
### Components
|
||||||
|
|
||||||
|
- Use functional components with named exports
|
||||||
|
- Prefer React Server Components; use `"use client"` only when Web APIs are needed
|
||||||
|
- Structure files: exported component → subcomponents → helpers → static content → types
|
||||||
|
- Use `cn()` from `@/lib/utils` for conditional class merging
|
||||||
|
|
||||||
|
### Styling
|
||||||
|
|
||||||
|
- Tailwind utility classes only — no CSS modules, no styled-components
|
||||||
|
- Mobile-first responsive design
|
||||||
|
- Theme tokens defined as CSS custom properties in `globals.css` (oklch color space)
|
||||||
|
- No hardcoded colors — always use semantic tokens (`foreground`, `muted-foreground`, `border`, etc.)
|
||||||
|
|
||||||
|
### Registry (`src/registry/`)
|
||||||
|
|
||||||
|
- `map.tsx` is the single-file component library containing Map, Marker, Popup, Route, Controls, etc.
|
||||||
|
- Blocks live in `src/registry/blocks/` — each block is a self-contained page component
|
||||||
|
- Registry is built via `npm run registry:build` using the shadcn CLI
|
||||||
|
|
||||||
|
### Naming
|
||||||
|
|
||||||
|
- Directories: lowercase with dashes (`docs-sidebar`, `block-preview`)
|
||||||
|
- Components: PascalCase exports (`DocsLayout`, `PageHeader`)
|
||||||
|
- Files: kebab-case (`command-search.tsx`, `page-header.tsx`)
|
||||||
|
|
||||||
|
### Navigation
|
||||||
|
|
||||||
|
- All site navigation is centralized in `src/lib/site-navigation.ts`
|
||||||
|
- Docs sidebar nav uses `docsNavigation`, command search uses `siteNavigation`
|
||||||
|
|
||||||
|
## Design System
|
||||||
|
|
||||||
|
- Color palette is monochrome (grayscale oklch)
|
||||||
|
- Radius: `0.625rem` base with computed variants
|
||||||
|
- Animations: `fade-up`, `fade-in`, `scale-in` with staggered delays (100ms intervals)
|
||||||
|
- Header: sticky, backdrop-blur, gradient bottom border
|
||||||
|
- Footer: gradient top border matching header
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev # Start dev server
|
||||||
|
npm run build # Production build
|
||||||
|
npm run lint # ESLint
|
||||||
|
npm run registry:build # Build distributable registry to public/r/
|
||||||
|
```
|
||||||
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -46,7 +46,6 @@ export function DeliveryExample() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ExampleCard
|
<ExampleCard
|
||||||
label="Delivery"
|
|
||||||
className="aspect-square sm:col-span-2 sm:aspect-video lg:aspect-auto"
|
className="aspect-square sm:col-span-2 sm:aspect-video lg:aspect-auto"
|
||||||
stagger={9}
|
stagger={9}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ const statusConfig: Record<
|
|||||||
|
|
||||||
export function EVChargingExample() {
|
export function EVChargingExample() {
|
||||||
return (
|
return (
|
||||||
<ExampleCard label="EV Charging" className="aspect-square" stagger={7}>
|
<ExampleCard className="aspect-square" stagger={7}>
|
||||||
<Map center={[-122.434, 37.776]} zoom={11}>
|
<Map center={[-122.434, 37.776]} zoom={11}>
|
||||||
{stations.map((station) => {
|
{stations.map((station) => {
|
||||||
const config = statusConfig[station.status];
|
const config = statusConfig[station.status];
|
||||||
|
|||||||
@@ -4,14 +4,12 @@ import { CSSProperties } from "react";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface ExampleCardProps {
|
interface ExampleCardProps {
|
||||||
label?: string;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
stagger?: number;
|
stagger?: number;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ExampleCard({
|
export function ExampleCard({
|
||||||
label,
|
|
||||||
className,
|
className,
|
||||||
stagger = 5,
|
stagger = 5,
|
||||||
children,
|
children,
|
||||||
@@ -28,11 +26,6 @@ export function ExampleCard({
|
|||||||
} as CSSProperties
|
} as CSSProperties
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{label && (
|
|
||||||
<div className="text-muted-foreground bg-background/90 absolute top-2 left-2 z-10 rounded px-2 py-1 text-[10px] tracking-wider uppercase backdrop-blur-sm">
|
|
||||||
{label}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ export function FlyToExample() {
|
|||||||
const mapRef = useRef<MapRef>(null);
|
const mapRef = useRef<MapRef>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExampleCard label="Fly To" className="aspect-square" stagger={6}>
|
<ExampleCard className="aspect-square" stagger={6}>
|
||||||
<Map
|
<Map
|
||||||
center={destination.startCenter}
|
center={destination.startCenter}
|
||||||
zoom={0.5}
|
zoom={0.6}
|
||||||
ref={mapRef}
|
ref={mapRef}
|
||||||
projection={{ type: "globe" }}
|
projection={{ type: "globe" }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { ExampleCard } from "./example-card";
|
|||||||
|
|
||||||
export function TrendingExample() {
|
export function TrendingExample() {
|
||||||
return (
|
return (
|
||||||
<ExampleCard label="Trending" className="aspect-square" stagger={8}>
|
<ExampleCard className="aspect-square" stagger={8}>
|
||||||
<Map center={[-73.99, 40.735]} zoom={10}>
|
<Map center={[-73.99, 40.735]} zoom={10}>
|
||||||
<MapMarker longitude={-73.9857} latitude={40.7484}>
|
<MapMarker longitude={-73.9857} latitude={40.7484}>
|
||||||
<MarkerContent>
|
<MarkerContent>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export function DocsToc({ items, className }: DocsTocProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<nav aria-label="On this page" className={cn("flex flex-col", className)}>
|
<nav aria-label="On this page" className={cn("flex flex-col", className)}>
|
||||||
<p className="text-sidebar-foreground/70 mb-3 text-[13px] font-medium tracking-tight">
|
<p className="text-sidebar-foreground/70 mb-3 text-xs font-medium">
|
||||||
On This Page
|
On This Page
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ export function DocsToc({ items, className }: DocsTocProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"py-1 text-sm no-underline transition-colors",
|
"py-1 text-sm no-underline transition-colors",
|
||||||
isActive
|
isActive
|
||||||
? "text-foreground"
|
? "text-foreground font-medium"
|
||||||
: "text-muted-foreground hover:text-foreground",
|
: "text-muted-foreground hover:text-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -98,8 +98,8 @@ export function DocsLayout({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside className="hidden w-42 shrink-0 xl:block">
|
<aside className="hidden w-44 shrink-0 xl:block">
|
||||||
<nav className="sticky top-24 max-h-[calc(100svh-7rem)] overflow-y-auto pr-1">
|
<nav className="sticky top-14 max-h-[calc(100svh-3.5rem)] overflow-y-auto pt-10 pb-10 [scrollbar-gutter:stable]">
|
||||||
{toc.length > 0 && <DocsToc items={toc} />}
|
{toc.length > 0 && <DocsToc items={toc} />}
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -122,7 +122,7 @@ export function DocsSection({ title, children }: DocsSectionProps) {
|
|||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
)}
|
)}
|
||||||
<div className="text-primary [&_strong]:text-foreground [&_em]:text-muted-foreground space-y-4 text-base leading-7 [&_li]:leading-7 [&_ol]:list-decimal [&_ol]:space-y-2 [&_ol]:pl-5 [&_p]:leading-7 [&_strong]:font-medium [&_ul]:list-disc [&_ul]:space-y-2 [&_ul]:pl-5">
|
<div className="text-primary [&_strong]:text-foreground [&_em]:text-muted-foreground space-y-4 text-base leading-7 [&_strong]:font-medium [&>ol]:list-decimal [&>ol]:space-y-2 [&>ol]:pl-5 [&>ol>li]:leading-7 [&>p]:leading-7 [&>ul]:list-disc [&>ul]:space-y-2 [&>ul]:pl-5 [&>ul>li]:leading-7">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -173,7 +173,7 @@ export function DocsCode({
|
|||||||
return (
|
return (
|
||||||
<code
|
<code
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-muted relative rounded-md px-2 py-1 font-mono text-sm",
|
"bg-muted relative rounded-md px-1.5 py-0.5 font-mono text-sm",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -41,14 +41,20 @@ export default function ClusterExample() {
|
|||||||
closeOnClick={false}
|
closeOnClick={false}
|
||||||
focusAfterOpen={false}
|
focusAfterOpen={false}
|
||||||
closeButton
|
closeButton
|
||||||
|
className="w-34"
|
||||||
>
|
>
|
||||||
<div className="space-y-1 p-1">
|
<div className="text-[13px]">
|
||||||
<p className="text-sm">
|
<p className="text-muted-foreground">
|
||||||
Magnitude: {selectedPoint.properties.mag}
|
Magnitude:{" "}
|
||||||
|
<span className="text-foreground font-medium">
|
||||||
|
{selectedPoint.properties.mag}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm">
|
<p className="text-muted-foreground">
|
||||||
Tsunami:{" "}
|
Tsunami:{" "}
|
||||||
{selectedPoint.properties?.tsunami === 1 ? "Yes" : "No"}
|
<span className="text-foreground font-medium">
|
||||||
|
{selectedPoint.properties?.tsunami === 1 ? "Yes" : "No"}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</MapPopup>
|
</MapPopup>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export function DraggableMarkerExample() {
|
|||||||
draggable
|
draggable
|
||||||
longitude={draggableMarker.lng}
|
longitude={draggableMarker.lng}
|
||||||
latitude={draggableMarker.lat}
|
latitude={draggableMarker.lat}
|
||||||
onDragEnd={(lngLat) => {
|
onDrag={(lngLat) => {
|
||||||
setDraggableMarker({ lng: lngLat.lng, lat: lngLat.lat });
|
setDraggableMarker({ lng: lngLat.lng, lat: lngLat.lat });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -32,7 +32,7 @@ export function DraggableMarkerExample() {
|
|||||||
<MarkerPopup>
|
<MarkerPopup>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-foreground font-medium">Coordinates</p>
|
<p className="text-foreground font-medium">Coordinates</p>
|
||||||
<p className="text-muted-foreground text-xs">
|
<p className="text-muted-foreground text-xs tabular-nums">
|
||||||
{draggableMarker.lat.toFixed(4)},{" "}
|
{draggableMarker.lat.toFixed(4)},{" "}
|
||||||
{draggableMarker.lng.toFixed(4)}
|
{draggableMarker.lng.toFixed(4)}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ function MarkersLayer() {
|
|||||||
offset={10}
|
offset={10}
|
||||||
closeButton
|
closeButton
|
||||||
>
|
>
|
||||||
<div className="min-w-[140px]">
|
<div className="min-w-24">
|
||||||
<p className="font-medium">{selectedPoint.name}</p>
|
<p className="font-medium">{selectedPoint.name}</p>
|
||||||
<p className="text-muted-foreground text-sm">
|
<p className="text-muted-foreground text-sm">
|
||||||
{selectedPoint.category}
|
{selectedPoint.category}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { Map, MapControls } from "@/registry/map";
|
|||||||
export function MapControlsExample() {
|
export function MapControlsExample() {
|
||||||
return (
|
return (
|
||||||
<div className="h-[420px] w-full">
|
<div className="h-[420px] w-full">
|
||||||
<Map center={[2.3522, 48.8566]} zoom={11}>
|
<Map center={[2.3522, 48.8566]} zoom={8.5}>
|
||||||
<MapControls
|
<MapControls
|
||||||
position="bottom-right"
|
position="top-right"
|
||||||
showZoom
|
showZoom
|
||||||
showCompass
|
showCompass
|
||||||
showLocate
|
showLocate
|
||||||
|
|||||||
@@ -58,10 +58,10 @@ export function PopupExample() {
|
|||||||
{places.map((place) => (
|
{places.map((place) => (
|
||||||
<MapMarker key={place.id} longitude={place.lng} latitude={place.lat}>
|
<MapMarker key={place.id} longitude={place.lng} latitude={place.lat}>
|
||||||
<MarkerContent>
|
<MarkerContent>
|
||||||
<div className="size-5 rounded-full bg-rose-500 border-2 border-white shadow-lg cursor-pointer hover:scale-110 transition-transform" />
|
<div className="size-5 cursor-pointer rounded-full border-2 border-white bg-rose-500 shadow-lg transition-transform hover:scale-110" />
|
||||||
<MarkerLabel position="bottom">{place.label}</MarkerLabel>
|
<MarkerLabel position="bottom">{place.label}</MarkerLabel>
|
||||||
</MarkerContent>
|
</MarkerContent>
|
||||||
<MarkerPopup className="p-0 w-62">
|
<MarkerPopup className="w-62 p-0">
|
||||||
<div className="relative h-32 overflow-hidden rounded-t-md">
|
<div className="relative h-32 overflow-hidden rounded-t-md">
|
||||||
<Image
|
<Image
|
||||||
fill
|
fill
|
||||||
@@ -72,10 +72,10 @@ export function PopupExample() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-2 p-3">
|
<div className="space-y-2 p-3">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
<p className="text-muted-foreground pb-0.5 text-[11px] font-medium tracking-wide uppercase">
|
||||||
{place.category}
|
{place.category}
|
||||||
</span>
|
</p>
|
||||||
<h3 className="font-semibold text-foreground leading-tight">
|
<h3 className="text-foreground leading-tight font-semibold">
|
||||||
{place.name}
|
{place.name}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -88,16 +88,16 @@ export function PopupExample() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5 text-sm text-muted-foreground">
|
<div className="text-muted-foreground flex items-center gap-1.5 text-sm">
|
||||||
<Clock className="size-3.5" />
|
<Clock className="size-3.5" />
|
||||||
<span>{place.hours}</span>
|
<span>{place.hours}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 pt-1">
|
<div className="flex gap-2 pt-1">
|
||||||
<Button size="sm" className="flex-1 h-8">
|
<Button size="sm" className="flex-1">
|
||||||
<Navigation className="size-3.5 mr-1.5" />
|
<Navigation className="size-3.5" />
|
||||||
Directions
|
Directions
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" variant="outline" className="h-8">
|
<Button size="icon-sm" variant="outline">
|
||||||
<ExternalLink className="size-3.5" />
|
<ExternalLink className="size-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export function StandalonePopupExample() {
|
|||||||
closeButton
|
closeButton
|
||||||
focusAfterOpen={false}
|
focusAfterOpen={false}
|
||||||
closeOnClick={false}
|
closeOnClick={false}
|
||||||
className="w-62"
|
|
||||||
>
|
>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="text-foreground font-semibold">New York City</h3>
|
<h3 className="text-foreground font-semibold">New York City</h3>
|
||||||
|
|||||||
@@ -166,13 +166,15 @@ export default function IntroductionPage() {
|
|||||||
{features.map((feature) => (
|
{features.map((feature) => (
|
||||||
<div
|
<div
|
||||||
key={feature.title}
|
key={feature.title}
|
||||||
className="bg-card space-y-2 rounded-lg border p-4"
|
className="bg-card space-y-3 rounded-lg border p-4"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="bg-primary/10 flex size-8 items-center justify-center rounded-md">
|
<div className="bg-muted flex size-8 items-center justify-center rounded-md">
|
||||||
<feature.icon className="text-primary size-4" />
|
<feature.icon className="text-foreground size-4" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-foreground font-medium">{feature.title}</h3>
|
<h3 className="text-foreground text-base font-medium">
|
||||||
|
{feature.title}
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-muted-foreground text-sm">
|
<p className="text-muted-foreground text-sm">
|
||||||
{feature.description}
|
{feature.description}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Logo } from "./logo";
|
import { Logo } from "./logo";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const footerLinks = {
|
const footerLinks = {
|
||||||
product: [
|
product: [
|
||||||
@@ -30,9 +31,9 @@ const footerLinks = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Footer() {
|
export function Footer({ className }: { className?: string }) {
|
||||||
return (
|
return (
|
||||||
<footer className="mt-24 border-t md:mt-32">
|
<footer className={cn("mt-30 border-t", className)}>
|
||||||
<div className="container py-12 md:py-16">
|
<div className="container py-12 md:py-16">
|
||||||
<div className="grid grid-cols-2 gap-8 md:grid-cols-4">
|
<div className="grid grid-cols-2 gap-8 md:grid-cols-4">
|
||||||
<div className="col-span-2 md:col-span-1">
|
<div className="col-span-2 md:col-span-1">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function Header({ className }: { className?: string }) {
|
|||||||
<MobileNav />
|
<MobileNav />
|
||||||
<Logo className="hidden shrink-0 lg:flex" />
|
<Logo className="hidden shrink-0 lg:flex" />
|
||||||
<Separator
|
<Separator
|
||||||
className="ml-2.5 hidden h-4! w-px lg:block"
|
className="ml-2.5 hidden h-4! lg:block"
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
/>
|
/>
|
||||||
<MainNav className="hidden lg:flex" />
|
<MainNav className="hidden lg:flex" />
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
|
||||||
import { XIcon } from "lucide-react"
|
import { XIcon } from "lucide-react"
|
||||||
|
import { Dialog as DialogPrimitive } from "radix-ui"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
@@ -39,7 +39,7 @@ function DialogOverlay({
|
|||||||
<DialogPrimitive.Overlay
|
<DialogPrimitive.Overlay
|
||||||
data-slot="dialog-overlay"
|
data-slot="dialog-overlay"
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -61,7 +61,7 @@ function DialogContent({
|
|||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
data-slot="dialog-content"
|
data-slot="dialog-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
|
"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -70,7 +70,7 @@ function DialogContent({
|
|||||||
{showCloseButton && (
|
{showCloseButton && (
|
||||||
<DialogPrimitive.Close
|
<DialogPrimitive.Close
|
||||||
data-slot="dialog-close"
|
data-slot="dialog-close"
|
||||||
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||||
>
|
>
|
||||||
<XIcon />
|
<XIcon />
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
@@ -138,7 +138,7 @@ function DialogDescription({
|
|||||||
return (
|
return (
|
||||||
<DialogPrimitive.Description
|
<DialogPrimitive.Description
|
||||||
data-slot="dialog-description"
|
data-slot="dialog-description"
|
||||||
className={cn("text-muted-foreground text-sm", className)}
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export default function Page() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-8">
|
<div className="p-8">
|
||||||
<div className="bg-sidebar mx-auto grid max-w-7xl rounded-lg border md:h-[600px] md:grid-cols-[1.05fr_1fr]">
|
<div className="bg-sidebar mx-auto grid max-w-7xl rounded-xl border md:h-[600px] md:grid-cols-[1.05fr_1fr]">
|
||||||
<div className="flex flex-col p-5 md:p-6">
|
<div className="flex flex-col p-5 md:p-6">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<h3 className="text-2xl font-semibold tracking-tight">
|
<h3 className="text-2xl font-semibold tracking-tight">
|
||||||
|
|||||||
@@ -518,6 +518,19 @@ function DefaultMarkerIcon() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PopupCloseButton({ onClick }: { onClick: () => void }) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
aria-label="Close popup"
|
||||||
|
className="focus-visible:ring-ring hover:bg-muted text-foreground absolute top-0.5 right-0.5 z-10 inline-flex size-5 cursor-pointer items-center justify-center rounded-sm transition-colors focus:outline-none focus-visible:ring-2"
|
||||||
|
>
|
||||||
|
<X className="size-3.5" />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type MarkerPopupProps = {
|
type MarkerPopupProps = {
|
||||||
/** Popup content */
|
/** Popup content */
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -580,21 +593,12 @@ function MarkerPopup({
|
|||||||
return createPortal(
|
return createPortal(
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground animate-in fade-in-0 zoom-in-95 relative rounded-md border p-3 shadow-md",
|
"bg-popover text-popover-foreground relative max-w-62 rounded-md border p-3 shadow-md",
|
||||||
|
"animate-in fade-in-0 zoom-in-95 duration-200 ease-out",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{closeButton && (
|
{closeButton && <PopupCloseButton onClick={handleClose} />}
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={handleClose}
|
|
||||||
className="ring-offset-background focus:ring-ring absolute top-1 right-1 z-10 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none"
|
|
||||||
aria-label="Close popup"
|
|
||||||
>
|
|
||||||
<X className="h-4 w-4" />
|
|
||||||
<span className="sr-only">Close</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{children}
|
{children}
|
||||||
</div>,
|
</div>,
|
||||||
container,
|
container,
|
||||||
@@ -666,7 +670,8 @@ function MarkerTooltip({
|
|||||||
return createPortal(
|
return createPortal(
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-foreground text-background animate-in fade-in-0 zoom-in-95 rounded-md px-2 py-1 text-xs shadow-md",
|
"bg-foreground text-background pointer-events-none rounded-md px-2 py-1 text-xs text-balance shadow-md",
|
||||||
|
"animate-in fade-in-0 zoom-in-95 duration-200 ease-out",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -758,8 +763,11 @@ function ControlButton({
|
|||||||
aria-label={label}
|
aria-label={label}
|
||||||
type="button"
|
type="button"
|
||||||
className={cn(
|
className={cn(
|
||||||
"hover:bg-accent dark:hover:bg-accent/40 flex size-8 items-center justify-center transition-colors",
|
"flex size-8 items-center justify-center transition-all",
|
||||||
disabled && "pointer-events-none cursor-not-allowed opacity-50",
|
"first:rounded-t-md last:rounded-b-md",
|
||||||
|
"hover:bg-accent dark:hover:bg-accent/40",
|
||||||
|
"focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-inset",
|
||||||
|
"disabled:pointer-events-none disabled:opacity-50",
|
||||||
)}
|
)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
@@ -1006,21 +1014,12 @@ function MapPopup({
|
|||||||
return createPortal(
|
return createPortal(
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground animate-in fade-in-0 zoom-in-95 relative rounded-md border p-3 shadow-md",
|
"bg-popover text-popover-foreground relative max-w-62 rounded-md border p-3 shadow-md",
|
||||||
|
"animate-in fade-in-0 zoom-in-95 duration-200 ease-out",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{closeButton && (
|
{closeButton && <PopupCloseButton onClick={handleClose} />}
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={handleClose}
|
|
||||||
className="ring-offset-background focus:ring-ring absolute top-1 right-1 z-10 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none"
|
|
||||||
aria-label="Close popup"
|
|
||||||
>
|
|
||||||
<X className="h-4 w-4" />
|
|
||||||
<span className="sr-only">Close</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{children}
|
{children}
|
||||||
</div>,
|
</div>,
|
||||||
container,
|
container,
|
||||||
|
|||||||
Reference in New Issue
Block a user