keystone-design-bootstrap 1.0.59 → 1.0.60
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/package.json +1 -1
- package/src/design_system/portal/LoginForm.tsx +11 -11
- package/src/design_system/portal/MessageComposer.tsx +2 -2
- package/src/design_system/portal/PortalPage.tsx +19 -19
- package/src/design_system/portal/RowThumbnail.tsx +3 -3
- package/src/styles/style-overrides.aman.css +6 -0
- package/src/styles/style-overrides.barelux.css +6 -0
- package/src/styles/theme.css +6 -0
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@ interface LoginFormProps {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const inputClass =
|
|
16
|
-
'block w-full rounded border border-
|
|
16
|
+
'block w-full rounded-input border border-primary bg-primary px-3 py-2.5 text-sm text-primary placeholder-quaternary focus:border-brand focus:outline-none focus:ring-1 focus:ring-brand transition-colors';
|
|
17
17
|
const labelClass = 'block text-sm text-secondary mb-1';
|
|
18
18
|
|
|
19
19
|
function isValidEmail(value: string): boolean {
|
|
@@ -185,7 +185,7 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
185
185
|
</div>
|
|
186
186
|
|
|
187
187
|
{error && (
|
|
188
|
-
<div className="mb-4 rounded border border-
|
|
188
|
+
<div className="mb-4 rounded-input border border-error bg-error-primary px-3 py-2 text-sm text-error-primary">
|
|
189
189
|
{error}
|
|
190
190
|
</div>
|
|
191
191
|
)}
|
|
@@ -206,17 +206,17 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
206
206
|
/>
|
|
207
207
|
</div>
|
|
208
208
|
<div className="flex items-center gap-3">
|
|
209
|
-
<hr className="flex-1 border-
|
|
210
|
-
<span className="text-xs text-
|
|
211
|
-
<hr className="flex-1 border-
|
|
209
|
+
<hr className="flex-1 border-secondary" />
|
|
210
|
+
<span className="text-xs text-quaternary">or</span>
|
|
211
|
+
<hr className="flex-1 border-secondary" />
|
|
212
212
|
</div>
|
|
213
213
|
<div>
|
|
214
214
|
<label className={labelClass}>Phone number</label>
|
|
215
|
-
<div className="flex rounded border border-
|
|
215
|
+
<div className="flex rounded-input border border-primary overflow-hidden focus-within:border-brand focus-within:ring-1 focus-within:ring-brand transition-colors">
|
|
216
216
|
<select
|
|
217
217
|
value={selectedCountry}
|
|
218
218
|
onChange={(e) => { setSelectedCountry(e.target.value); setPhoneValue(''); }}
|
|
219
|
-
className="border-r border-
|
|
219
|
+
className="border-r border-secondary bg-secondary px-2 py-2.5 text-sm text-secondary focus:outline-none"
|
|
220
220
|
aria-label="Country code"
|
|
221
221
|
>
|
|
222
222
|
{countryOptions.map((opt) => (
|
|
@@ -228,7 +228,7 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
228
228
|
value={phoneValue}
|
|
229
229
|
onChange={handlePhoneChange}
|
|
230
230
|
placeholder={nationalPlaceholder}
|
|
231
|
-
className="flex-1 px-3 py-2.5 text-sm text-
|
|
231
|
+
className="flex-1 px-3 py-2.5 text-sm text-primary placeholder-quaternary bg-transparent focus:outline-none"
|
|
232
232
|
/>
|
|
233
233
|
</div>
|
|
234
234
|
</div>
|
|
@@ -236,7 +236,7 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
236
236
|
<button
|
|
237
237
|
type="submit"
|
|
238
238
|
disabled={loading}
|
|
239
|
-
className="w-full rounded
|
|
239
|
+
className="w-full rounded-interactive bg-brand-solid px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-solid_hover transition-colors disabled:opacity-50"
|
|
240
240
|
>
|
|
241
241
|
{loading ? 'Looking you up…' : 'Continue'}
|
|
242
242
|
</button>
|
|
@@ -264,7 +264,7 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
264
264
|
<button
|
|
265
265
|
type="submit"
|
|
266
266
|
disabled={loading}
|
|
267
|
-
className="w-full rounded
|
|
267
|
+
className="w-full rounded-interactive bg-brand-solid px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-solid_hover transition-colors disabled:opacity-50"
|
|
268
268
|
>
|
|
269
269
|
{loading ? 'Signing in…' : 'Sign in'}
|
|
270
270
|
</button>
|
|
@@ -339,7 +339,7 @@ export function LoginForm({ onSuccess, onClose }: LoginFormProps) {
|
|
|
339
339
|
<button
|
|
340
340
|
type="submit"
|
|
341
341
|
disabled={loading}
|
|
342
|
-
className="w-full rounded
|
|
342
|
+
className="w-full rounded-interactive bg-brand-solid px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-solid_hover transition-colors disabled:opacity-50"
|
|
343
343
|
>
|
|
344
344
|
{loading ? 'Creating account…' : 'Create account'}
|
|
345
345
|
</button>
|
|
@@ -67,12 +67,12 @@ export function MessageComposer({ contactId }: { contactId: number }) {
|
|
|
67
67
|
placeholder="Type a message… (Enter to send)"
|
|
68
68
|
rows={1}
|
|
69
69
|
disabled={isPending}
|
|
70
|
-
className="flex-1 resize-none rounded-
|
|
70
|
+
className="flex-1 resize-none rounded-input border border-primary bg-primary px-3 py-2 text-sm text-primary placeholder-quaternary focus:border-brand focus:outline-none focus:ring-1 focus:ring-brand transition-colors disabled:opacity-50"
|
|
71
71
|
/>
|
|
72
72
|
<button
|
|
73
73
|
type="submit"
|
|
74
74
|
disabled={!body.trim() || isPending}
|
|
75
|
-
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-
|
|
75
|
+
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-interactive bg-brand-solid text-white transition-colors hover:bg-brand-solid_hover disabled:opacity-40 disabled:cursor-not-allowed"
|
|
76
76
|
aria-label="Send message"
|
|
77
77
|
>
|
|
78
78
|
{isPending ? (
|
|
@@ -165,7 +165,7 @@ function LoginWall({ message, cta = 'Sign in' }: { message: string; cta?: string
|
|
|
165
165
|
<p className="text-sm font-medium text-primary">{message}</p>
|
|
166
166
|
<button
|
|
167
167
|
data-open-login-modal
|
|
168
|
-
className="mt-4 cursor-pointer rounded-
|
|
168
|
+
className="mt-4 cursor-pointer rounded-interactive bg-brand-solid px-5 py-2 text-sm font-semibold text-white transition-colors hover:bg-brand-solid_hover"
|
|
169
169
|
>
|
|
170
170
|
{cta}
|
|
171
171
|
</button>
|
|
@@ -175,7 +175,7 @@ function LoginWall({ message, cta = 'Sign in' }: { message: string; cta?: string
|
|
|
175
175
|
|
|
176
176
|
function EmptyState({ message }: { message: string }) {
|
|
177
177
|
return (
|
|
178
|
-
<div className="rounded-
|
|
178
|
+
<div className="rounded-component border border-secondary bg-secondary py-16 text-center">
|
|
179
179
|
<p className="text-sm text-tertiary">{message}</p>
|
|
180
180
|
</div>
|
|
181
181
|
);
|
|
@@ -214,7 +214,7 @@ function ServiceItemRow({
|
|
|
214
214
|
<Link
|
|
215
215
|
key={offer.id}
|
|
216
216
|
href={specialsHref}
|
|
217
|
-
className="inline-flex items-center rounded-
|
|
217
|
+
className="inline-flex items-center rounded-badge bg-secondary border border-brand px-2 py-0.5 text-xs font-medium text-brand-secondary hover:bg-secondary_hover transition-colors"
|
|
218
218
|
>
|
|
219
219
|
Special: {offer.name}
|
|
220
220
|
</Link>
|
|
@@ -223,7 +223,7 @@ function ServiceItemRow({
|
|
|
223
223
|
key={offer.id}
|
|
224
224
|
data-open-login-modal
|
|
225
225
|
data-login-redirect={specialsHref}
|
|
226
|
-
className="inline-flex items-center rounded-
|
|
226
|
+
className="inline-flex items-center rounded-badge bg-secondary border border-brand px-2 py-0.5 text-xs font-medium text-brand-secondary hover:bg-secondary_hover transition-colors cursor-pointer"
|
|
227
227
|
>
|
|
228
228
|
Special: {offer.name}
|
|
229
229
|
</button>
|
|
@@ -267,7 +267,7 @@ function ServicesPanel({
|
|
|
267
267
|
return (
|
|
268
268
|
<>
|
|
269
269
|
<PortalTabTracker event="ViewContent" params={{ contentName: 'Services', contentCategory: 'Services' }} />
|
|
270
|
-
<div className="divide-y divide-tertiary rounded-
|
|
270
|
+
<div className="divide-y divide-tertiary rounded-component border border-secondary bg-primary overflow-hidden">
|
|
271
271
|
{activeServices.map((service) => (
|
|
272
272
|
<details key={service.id} className="group">
|
|
273
273
|
<summary className="flex cursor-pointer list-none items-center justify-between px-5 py-4 hover:bg-secondary transition-colors">
|
|
@@ -325,7 +325,7 @@ function PackagesPanel({
|
|
|
325
325
|
{packages.map((pkg) => {
|
|
326
326
|
const activeOffers = (pkg.offers ?? []).filter((o) => o.active !== false && !o.expired);
|
|
327
327
|
return (
|
|
328
|
-
<div key={pkg.id} className="group rounded-
|
|
328
|
+
<div key={pkg.id} className="group rounded-component border border-secondary bg-primary p-4 flex flex-col gap-3">
|
|
329
329
|
<div className="flex items-start gap-3">
|
|
330
330
|
<RowThumbnail
|
|
331
331
|
photoAttachments={pkg.photo_attachments}
|
|
@@ -371,7 +371,7 @@ function PackagesPanel({
|
|
|
371
371
|
<Link
|
|
372
372
|
key={offer.id}
|
|
373
373
|
href={specialsHref}
|
|
374
|
-
className="inline-flex items-center rounded-
|
|
374
|
+
className="inline-flex items-center rounded-badge bg-secondary border border-brand px-2.5 py-0.5 text-xs font-medium text-brand-secondary hover:bg-secondary_hover transition-colors"
|
|
375
375
|
>
|
|
376
376
|
Special: {offer.name}
|
|
377
377
|
</Link>
|
|
@@ -380,7 +380,7 @@ function PackagesPanel({
|
|
|
380
380
|
key={offer.id}
|
|
381
381
|
data-open-login-modal
|
|
382
382
|
data-login-redirect={specialsHref}
|
|
383
|
-
className="inline-flex items-center rounded-
|
|
383
|
+
className="inline-flex items-center rounded-badge bg-secondary border border-brand px-2.5 py-0.5 text-xs font-medium text-brand-secondary hover:bg-secondary_hover transition-colors cursor-pointer"
|
|
384
384
|
>
|
|
385
385
|
Special: {offer.name}
|
|
386
386
|
</button>
|
|
@@ -408,7 +408,7 @@ function SpecialsPanel({ specials }: { specials: SpecialItem[] }) {
|
|
|
408
408
|
<PortalTabTracker event="ViewContent" params={{ contentName: 'Specials', contentCategory: 'Specials' }} />
|
|
409
409
|
<div className="space-y-3">
|
|
410
410
|
{specials.map((special) => (
|
|
411
|
-
<div key={special.id} className="group flex items-start gap-3 rounded-
|
|
411
|
+
<div key={special.id} className="group flex items-start gap-3 rounded-component border border-secondary bg-primary px-4 py-4">
|
|
412
412
|
<RowThumbnail
|
|
413
413
|
photoAttachments={special.photoAttachments}
|
|
414
414
|
seed={`special-${special.id}`}
|
|
@@ -454,10 +454,10 @@ function MessagesPanel({
|
|
|
454
454
|
businessName;
|
|
455
455
|
|
|
456
456
|
return (
|
|
457
|
-
<div className="flex flex-col rounded-
|
|
457
|
+
<div className="flex flex-col rounded-component border border-secondary bg-primary overflow-hidden">
|
|
458
458
|
{/* Thread header */}
|
|
459
459
|
<div className="flex items-center gap-2.5 border-b border-secondary px-4 py-3 shrink-0">
|
|
460
|
-
<div className="flex h-7 w-7 items-center justify-center rounded-full bg-
|
|
460
|
+
<div className="flex h-7 w-7 items-center justify-center rounded-full bg-brand-solid text-xs font-semibold text-white shrink-0">
|
|
461
461
|
{getInitials(threadBusiness)}
|
|
462
462
|
</div>
|
|
463
463
|
<span className="text-sm font-semibold text-primary">{threadBusiness}</span>
|
|
@@ -480,8 +480,8 @@ function MessagesPanel({
|
|
|
480
480
|
const isOutbound = m.direction === 'outbound';
|
|
481
481
|
return (
|
|
482
482
|
<div key={m.id} className={`flex ${isOutbound ? 'justify-start' : 'justify-end'}`}>
|
|
483
|
-
<div className={`max-w-[80%] rounded-
|
|
484
|
-
isOutbound ? 'bg-secondary text-primary
|
|
483
|
+
<div className={`max-w-[80%] rounded-component px-4 py-2.5 text-sm ${
|
|
484
|
+
isOutbound ? 'bg-secondary text-primary' : 'bg-brand-solid text-white'
|
|
485
485
|
}`}>
|
|
486
486
|
{m.sender_display_name && (
|
|
487
487
|
<p className="mb-0.5 text-xs font-medium opacity-60">{m.sender_display_name}</p>
|
|
@@ -522,7 +522,7 @@ function BookPanel({
|
|
|
522
522
|
return (
|
|
523
523
|
<>
|
|
524
524
|
<PortalTabTracker event="InitiateCheckout" />
|
|
525
|
-
<div className="rounded-
|
|
525
|
+
<div className="rounded-component border border-secondary overflow-hidden" style={{ height: '70vh' }}>
|
|
526
526
|
<iframe
|
|
527
527
|
src={bookingHref}
|
|
528
528
|
className="w-full h-full"
|
|
@@ -549,7 +549,7 @@ function BookPanel({
|
|
|
549
549
|
href={bookingHref}
|
|
550
550
|
target="_blank"
|
|
551
551
|
rel="noopener noreferrer"
|
|
552
|
-
className="mt-5 inline-flex items-center gap-2 rounded-
|
|
552
|
+
className="mt-5 inline-flex items-center gap-2 rounded-interactive bg-brand-solid px-6 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-brand-solid_hover"
|
|
553
553
|
>
|
|
554
554
|
{bookingLabel}
|
|
555
555
|
<svg className="size-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
@@ -659,7 +659,7 @@ export async function PortalPage({
|
|
|
659
659
|
<div className="flex items-center gap-3">
|
|
660
660
|
{consumerDisplayName && (
|
|
661
661
|
<div className="hidden sm:flex items-center gap-2 rounded-full border border-secondary bg-secondary px-3 py-1.5">
|
|
662
|
-
<div className="flex h-5 w-5 items-center justify-center rounded-full bg-
|
|
662
|
+
<div className="flex h-5 w-5 items-center justify-center rounded-full bg-brand-solid text-[9px] font-bold text-white shrink-0">
|
|
663
663
|
{getInitials(consumerDisplayName)}
|
|
664
664
|
</div>
|
|
665
665
|
<span className="text-xs font-medium text-secondary max-w-[120px] truncate">
|
|
@@ -672,7 +672,7 @@ export async function PortalPage({
|
|
|
672
672
|
) : (
|
|
673
673
|
<button
|
|
674
674
|
data-open-login-modal
|
|
675
|
-
className="cursor-pointer rounded-
|
|
675
|
+
className="cursor-pointer rounded-interactive border border-secondary px-3 py-1.5 text-xs font-medium text-secondary hover:bg-secondary transition-colors"
|
|
676
676
|
>
|
|
677
677
|
Sign in
|
|
678
678
|
</button>
|
|
@@ -691,8 +691,8 @@ export async function PortalPage({
|
|
|
691
691
|
const isActive = tab === t.id;
|
|
692
692
|
const isGated = !isLoggedIn && (t.id === 'specials' || t.id === 'messages' || t.id === 'book');
|
|
693
693
|
const href = `${portalHref}?tab=${t.id}`;
|
|
694
|
-
const className = `shrink-0 rounded-
|
|
695
|
-
isActive ? 'bg-
|
|
694
|
+
const className = `shrink-0 rounded-interactive px-4 py-2 text-sm font-medium transition-colors whitespace-nowrap ${
|
|
695
|
+
isActive ? 'bg-brand-solid text-white' : 'text-secondary hover:bg-secondary hover:text-primary'
|
|
696
696
|
}`;
|
|
697
697
|
if (isGated) {
|
|
698
698
|
return (
|
|
@@ -26,7 +26,7 @@ export function RowThumbnail({
|
|
|
26
26
|
|
|
27
27
|
if (list.length === 0) {
|
|
28
28
|
return (
|
|
29
|
-
<div className={`${sizeClassName} shrink-0 rounded-
|
|
29
|
+
<div className={`${sizeClassName} shrink-0 rounded-component bg-secondary border border-tertiary flex items-center justify-center`}>
|
|
30
30
|
<svg className="size-5 text-quaternary" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
31
31
|
<path strokeLinecap="round" strokeLinejoin="round" d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" />
|
|
32
32
|
</svg>
|
|
@@ -36,7 +36,7 @@ export function RowThumbnail({
|
|
|
36
36
|
|
|
37
37
|
if (list.length === 1) {
|
|
38
38
|
return (
|
|
39
|
-
<div className={`${sizeClassName} shrink-0 rounded-
|
|
39
|
+
<div className={`${sizeClassName} shrink-0 rounded-component overflow-hidden`}>
|
|
40
40
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
41
41
|
<img
|
|
42
42
|
src={list[0]!.url}
|
|
@@ -48,7 +48,7 @@ export function RowThumbnail({
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
return (
|
|
51
|
-
|
|
51
|
+
<div className={`${sizeClassName} shrink-0 rounded-component overflow-hidden relative`}>
|
|
52
52
|
<div
|
|
53
53
|
className={`absolute inset-0 ${transitioning ? 'transition-opacity ease-in-out' : 'transition-none'}`}
|
|
54
54
|
style={{ opacity: transitioning ? 0 : 1, ...(transitioning ? CROSSFADE_STYLE : {}) }}
|
|
@@ -40,6 +40,12 @@
|
|
|
40
40
|
/* Border colors */
|
|
41
41
|
--color-border-primary: #E5E2D9;
|
|
42
42
|
--color-border-secondary: #D5D2C9;
|
|
43
|
+
|
|
44
|
+
/* Radius — minimal/sharp aesthetic */
|
|
45
|
+
--radius-component: 0.25rem;
|
|
46
|
+
--radius-interactive: 0.25rem;
|
|
47
|
+
--radius-input: 0.25rem;
|
|
48
|
+
--radius-badge: 0.25rem;
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
/* Body text uses Inter */
|
|
@@ -40,6 +40,12 @@
|
|
|
40
40
|
/* Border colors */
|
|
41
41
|
--color-border-primary: #EEEAE7;
|
|
42
42
|
--color-border-secondary: #E5E1DE;
|
|
43
|
+
|
|
44
|
+
/* Radius — soft/rounded aesthetic */
|
|
45
|
+
--radius-component: 0.75rem;
|
|
46
|
+
--radius-interactive: 0.5rem;
|
|
47
|
+
--radius-input: 0.5rem;
|
|
48
|
+
--radius-badge: 9999px;
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
/* Body text uses Poppins */
|
package/src/styles/theme.css
CHANGED
|
@@ -54,6 +54,12 @@
|
|
|
54
54
|
--radius-3xl: 1.5rem;
|
|
55
55
|
--radius-full: 9999px;
|
|
56
56
|
|
|
57
|
+
/* Semantic radius tokens — override per theme to get consistent rounding everywhere */
|
|
58
|
+
--radius-component: 0.5rem; /* cards, panels, containers */
|
|
59
|
+
--radius-interactive: 0.375rem; /* buttons, tabs */
|
|
60
|
+
--radius-input: 0.375rem; /* form inputs */
|
|
61
|
+
--radius-badge: 9999px; /* pill badges / tags */
|
|
62
|
+
|
|
57
63
|
/* ==================== SHADOWS ==================== */
|
|
58
64
|
--shadow-xs: 0px 1px 2px rgba(10, 13, 18, 0.05);
|
|
59
65
|
--shadow-sm: 0px 1px 3px rgba(10, 13, 18, 0.1), 0px 1px 2px -1px rgba(10, 13, 18, 0.1);
|