@tuturuuu/ui 0.16.0 → 0.17.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.17.0](https://github.com/tutur3u/platform/compare/ui-v0.16.0...ui-v0.17.0) (2026-07-13)
4
+
5
+
6
+ ### Features
7
+
8
+ * **storefront:** refine the buyer experience ([45d0e76](https://github.com/tutur3u/platform/commit/45d0e763ff2adc858aa7c76024eb4554879b3977))
9
+ * **tasks:** promote progress tracking workspace ([058503a](https://github.com/tutur3u/platform/commit/058503a96c8df10725ecd3f88738d574760fc944))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * **platform:** address attendance and invoice feedback ([8d2a537](https://github.com/tutur3u/platform/commit/8d2a53782d01427839607c7b0d90eaa3ecc5a1d8))
15
+
16
+
17
+ ### Performance Improvements
18
+
19
+ * **web:** reduce dashboard request overhead ([93f4b2b](https://github.com/tutur3u/platform/commit/93f4b2b354286b33c5cf8ff9ee0e54a31b18a022))
20
+
3
21
  ## [0.16.0](https://github.com/tutur3u/platform/compare/ui-v0.15.0...ui-v0.16.0) (2026-07-11)
4
22
 
5
23
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tuturuuu/ui",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -85,12 +85,12 @@
85
85
  "@tiptap/react": "3.27.3",
86
86
  "@tiptap/starter-kit": "3.27.3",
87
87
  "@tuturuuu/ai": "0.2.2",
88
- "@tuturuuu/apis": "0.9.0",
88
+ "@tuturuuu/apis": "0.9.1",
89
89
  "@tuturuuu/hooks": "0.0.2",
90
90
  "@tuturuuu/icons": "0.0.6",
91
- "@tuturuuu/internal-api": "0.17.0",
91
+ "@tuturuuu/internal-api": "0.18.0",
92
92
  "@tuturuuu/supabase": "0.4.0",
93
- "@tuturuuu/utils": "0.15.0",
93
+ "@tuturuuu/utils": "0.15.1",
94
94
  "@types/debug": "^4.1.13",
95
95
  "browser-image-compression": "^2.0.2",
96
96
  "class-variance-authority": "^0.7.1",
@@ -129,6 +129,7 @@
129
129
  "react-resizable-panels": "^4.12.1",
130
130
  "react-syntax-highlighter": "^16.1.1",
131
131
  "recharts": "3.9.2",
132
+ "slot-text": "^0.3.3",
132
133
  "sonner": "^2.0.7",
133
134
  "streamdown": "^2.5.0",
134
135
  "tailwind-scrollbar": "^4.0.2",
@@ -149,7 +150,7 @@
149
150
  "@tanstack/react-table": "^8.21.3",
150
151
  "@testing-library/jest-dom": "^6.9.1",
151
152
  "@testing-library/react": "^16.3.2",
152
- "@tuturuuu/types": "0.16.0",
153
+ "@tuturuuu/types": "0.17.0",
153
154
  "@tuturuuu/typescript-config": "0.1.1",
154
155
  "@types/html2canvas": "^1.0.0",
155
156
  "@types/lodash": "^4.17.24",
@@ -0,0 +1,161 @@
1
+ 'use client';
2
+
3
+ import { Search } from '@tuturuuu/icons';
4
+ import type { InventoryStorefrontListing } from '@tuturuuu/internal-api/inventory';
5
+ import { cn } from '@tuturuuu/utils/format';
6
+ import type { ReactNode } from 'react';
7
+ import { useMemo, useState } from 'react';
8
+ import { SlotText } from 'slot-text/react';
9
+ import { Button } from '../button';
10
+ import { Input } from '../input';
11
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '../tabs';
12
+ import type { StorefrontSurfaceLabels } from './types';
13
+
14
+ type BrowseFilter = 'all' | 'bundle' | 'product';
15
+
16
+ export function StorefrontBrowsePanel({
17
+ compactLayout,
18
+ emptyListings,
19
+ labels,
20
+ listings,
21
+ renderListing,
22
+ }: {
23
+ compactLayout: boolean;
24
+ emptyListings: ReactNode;
25
+ labels: StorefrontSurfaceLabels;
26
+ listings: InventoryStorefrontListing[];
27
+ renderListing: (listing: InventoryStorefrontListing) => ReactNode;
28
+ }) {
29
+ const [filter, setFilter] = useState<BrowseFilter>('all');
30
+ const [query, setQuery] = useState('');
31
+ const visibleListings = useMemo(() => {
32
+ const normalizedQuery = query.trim().toLocaleLowerCase();
33
+
34
+ return listings.filter((listing) => {
35
+ if (filter !== 'all' && listing.listingType !== filter) return false;
36
+ if (!normalizedQuery) return true;
37
+
38
+ return [listing.title, listing.description]
39
+ .filter(Boolean)
40
+ .some((value) => value?.toLocaleLowerCase().includes(normalizedQuery));
41
+ });
42
+ }, [filter, listings, query]);
43
+ const resultLabel = labels.visibleItems.replace(
44
+ '{count}',
45
+ String(visibleListings.length)
46
+ );
47
+ const hasActiveFilters = filter !== 'all' || query.trim().length > 0;
48
+
49
+ return (
50
+ <section aria-labelledby="storefront-shop-heading" className="mt-10">
51
+ <div className="flex flex-col gap-5 border-border border-b pb-5 lg:flex-row lg:items-end lg:justify-between">
52
+ <div>
53
+ <p className="font-mono text-muted-foreground text-xs uppercase tracking-[0.16em]">
54
+ {labels.browse}
55
+ </p>
56
+ <h2
57
+ className="mt-2 font-semibold text-2xl tracking-tight"
58
+ id="storefront-shop-heading"
59
+ >
60
+ {labels.shopTitle}
61
+ </h2>
62
+ </div>
63
+ {listings.length ? (
64
+ <div className="grid gap-3 sm:grid-cols-[minmax(14rem,20rem)_auto] sm:items-center">
65
+ <label className="relative block">
66
+ <span className="sr-only">{labels.searchStore}</span>
67
+ <Search className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" />
68
+ <Input
69
+ className="h-10 rounded-lg border-border bg-background pl-9 shadow-none"
70
+ onChange={(event) => setQuery(event.target.value)}
71
+ placeholder={labels.searchStore}
72
+ type="search"
73
+ value={query}
74
+ />
75
+ </label>
76
+ <Tabs
77
+ onValueChange={(value) => setFilter(value as BrowseFilter)}
78
+ value={filter}
79
+ >
80
+ <TabsList className="grid h-10 w-full grid-cols-3 rounded-lg bg-muted/60 sm:w-auto">
81
+ <TabsTrigger onClick={() => setFilter('all')} value="all">
82
+ {labels.allItems}
83
+ </TabsTrigger>
84
+ <TabsTrigger
85
+ onClick={() => setFilter('product')}
86
+ value="product"
87
+ >
88
+ {labels.products}
89
+ </TabsTrigger>
90
+ <TabsTrigger onClick={() => setFilter('bundle')} value="bundle">
91
+ {labels.bundles}
92
+ </TabsTrigger>
93
+ </TabsList>
94
+ <TabsContent className="sr-only" forceMount value="all">
95
+ {labels.allItems}
96
+ </TabsContent>
97
+ <TabsContent className="sr-only" forceMount value="product">
98
+ {labels.products}
99
+ </TabsContent>
100
+ <TabsContent className="sr-only" forceMount value="bundle">
101
+ {labels.bundles}
102
+ </TabsContent>
103
+ </Tabs>
104
+ </div>
105
+ ) : null}
106
+ </div>
107
+
108
+ <div className="sr-only" role="status">
109
+ {resultLabel}
110
+ </div>
111
+ <div
112
+ aria-hidden
113
+ className="mt-4 h-5 font-mono text-muted-foreground text-xs"
114
+ >
115
+ <SlotText
116
+ options={{ bounce: 0.12, duration: 220, stagger: 18 }}
117
+ text={resultLabel}
118
+ />
119
+ </div>
120
+
121
+ {listings.length === 0 ? (
122
+ <div className="mt-5">{emptyListings}</div>
123
+ ) : visibleListings.length === 0 ? (
124
+ <div className="mt-5 grid min-h-64 place-items-center rounded-xl border border-border border-dashed bg-muted/15 p-8 text-center">
125
+ <div className="max-w-sm">
126
+ <Search className="mx-auto size-5 text-muted-foreground" />
127
+ <h3 className="mt-4 font-semibold text-lg">
128
+ {labels.noResultsTitle}
129
+ </h3>
130
+ <p className="mt-2 text-muted-foreground text-sm leading-6">
131
+ {labels.noResultsDescription}
132
+ </p>
133
+ {hasActiveFilters ? (
134
+ <Button
135
+ className="mt-4"
136
+ onClick={() => {
137
+ setFilter('all');
138
+ setQuery('');
139
+ }}
140
+ size="sm"
141
+ type="button"
142
+ variant="outline"
143
+ >
144
+ {labels.clearFilters}
145
+ </Button>
146
+ ) : null}
147
+ </div>
148
+ </div>
149
+ ) : (
150
+ <div
151
+ className={cn(
152
+ 'mt-5 grid gap-5',
153
+ compactLayout ? 'sm:grid-cols-2' : 'sm:grid-cols-2 xl:grid-cols-3'
154
+ )}
155
+ >
156
+ {visibleListings.map(renderListing)}
157
+ </div>
158
+ )}
159
+ </section>
160
+ );
161
+ }
@@ -92,7 +92,7 @@ export function StorefrontBundleSelectionDialog({
92
92
  }}
93
93
  open={open}
94
94
  >
95
- <DialogContent className="grid max-h-[90dvh] max-w-[min(42rem,calc(100vw-1rem))] grid-rows-[auto_auto_minmax(0,1fr)_auto] gap-4 overflow-hidden border-border/60 p-5 sm:rounded-2xl">
95
+ <DialogContent className="grid max-h-[90dvh] max-w-[min(42rem,calc(100vw-1rem))] grid-rows-[auto_auto_minmax(0,1fr)_auto] gap-4 overflow-hidden border-border p-5 shadow-sm sm:rounded-xl">
96
96
  <DialogHeader className="text-left">
97
97
  <DialogTitle>{labels.bundleSelectionTitle}</DialogTitle>
98
98
  <p className="text-muted-foreground text-sm">{listing.title}</p>
@@ -49,7 +49,7 @@ export function StorefrontCartPopover({
49
49
  <PopoverContent
50
50
  align="end"
51
51
  className={cn(
52
- 'w-[min(calc(100vw-2rem),24rem)] border-border/70 p-4 shadow-xl',
52
+ 'w-[min(calc(100vw-2rem),24rem)] border-border p-4 shadow-sm',
53
53
  radius
54
54
  )}
55
55
  sideOffset={10}
@@ -24,13 +24,40 @@ export function StorefrontHeroPanel({
24
24
  return (
25
25
  <section
26
26
  className={cn(
27
- 'relative isolate overflow-hidden',
27
+ 'grid overflow-hidden md:grid-cols-[minmax(0,1.08fr)_minmax(18rem,0.92fr)]',
28
28
  storefrontSurfaceClasses[storefront.surfaceStyle],
29
29
  radius
30
30
  )}
31
31
  >
32
- {/* Full-width featured banner backdrop. */}
33
- <div className="relative h-40 w-full sm:h-52 md:h-60">
32
+ <div className="flex min-h-80 flex-col justify-between gap-10 p-6 sm:p-8 lg:p-10">
33
+ <div className="flex items-center gap-2 font-mono text-muted-foreground text-xs uppercase tracking-[0.16em]">
34
+ <Store className="size-4" />
35
+ <span>{labels.browse}</span>
36
+ </div>
37
+ <div className="max-w-2xl">
38
+ <h2 className="text-balance font-semibold text-4xl tracking-[-0.035em] sm:text-5xl lg:text-6xl">
39
+ {storefront.name}
40
+ </h2>
41
+ <p className="mt-4 max-w-xl text-pretty text-muted-foreground leading-7">
42
+ {storefront.description ?? labels.fallbackDescription}
43
+ </p>
44
+ <div className="mt-6 flex flex-wrap gap-2">
45
+ <Badge className="border-border bg-background" variant="outline">
46
+ {listingsCount} {labels.product}
47
+ </Badge>
48
+ <Badge className="border-border bg-background" variant="outline">
49
+ {currency}
50
+ </Badge>
51
+ <Badge className="border-border bg-background" variant="outline">
52
+ {storefront.visibility === 'public'
53
+ ? labels.publicStore
54
+ : labels.privateStore}
55
+ </Badge>
56
+ </div>
57
+ </div>
58
+ </div>
59
+
60
+ <div className="relative min-h-64 border-border border-t md:min-h-full md:border-t-0 md:border-l">
34
61
  {heroImage ? (
35
62
  <StorefrontImagePanel
36
63
  className="absolute inset-0 h-full w-full"
@@ -39,34 +66,12 @@ export function StorefrontHeroPanel({
39
66
  priority
40
67
  />
41
68
  ) : (
42
- <div className="absolute inset-0 bg-gradient-to-br from-muted via-muted/60 to-background" />
43
- )}
44
- {/* Scrim that fades the banner into the page so overlaid text stays legible. */}
45
- <div className="absolute inset-0 bg-gradient-to-t from-background via-background/55 to-transparent" />
46
- </div>
47
-
48
- {/* Overlaid title block — pulled up onto the lower, faded part of the banner. */}
49
- <div className="relative -mt-20 flex flex-col gap-4 p-5">
50
- <div>
51
- <div className="flex items-center gap-2 text-muted-foreground text-xs">
52
- <Store className="h-4 w-4" />
53
- <span>{labels.browse}</span>
69
+ <div className="absolute inset-0 grid place-items-center bg-muted/35">
70
+ <div className="grid size-24 place-items-center rounded-xl border border-border bg-background/80">
71
+ <Store className="size-8 text-muted-foreground" />
72
+ </div>
54
73
  </div>
55
- <h2 className="mt-2 text-balance font-semibold text-3xl tracking-tight md:text-4xl">
56
- {storefront.name}
57
- </h2>
58
- <p className="mt-2 max-w-2xl text-muted-foreground text-sm leading-6">
59
- {storefront.description ?? labels.fallbackDescription}
60
- </p>
61
- </div>
62
- <div className="flex flex-wrap gap-2">
63
- <Badge className="border-border bg-background" variant="outline">
64
- {listingsCount} {labels.product}
65
- </Badge>
66
- <Badge className="border-border bg-background" variant="outline">
67
- {currency}
68
- </Badge>
69
- </div>
74
+ )}
70
75
  </div>
71
76
  </section>
72
77
  );
@@ -59,10 +59,10 @@ export function StorefrontListingCard({
59
59
  className={cn(
60
60
  surfaceClassName,
61
61
  radius,
62
- 'group relative overflow-hidden transition duration-200 hover:-translate-y-0.5 hover:shadow-foreground/10 hover:shadow-md',
62
+ 'group relative overflow-hidden transition duration-200 hover:border-foreground/20 hover:shadow-foreground/5 hover:shadow-sm',
63
63
  isList
64
64
  ? 'grid gap-4 p-3 sm:grid-cols-[112px_minmax(0,1fr)_auto] sm:items-center'
65
- : 'flex min-h-full flex-col gap-4 p-3'
65
+ : 'flex min-h-full flex-col'
66
66
  )}
67
67
  >
68
68
  <div className="relative">
@@ -80,7 +80,7 @@ export function StorefrontListingCard({
80
80
  className={cn(
81
81
  'overflow-hidden transition duration-300 group-hover:scale-[1.02]',
82
82
  radius,
83
- isList ? 'aspect-square' : 'aspect-[4/3]'
83
+ isList ? 'aspect-square' : 'aspect-[5/4] rounded-b-none'
84
84
  )}
85
85
  imageUrl={listing.imageUrl}
86
86
  label={listing.title}
@@ -92,7 +92,7 @@ export function StorefrontListingCard({
92
92
  </span>
93
93
  ) : null}
94
94
  </div>
95
- <div className="min-w-0">
95
+ <div className={cn('min-w-0', isList ? null : 'px-5 pt-5')}>
96
96
  <div className="flex flex-wrap items-center gap-2">
97
97
  <button
98
98
  className="min-w-0 truncate text-left font-semibold transition hover:text-[var(--storefront-accent-text,var(--primary))] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 disabled:cursor-default disabled:hover:text-foreground"
@@ -122,7 +122,12 @@ export function StorefrontListingCard({
122
122
  ) : null}
123
123
  </div>
124
124
 
125
- <div className="mt-auto flex flex-wrap items-center justify-between gap-3">
125
+ <div
126
+ className={cn(
127
+ 'mt-auto flex flex-wrap items-center justify-between gap-3',
128
+ isList ? null : 'px-5 pt-4 pb-5'
129
+ )}
130
+ >
126
131
  <div className="min-w-0">
127
132
  {hasVariants ? (
128
133
  <p className="truncate font-semibold tabular-nums">
@@ -27,21 +27,23 @@ export function StorefrontMerchSections({
27
27
  if (visibleSections.length === 0) return null;
28
28
 
29
29
  return (
30
- <div className="mt-4 grid gap-3">
30
+ <div className="mt-6 grid gap-4">
31
31
  {visibleSections.map((section) => {
32
32
  const sectionHref = getSafeStorefrontHttpUrl(section.href);
33
33
 
34
34
  return (
35
35
  <section
36
36
  className={cn(
37
- 'grid overflow-hidden border border-border bg-card md:grid-cols-[minmax(0,1fr)_280px]',
37
+ 'grid overflow-hidden border border-border bg-card md:grid-cols-[minmax(0,1fr)_minmax(16rem,0.7fr)]',
38
38
  radius
39
39
  )}
40
40
  key={section.id}
41
41
  >
42
- <div className="flex min-w-0 flex-col justify-center gap-2 p-4">
42
+ <div className="flex min-w-0 flex-col justify-center gap-3 p-6 sm:p-8">
43
43
  {section.title ? (
44
- <h2 className="font-semibold text-lg">{section.title}</h2>
44
+ <h2 className="text-balance font-semibold text-2xl tracking-tight">
45
+ {section.title}
46
+ </h2>
45
47
  ) : null}
46
48
  {section.description ? (
47
49
  <p className="text-muted-foreground text-sm leading-6">
@@ -50,7 +52,7 @@ export function StorefrontMerchSections({
50
52
  ) : null}
51
53
  {sectionHref ? (
52
54
  <a
53
- className="mt-1 w-fit font-medium text-sm underline-offset-4 hover:underline"
55
+ className="mt-2 w-fit border-border border-b pb-1 font-medium text-sm transition hover:border-foreground"
54
56
  href={sectionHref}
55
57
  >
56
58
  {sectionHref.replace(/^https?:\/\//u, '')}
@@ -1,8 +1,17 @@
1
1
  'use client';
2
2
 
3
- import { ArrowRight, Minus, Plus, ShoppingCart, Zap } from '@tuturuuu/icons';
3
+ import {
4
+ ArrowRight,
5
+ Minus,
6
+ MonitorSmartphone,
7
+ Plus,
8
+ ShieldCheck,
9
+ ShoppingCart,
10
+ Zap,
11
+ } from '@tuturuuu/icons';
4
12
  import type {
5
13
  InventoryListingVariant,
14
+ InventoryStorefront,
6
15
  InventoryStorefrontListing,
7
16
  } from '@tuturuuu/internal-api/inventory';
8
17
  import { cn } from '@tuturuuu/utils/format';
@@ -30,6 +39,7 @@ import {
30
39
  export function StorefrontProductDetail({
31
40
  cartHref,
32
41
  cartLines,
42
+ checkoutMode,
33
43
  currency,
34
44
  isSubmitting = false,
35
45
  labels,
@@ -39,11 +49,13 @@ export function StorefrontProductDetail({
39
49
  onIncrement,
40
50
  quantity,
41
51
  radius,
52
+ presentation = 'page',
42
53
  showInventoryBadges,
43
54
  surfaceClassName,
44
55
  }: {
45
56
  cartHref?: string;
46
57
  cartLines?: StorefrontCartLine[];
58
+ checkoutMode?: InventoryStorefront['checkoutMode'];
47
59
  currency: string;
48
60
  isSubmitting?: boolean;
49
61
  labels: StorefrontSurfaceLabels;
@@ -57,6 +69,7 @@ export function StorefrontProductDetail({
57
69
  ) => void;
58
70
  quantity: number;
59
71
  radius: string;
72
+ presentation?: 'dialog' | 'page';
60
73
  showInventoryBadges: boolean;
61
74
  surfaceClassName: string;
62
75
  }) {
@@ -114,19 +127,45 @@ export function StorefrontProductDetail({
114
127
  compareAtPrice && compareAtPrice > displayPrice
115
128
  ? Math.round((1 - displayPrice / compareAtPrice) * 100)
116
129
  : 0;
130
+ const isDialog = presentation === 'dialog';
131
+ const isSquareTerminal = checkoutMode === 'square_terminal';
132
+ const isPolar = checkoutMode === 'polar';
117
133
 
118
134
  return (
119
- <div className="grid gap-6 lg:grid-cols-2">
120
- <div className={cn('overflow-hidden', surfaceClassName, radius)}>
135
+ <div
136
+ className={cn(
137
+ 'grid min-w-0',
138
+ isDialog
139
+ ? 'lg:grid-cols-[minmax(0,1.08fr)_minmax(22rem,0.92fr)]'
140
+ : 'gap-6 lg:grid-cols-2'
141
+ )}
142
+ >
143
+ <div
144
+ className={cn(
145
+ 'overflow-hidden',
146
+ surfaceClassName,
147
+ isDialog ? 'border-border border-b lg:border-r lg:border-b-0' : radius
148
+ )}
149
+ >
121
150
  <StorefrontImagePanel
122
- className="aspect-square"
151
+ className={cn(
152
+ isDialog
153
+ ? 'aspect-[4/3] min-h-72 lg:aspect-auto lg:h-full lg:min-h-[36rem]'
154
+ : 'aspect-square'
155
+ )}
123
156
  imageUrl={imageUrl}
124
157
  label={listing.title}
125
158
  priority
126
159
  />
127
160
  </div>
128
161
 
129
- <div className="flex flex-col gap-5">
162
+ <div
163
+ className={cn(
164
+ 'flex min-w-0 flex-col gap-5',
165
+ isDialog &&
166
+ 'p-5 sm:p-7 lg:max-h-[min(44rem,90dvh)] lg:overflow-y-auto lg:p-9'
167
+ )}
168
+ >
130
169
  <div className="flex flex-wrap items-center gap-2">
131
170
  <Badge className="border-border bg-background" variant="outline">
132
171
  {listing.listingType === 'bundle' ? labels.bundle : labels.product}
@@ -141,7 +180,7 @@ export function StorefrontProductDetail({
141
180
  ) : null}
142
181
  </div>
143
182
 
144
- <h2 className="text-balance font-semibold text-3xl tracking-tight md:text-4xl">
183
+ <h2 className="text-balance break-words font-semibold text-2xl tracking-tight sm:text-3xl lg:text-4xl">
145
184
  {listing.title}
146
185
  </h2>
147
186
 
@@ -218,6 +257,40 @@ export function StorefrontProductDetail({
218
257
  {listing.description ?? labels.fallbackDescription}
219
258
  </p>
220
259
 
260
+ {isSquareTerminal || isPolar ? (
261
+ <div
262
+ className={cn(
263
+ 'flex items-start gap-3 border border-border bg-muted/25 p-3.5',
264
+ radius
265
+ )}
266
+ >
267
+ <span
268
+ className={cn(
269
+ 'grid size-9 shrink-0 place-items-center border border-border bg-background',
270
+ radius
271
+ )}
272
+ >
273
+ {isSquareTerminal ? (
274
+ <MonitorSmartphone className="size-4" />
275
+ ) : (
276
+ <ShieldCheck className="size-4" />
277
+ )}
278
+ </span>
279
+ <div className="min-w-0">
280
+ <p className="font-medium text-sm">
281
+ {isSquareTerminal
282
+ ? labels.squareTerminal
283
+ : labels.onlineCheckout}
284
+ </p>
285
+ <p className="mt-0.5 text-muted-foreground text-xs leading-5">
286
+ {isSquareTerminal
287
+ ? labels.squareTerminalDescription
288
+ : labels.onlineCheckoutDescription}
289
+ </p>
290
+ </div>
291
+ </div>
292
+ ) : null}
293
+
221
294
  <div className="mt-auto flex flex-wrap items-center gap-3 border-border border-t pt-5">
222
295
  {canChange ? (
223
296
  needsSelection ? (
@@ -13,6 +13,7 @@ import type { StorefrontCartLine, StorefrontSurfaceLabels } from './types';
13
13
  export function StorefrontProductDialog({
14
14
  cartHref,
15
15
  cartLines,
16
+ checkoutMode,
16
17
  currency,
17
18
  isSubmitting,
18
19
  labels,
@@ -27,6 +28,7 @@ export function StorefrontProductDialog({
27
28
  }: {
28
29
  cartHref?: string;
29
30
  cartLines?: StorefrontCartLine[];
31
+ checkoutMode?: Parameters<typeof StorefrontProductDetail>[0]['checkoutMode'];
30
32
  currency: string;
31
33
  isSubmitting?: boolean;
32
34
  labels: StorefrontSurfaceLabels;
@@ -45,13 +47,14 @@ export function StorefrontProductDialog({
45
47
  }) {
46
48
  return (
47
49
  <Dialog onOpenChange={onOpenChange} open={Boolean(listing)}>
48
- <DialogContent className="max-h-[90vh] w-[calc(100%-1.5rem)] max-w-5xl overflow-y-auto sm:w-[calc(100%-2rem)]">
50
+ <DialogContent className="max-h-[92dvh] w-[calc(100%-1rem)] max-w-none overflow-hidden border-border p-0 shadow-2xl sm:w-[calc(100%-2rem)] sm:max-w-6xl">
49
51
  {listing ? (
50
52
  <>
51
53
  <DialogTitle className="sr-only">{listing.title}</DialogTitle>
52
54
  <StorefrontProductDetail
53
55
  cartHref={cartHref}
54
56
  cartLines={cartLines}
57
+ checkoutMode={checkoutMode}
55
58
  currency={currency}
56
59
  isSubmitting={isSubmitting}
57
60
  labels={labels}
@@ -61,6 +64,7 @@ export function StorefrontProductDialog({
61
64
  onIncrement={onIncrement}
62
65
  quantity={0}
63
66
  radius={radius}
67
+ presentation="dialog"
64
68
  showInventoryBadges={showInventoryBadges}
65
69
  surfaceClassName={surfaceClassName}
66
70
  />