From 05bd07be58e2491b9fe06249b051802710d38a97 Mon Sep 17 00:00:00 2001 From: Darren Ackers Date: Tue, 23 Sep 2025 11:18:48 +0100 Subject: [PATCH] feat(styles): added where clauses and tests --- packages/styles/dist.css | 2 +- packages/styles/src/base.css | 57 +++--- packages/styles/src/base.test.ts | 319 +++++++++++++++++++++++++++++++ 3 files changed, 349 insertions(+), 29 deletions(-) create mode 100644 packages/styles/src/base.test.ts diff --git a/packages/styles/dist.css b/packages/styles/dist.css index 88dec7ec..a03b105e 100644 --- a/packages/styles/dist.css +++ b/packages/styles/dist.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.1.13 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0}}}@layer theme{:root,:host{--color-red-500:oklch(63.7% .237 25.331);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-800:oklch(27.8% .033 256.848);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--font-weight-medium:500;--font-weight-bold:700;--radius-sm:.25rem;--radius-xl:.75rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--radius:var(--fui-radius);--color-primary:var(--fui-primary);--color-primary-hover:var(--fui-primary-hover);--color-primary-surface:var(--fui-primary-surface);--color-text:var(--fui-text);--color-text-muted:var(--fui-text-muted);--color-background:var(--fui-background);--color-border:var(--fui-border);--color-input:var(--fui-input);--color-error:var(--fui-error);--radius-card:var(--fui-radius-card)}:root{--fui-primary:var(--color-black);--fui-primary-hover:var(--fui-primary)}@supports (color:color-mix(in lab, red, red)){:root{--fui-primary-hover:color-mix(in oklab,var(--fui-primary)85%,transparent)}}:root{--fui-primary-surface:var(--color-white);--fui-text:var(--color-black);--fui-text-muted:var(--color-gray-800);--fui-background:var(--color-white);--fui-border:var(--color-gray-200);--fui-input:var(--color-gray-300);--fui-error:var(--color-red-500);--fui-radius:var(--radius-sm);--fui-radius-card:var(--radius-xl)}@media (prefers-color-scheme:dark){:root{--fui-primary:var(--color-white);--fui-primary-hover:var(--fui-primary)}@supports (color:color-mix(in lab, red, red)){:root{--fui-primary-hover:color-mix(in oklab,var(--fui-primary)85%,transparent)}}:root{--fui-primary-surface:var(--color-black);--fui-text:var(--color-white);--fui-text-muted:var(--color-gray-200);--fui-background:var(--color-black);--fui-border:var(--color-gray-200);--fui-input:var(--color-gray-300);--fui-error:var(--color-red-500);--fui-radius:var(--radius-sm);--fui-radius-card:var(--radius-xl)}}}@layer components{.fui-screen{max-width:var(--container-md);padding-top:calc(var(--spacing)*24);margin-inline:auto}:where(.fui-card>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}.fui-card{border-radius:var(--radius-card);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-border);background-color:var(--color-background);padding:calc(var(--spacing)*10)}:where(.fui-card__header>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}.fui-card__header{text-align:center}.fui-card__title{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--color-text)}.fui-card__subtitle{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-text-muted)}:where(.fui-form>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}.fui-form fieldset,.fui-form fieldset>label{gap:calc(var(--spacing)*2);color:var(--color-text);flex-direction:column;display:flex}.fui-form fieldset>label>span{gap:calc(var(--spacing)*3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);display:inline-flex}.fui-form .fui-form__action{padding-inline:calc(var(--spacing)*1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-text-muted)}@media (hover:hover){.fui-form .fui-form__action:hover{text-decoration-line:underline}}.fui-form fieldset>label>input{border-radius:var(--radius);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-input);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000}.fui-form fieldset>label>input:focus{outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-primary)}.fui-form fieldset>label>input[aria-invalid=true]{outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-error)}.fui-form .fui-form__error{text-align:center;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-error)}.fui-success{text-align:center;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.fui-button{justify-content:center;align-items:center;gap:calc(var(--spacing)*3);border-radius:var(--radius);background-color:var(--color-primary);width:100%;padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-primary-surface);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:flex}@media (hover:hover){.fui-button:hover{cursor:pointer;background-color:var(--color-primary-hover)}}.fui-button:disabled{cursor:not-allowed;opacity:.5}.fui-button--secondary{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-input);color:var(--color-text);background-color:#0000}@media (hover:hover){.fui-button--secondary:hover{border-color:var(--color-primary);background-color:var(--color-background)}}.fui-provider__button>svg{height:calc(var(--spacing)*5);width:calc(var(--spacing)*5)}.fui-divider{align-items:center;gap:calc(var(--spacing)*3);display:flex}.fui-divider__line{background-color:var(--color-border);flex:1;height:1px}.fui-divider__text{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-text-muted)}.fui-phone-input{align-items:center;gap:calc(var(--spacing)*2);display:flex}.fui-phone-input__number-input{border-radius:var(--radius);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-input);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000;flex:1}.fui-phone-input__number-input:focus{outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-primary)}.fui-phone-input__number-input[aria-invalid=true]{outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-error)}.fui-country-selector{width:80px;display:inline-block;position:relative}.fui-country-selector__wrapper{border-radius:var(--radius);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-input);background-color:#0000;align-items:center;display:flex;position:relative;overflow:hidden}.fui-country-selector__flag{pointer-events:none;left:calc(var(--spacing)*2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));position:absolute}.fui-country-selector select{cursor:pointer;appearance:none;width:100%;padding-block:calc(var(--spacing)*2);padding-right:calc(var(--spacing)*2);padding-left:calc(var(--spacing)*8);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:#0000;--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000}.fui-country-selector select:focus{outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-primary)}.fui-country-selector__dial-code{pointer-events:none;top:50%;left:calc(var(--spacing)*8);--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-text);position:absolute}}@layer utilities;@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0} \ No newline at end of file +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0}}}@layer theme{:root,:host{--color-red-500:oklch(63.7% .237 25.331);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-800:oklch(27.8% .033 256.848);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--font-weight-medium:500;--font-weight-bold:700;--radius-sm:.25rem;--radius-xl:.75rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--radius:var(--fui-radius);--color-primary:var(--fui-primary);--color-primary-hover:var(--fui-primary-hover);--color-primary-surface:var(--fui-primary-surface);--color-text:var(--fui-text);--color-text-muted:var(--fui-text-muted);--color-background:var(--fui-background);--color-border:var(--fui-border);--color-input:var(--fui-input);--color-error:var(--fui-error);--radius-card:var(--fui-radius-card)}:root{--fui-primary:var(--color-black);--fui-primary-hover:var(--fui-primary)}@supports (color:color-mix(in lab, red, red)){:root{--fui-primary-hover:color-mix(in oklab,var(--fui-primary)85%,transparent)}}:root{--fui-primary-surface:var(--color-white);--fui-text:var(--color-black);--fui-text-muted:var(--color-gray-800);--fui-background:var(--color-white);--fui-border:var(--color-gray-200);--fui-input:var(--color-gray-300);--fui-error:var(--color-red-500);--fui-radius:var(--radius-sm);--fui-radius-card:var(--radius-xl)}@media (prefers-color-scheme:dark){:root{--fui-primary:var(--color-white);--fui-primary-hover:var(--fui-primary)}@supports (color:color-mix(in lab, red, red)){:root{--fui-primary-hover:color-mix(in oklab,var(--fui-primary)85%,transparent)}}:root{--fui-primary-surface:var(--color-black);--fui-text:var(--color-white);--fui-text-muted:var(--color-gray-200);--fui-background:var(--color-black);--fui-border:var(--color-gray-200);--fui-input:var(--color-gray-300);--fui-error:var(--color-red-500);--fui-radius:var(--radius-sm);--fui-radius-card:var(--radius-xl)}}}@layer components{:where(.fui-screen){max-width:var(--container-md);padding-top:calc(var(--spacing)*24);margin-inline:auto}:where(:where(.fui-card)>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.fui-card){border-radius:var(--radius-card);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-border);background-color:var(--color-background);padding:calc(var(--spacing)*10)}:where(:where(.fui-card__header)>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.fui-card__header){text-align:center}:where(.fui-card__title){font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--color-text)}:where(.fui-card__subtitle){font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-text-muted)}:where(:where(.fui-form)>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.fui-form fieldset),:where(.fui-form fieldset>label){gap:calc(var(--spacing)*2);color:var(--color-text);flex-direction:column;display:flex}:where(.fui-form fieldset>label>span){gap:calc(var(--spacing)*3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);display:inline-flex}:where(.fui-form .fui-form__action){padding-inline:calc(var(--spacing)*1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-text-muted)}@media (hover:hover){:where(.fui-form .fui-form__action):hover{text-decoration-line:underline}}:where(.fui-form fieldset>label>input){border-radius:var(--radius);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-input);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000}:where(.fui-form fieldset>label>input):focus{outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-primary)}:where(.fui-form fieldset>label>input[aria-invalid=true]){outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-error)}:where(.fui-form .fui-form__error){text-align:center;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-error)}:where(.fui-success){text-align:center;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}:where(.fui-button){justify-content:center;align-items:center;gap:calc(var(--spacing)*3);border-radius:var(--radius);background-color:var(--color-primary);width:100%;padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-primary-surface);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:flex}@media (hover:hover){:where(.fui-button):hover{cursor:pointer;background-color:var(--color-primary-hover)}}:where(.fui-button):disabled{cursor:not-allowed;opacity:.5}:where(.fui-button--secondary){border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-input);color:var(--color-text);background-color:#0000}@media (hover:hover){:where(.fui-button--secondary):hover{border-color:var(--color-primary);background-color:var(--color-background)}}:where(.fui-provider__button>svg){height:calc(var(--spacing)*5);width:calc(var(--spacing)*5)}:where(.fui-divider){align-items:center;gap:calc(var(--spacing)*3);display:flex}:where(.fui-divider__line){background-color:var(--color-border);flex:1;height:1px}:where(.fui-divider__text){font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-text-muted)}:where(.fui-phone-input){align-items:center;gap:calc(var(--spacing)*2);display:flex}:where(.fui-phone-input__number-input){border-radius:var(--radius);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-input);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000;flex:1}:where(.fui-phone-input__number-input):focus{outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-primary)}:where(.fui-phone-input__number-input[aria-invalid=true]){outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-error)}:where(.fui-country-selector){width:80px;display:inline-block;position:relative}:where(.fui-country-selector__wrapper){border-radius:var(--radius);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-input);background-color:#0000;align-items:center;display:flex;position:relative;overflow:hidden}:where(.fui-country-selector__flag){pointer-events:none;left:calc(var(--spacing)*2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));position:absolute}:where(.fui-country-selector select){cursor:pointer;appearance:none;width:100%;padding-block:calc(var(--spacing)*2);padding-right:calc(var(--spacing)*2);padding-left:calc(var(--spacing)*8);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:#0000;--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000}:where(.fui-country-selector select):focus{outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-primary)}:where(.fui-country-selector__dial-code){pointer-events:none;top:50%;left:calc(var(--spacing)*8);--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-text);position:absolute}}@layer utilities;@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0} \ No newline at end of file diff --git a/packages/styles/src/base.css b/packages/styles/src/base.css index 8a5b03e7..e747f8fa 100644 --- a/packages/styles/src/base.css +++ b/packages/styles/src/base.css @@ -74,112 +74,113 @@ } @layer components { - .fui-screen { + /* Using :where() to reduce CSS specificity - allows users to easily override with their own Tailwind classes */ + :where(.fui-screen) { @apply pt-24 max-w-md mx-auto; } - .fui-card { + :where(.fui-card) { @apply bg-background p-10 border border-border rounded-card space-y-6; } - .fui-card__header { + :where(.fui-card__header) { @apply text-center space-y-1; } - .fui-card__title { + :where(.fui-card__title) { @apply text-xl font-bold text-text; } - .fui-card__subtitle { + :where(.fui-card__subtitle) { @apply text-sm text-text-muted; } - .fui-form { + :where(.fui-form) { @apply space-y-6; } - .fui-form fieldset, - .fui-form fieldset > label { + :where(.fui-form fieldset), + :where(.fui-form fieldset > label) { @apply flex flex-col gap-2 text-text; } - .fui-form fieldset > label > span { + :where(.fui-form fieldset > label > span) { @apply inline-flex gap-3 text-sm font-medium; } - .fui-form .fui-form__action { + :where(.fui-form .fui-form__action) { @apply px-1 hover:underline text-xs text-text-muted; } - .fui-form fieldset > label > input { + :where(.fui-form fieldset > label > input) { @apply border-1 border-input rounded px-2 py-2 text-sm focus:outline-2 focus:outline-primary shadow-xs bg-transparent; } - .fui-form fieldset > label > input[aria-invalid="true"] { + :where(.fui-form fieldset > label > input[aria-invalid="true"]) { @apply outline-error outline-2; } - .fui-form .fui-form__error { + :where(.fui-form .fui-form__error) { @apply text-error text-center text-xs; } - .fui-success { + :where(.fui-success) { @apply text-center text-xs; } - .fui-button { + :where(.fui-button) { @apply w-full flex items-center justify-center gap-3 px-4 py-2 rounded text-sm font-medium shadow-xs bg-primary text-primary-surface transition hover:bg-primary-hover hover:cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed; } - .fui-button--secondary { + :where(.fui-button--secondary) { @apply bg-transparent text-text border border-input hover:bg-background hover:border-primary; } - .fui-provider__button > svg { + :where(.fui-provider__button > svg) { @apply w-5 h-5; } - .fui-divider { + :where(.fui-divider) { @apply flex items-center gap-3; } - .fui-divider__line { + :where(.fui-divider__line) { @apply flex-1 h-px bg-border; } - .fui-divider__text { + :where(.fui-divider__text) { @apply text-text-muted text-xs; } - .fui-phone-input { + :where(.fui-phone-input) { @apply flex gap-2 items-center; } - .fui-phone-input__number-input { + :where(.fui-phone-input__number-input) { @apply border-1 border-input rounded px-2 py-2 text-sm focus:outline-2 focus:outline-primary shadow-xs bg-transparent flex-1; } - .fui-phone-input__number-input[aria-invalid="true"] { + :where(.fui-phone-input__number-input[aria-invalid="true"]) { @apply outline-error outline-2; } - .fui-country-selector { + :where(.fui-country-selector) { @apply relative inline-block w-[80px]; } - .fui-country-selector__wrapper { + :where(.fui-country-selector__wrapper) { @apply relative flex items-center border-1 border-input rounded bg-transparent overflow-hidden; } - .fui-country-selector__flag { + :where(.fui-country-selector__flag) { @apply absolute left-2 text-lg pointer-events-none; } - .fui-country-selector select { + :where(.fui-country-selector select) { @apply w-full pl-8 pr-2 py-2 text-sm focus:outline-2 focus:outline-primary shadow-xs bg-transparent appearance-none cursor-pointer text-transparent; } - .fui-country-selector__dial-code { + :where(.fui-country-selector__dial-code) { @apply absolute left-8 top-1/2 -translate-y-1/2 text-sm pointer-events-none text-text; } } diff --git a/packages/styles/src/base.test.ts b/packages/styles/src/base.test.ts new file mode 100644 index 00000000..b6907dd6 --- /dev/null +++ b/packages/styles/src/base.test.ts @@ -0,0 +1,319 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, beforeEach, afterEach } from "vitest"; + +/** + * CSS Specificity Test Suite + * + * This test suite verifies that our :where() pseudo-class implementation + * correctly reduces CSS specificity, allowing users to easily override + * component styles with their own Tailwind classes. + */ + +describe("Tailwind :where() pseudo-class", () => { + let testContainer: HTMLDivElement; + let styleElement: HTMLStyleElement; + + beforeEach(() => { + // Create a test container + testContainer = document.createElement("div"); + document.body.appendChild(testContainer); + + // Create a style element to inject our CSS + styleElement = document.createElement("style"); + document.head.appendChild(styleElement); + }); + + afterEach(() => { + // Clean up + document.body.removeChild(testContainer); + document.head.removeChild(styleElement); + }); + + /** + * Helper function to get computed styles for an element + */ + function getComputedStyleValue(element: HTMLElement, property: string): string { + return window.getComputedStyle(element).getPropertyValue(property); + } + + /** + * Helper function to create a test element with specific classes + */ + function createTestElement(classes: string): HTMLElement { + const element = document.createElement("div"); + element.className = classes; + testContainer.appendChild(element); + return element; + } + + describe("Basic :where() specificity test", () => { + it("should demonstrate that :where() has zero specificity", () => { + // Create CSS that shows specificity difference + styleElement.textContent = ` + /* Regular class selector (specificity: 0,0,1,0) */ + .regular-class { + color: red; + } + + /* :where() pseudo-class (specificity: 0,0,0,0) */ + :where(.where-class) { + color: blue; + } + + /* User class (specificity: 0,0,1,0) */ + .user-override { + color: green; + } + `; + + // Test regular class vs user override + const regularElement = createTestElement("regular-class user-override"); + expect(getComputedStyleValue(regularElement, "color")).toBe("rgb(0, 128, 0)"); // green wins + + // Test :where() class vs user override + const whereElement = createTestElement("where-class user-override"); + expect(getComputedStyleValue(whereElement, "color")).toBe("rgb(0, 128, 0)"); // green wins easily + }); + + it("should allow user classes to override :where() styles", () => { + // Inject CSS with :where() pseudo-class + styleElement.textContent = ` + :where(.fui-screen) { + padding-top: 6rem; + max-width: 28rem; + margin-left: auto; + margin-right: auto; + } + + /* User override classes */ + .pt-32 { padding-top: 8rem !important; } + .max-w-lg { max-width: 32rem !important; } + `; + + // Create element with both fui-screen and user override classes + const element = createTestElement("fui-screen pt-32 max-w-lg"); + + // User classes should override the :where() styles + expect(getComputedStyleValue(element, "padding-top")).toBe("8rem"); // pt-32 overrides pt-24 + expect(getComputedStyleValue(element, "max-width")).toBe("32rem"); // max-w-lg overrides max-w-md + }); + + it("should maintain :where() styles when no user overrides are present", () => { + styleElement.textContent = ` + :where(.fui-screen) { + padding-top: 6rem; + max-width: 28rem; + margin-left: auto; + margin-right: auto; + } + `; + + const element = createTestElement("fui-screen"); + + // Should maintain original styles + expect(getComputedStyleValue(element, "padding-top")).toBe("6rem"); + expect(getComputedStyleValue(element, "max-width")).toBe("28rem"); + }); + }); + + describe("Complex selectors with :where()", () => { + it("should handle nested selectors with :where() correctly", () => { + styleElement.textContent = ` + :where(.fui-form fieldset > label > input) { + border: 1px solid #d1d5db; + border-radius: 0.25rem; + padding: 0.5rem; + font-size: 0.875rem; + background-color: transparent; + } + + /* User override classes */ + .border-red-500 { border-color: rgb(239, 68, 68) !important; } + .rounded-lg { border-radius: 0.5rem !important; } + .p-3 { padding: 0.75rem !important; } + `; + + // Create nested structure + const form = document.createElement("div"); + form.className = "fui-form"; + const fieldset = document.createElement("fieldset"); + const label = document.createElement("label"); + const input = document.createElement("input"); + + fieldset.appendChild(label); + label.appendChild(input); + form.appendChild(fieldset); + testContainer.appendChild(form); + + // Add user override classes + input.className = "border-red-500 rounded-lg p-3"; + + // User classes should override + expect(getComputedStyleValue(input, "border-color")).toBe("rgb(239, 68, 68)"); // border-red-500 + expect(getComputedStyleValue(input, "border-radius")).toBe("0.5rem"); // rounded-lg + expect(getComputedStyleValue(input, "padding")).toBe("0.75rem"); // p-3 + }); + }); + + describe("Edge cases", () => { + it("should handle empty :where() selectors gracefully", () => { + styleElement.textContent = ` + :where() { + color: red; + } + `; + + const element = createTestElement("test-class"); + // Should not apply any styles (empty selector) + expect(getComputedStyleValue(element, "color")).not.toBe("rgb(255, 0, 0)"); + }); + + it("should handle :where() with attribute selectors", () => { + styleElement.textContent = ` + :where(.fui-form fieldset > label > input[aria-invalid="true"]) { + outline: 2px solid red; + } + + /* User override classes */ + .outline-blue-500 { outline-color: rgb(59, 130, 246) !important; } + .outline-4 { outline-width: 4px !important; } + `; + + const form = document.createElement("div"); + form.className = "fui-form"; + const fieldset = document.createElement("fieldset"); + const label = document.createElement("label"); + const input = document.createElement("input"); + input.setAttribute("aria-invalid", "true"); + + fieldset.appendChild(label); + label.appendChild(input); + form.appendChild(fieldset); + testContainer.appendChild(form); + + // Add user override + input.className = "outline-blue-500 outline-4"; + + // User classes should override + expect(getComputedStyleValue(input, "outline-color")).toBe("rgb(59, 130, 246)"); // outline-blue-500 + expect(getComputedStyleValue(input, "outline-width")).toBe("4px"); // outline-4 + }); + }); + + describe("Component integration tests", () => { + it("should verify layout components (screen, card) styles can be overridden", () => { + styleElement.textContent = ` + /* Base component styles with :where() for zero specificity */ + :where(.fui-screen) { padding-top: 6rem; max-width: 28rem; } + :where(.fui-card) { background-color: white; padding: 2.5rem; } + + /* User Tailwind override classes */ + .pt-8 { padding-top: 2rem !important; } + .max-w-lg { max-width: 32rem !important; } + .p-6 { padding: 1.5rem !important; } + .bg-gray-50 { background-color: rgb(249, 250, 251) !important; } + `; + + const screenElement = createTestElement("fui-screen pt-8 max-w-lg"); + const cardElement = createTestElement("fui-card p-6 bg-gray-50"); + + expect(getComputedStyleValue(screenElement, "padding-top")).toBe("2rem"); + expect(getComputedStyleValue(cardElement, "background-color")).toBe("rgb(249, 250, 251)"); + }); + + it("should verify button components styles can be overridden", () => { + styleElement.textContent = ` + /* Base component styles with :where() for zero specificity */ + :where(.fui-button) { background-color: black; color: white; padding: 0.5rem 1rem; } + :where(.fui-button--secondary) { background-color: transparent; border: 1px solid rgb(209, 213, 219); } + + /* User Tailwind override classes */ + .bg-blue-500 { background-color: rgb(59, 130, 246) !important; } + .px-6 { padding-left: 1.5rem !important; padding-right: 1.5rem !important; } + .bg-gray-100 { background-color: rgb(243, 244, 246) !important; } + `; + + const primaryButton = createTestElement("fui-button bg-blue-500 px-6"); + const secondaryButton = createTestElement("fui-button--secondary bg-gray-100"); + + expect(getComputedStyleValue(primaryButton, "background-color")).toBe("rgb(59, 130, 246)"); + expect(getComputedStyleValue(secondaryButton, "background-color")).toBe("rgb(243, 244, 246)"); + }); + + it("should verify form components styles can be overridden", () => { + styleElement.textContent = ` + /* Base component styles with :where() for zero specificity */ + :where(.fui-form fieldset > label > input) { border: 1px solid rgb(209, 213, 219); padding: 0.5rem; } + :where(.fui-form .fui-form__error) { color: rgb(239, 68, 68); } + + /* User Tailwind override classes */ + .border-gray-300 { border-color: rgb(209, 213, 219) !important; } + .px-3 { padding-left: 0.75rem !important; padding-right: 0.75rem !important; } + .text-red-600 { color: rgb(220, 38, 38) !important; } + `; + + const inputElement = createTestElement("border-gray-300 px-3"); + const errorElement = createTestElement("fui-form__error text-red-600"); + + expect(getComputedStyleValue(inputElement, "border-color")).toBe("rgb(209, 213, 219)"); + expect(getComputedStyleValue(errorElement, "color")).toBe("rgb(220, 38, 38)"); + }); + + it("should verify utility components (divider, success) styles can be overridden", () => { + styleElement.textContent = ` + /* Base component styles with :where() for zero specificity */ + :where(.fui-divider) { display: flex; gap: 0.75rem; } + :where(.fui-divider__line) { background-color: rgb(229, 231, 235); } + :where(.fui-success) { text-align: center; font-size: 0.75rem; } + + /* User Tailwind override classes */ + .gap-2 { gap: 0.5rem !important; } + .bg-gray-300 { background-color: rgb(209, 213, 219) !important; } + .text-left { text-align: left !important; } + .text-sm { font-size: 0.875rem !important; } + `; + + const dividerElement = createTestElement("fui-divider gap-2"); + const lineElement = createTestElement("fui-divider__line bg-gray-300"); + const successElement = createTestElement("fui-success text-left text-sm"); + + expect(getComputedStyleValue(dividerElement, "gap")).toBe("0.5rem"); + expect(getComputedStyleValue(lineElement, "background-color")).toBe("rgb(209, 213, 219)"); + expect(getComputedStyleValue(successElement, "text-align")).toBe("left"); + }); + + it("should verify complex input components (phone, country) styles can be overridden", () => { + styleElement.textContent = ` + /* Base component styles with :where() for zero specificity */ + :where(.fui-phone-input) { display: flex; gap: 0.5rem; } + :where(.fui-country-selector) { width: 80px; } + + /* User Tailwind override classes */ + .gap-2 { gap: 0.5rem !important; } + .w-24 { width: 6rem !important; } + .bg-gray-50 { background-color: rgb(249, 250, 251) !important; } + `; + + const phoneElement = createTestElement("fui-phone-input gap-2"); + const countryElement = createTestElement("fui-country-selector w-24 bg-gray-50"); + + expect(getComputedStyleValue(phoneElement, "gap")).toBe("0.5rem"); + expect(getComputedStyleValue(countryElement, "width")).toBe("6rem"); + }); + }); +});