@firebase-oss/ui-styles 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/dist.css +2 -0
- package/package.json +49 -0
- package/src/base.css +319 -0
- package/src/base.test.ts +319 -0
- package/src/index.test.ts +65 -0
- package/src/index.ts +16 -0
- package/src/themes/brutalist.css +30 -0
- package/src/themes/dark.css +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @firebase-oss/ui-styles
|
|
2
|
+
|
|
3
|
+
This package contains the styles for the FirebaseUI components.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### With tailwind
|
|
8
|
+
|
|
9
|
+
If you are using Tailwind CSS in your project, you can import the source files directly into your project.
|
|
10
|
+
|
|
11
|
+
```css
|
|
12
|
+
@import "tailwindcss";
|
|
13
|
+
@import "@firebase-oss/ui-styles/tailwind";
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### With CSS
|
|
17
|
+
|
|
18
|
+
Alternatively, you can import fully compiled CSS files into your project. This output contains both the tailwind styles and the FirebaseUI styles.
|
|
19
|
+
|
|
20
|
+
```jsx
|
|
21
|
+
import "@firebase-oss/ui-styles";
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Themes
|
|
25
|
+
|
|
26
|
+
The packages also exports themes which overrides the CSS variables with preset colors. These can be imported from your CSS:
|
|
27
|
+
|
|
28
|
+
```css
|
|
29
|
+
@import "tailwindcss";
|
|
30
|
+
@import "@firebase-oss/ui-styles/tailwind";
|
|
31
|
+
@import "@firebase-oss/ui-styles/themes/brualist";
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Building
|
|
35
|
+
|
|
36
|
+
To build the styles into a single CSS file, run the following command:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pnpm build
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This command will source the `src.css` file and output the compiled CSS to the `dist.css` file.
|
package/dist.css
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@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-semibold:600;--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-screen .fui-screen__children)>:not(:last-child)),:where(:where(.fui-content)>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}: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 div[data-input-label]){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:flex}:where(.fui-form fieldset label div[data-input-label]>div:first-child){flex-grow:1}: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);width:100%;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]){border-color:var(--color-error);outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-error)}:where(.fui-form fieldset label div[data-input-group]){align-items:center;gap:calc(var(--spacing)*2);display:flex}:where(.fui-form .fui-form__error){text-align:left;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-button.fui-provider__button){border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-border)}:where(.fui-provider__button>svg,.fui-provider__button>img){height:calc(var(--spacing)*5);width:calc(var(--spacing)*5)}:where(.fui-divider){margin-block:calc(var(--spacing)*4);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:120px;display:inline-block;position:relative}:where(.fui-country-selector__wrapper){border-radius:var(--radius);outline-style:var(--tw-outline-style);outline-width:1px;outline-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}:where(.fui-form fieldset label div[data-input-group]:has(input[aria-invalid=true]) .fui-country-selector),:where(.fui-form fieldset label div[data-input-group]:has(input[aria-invalid=true]) .fui-country-selector .fui-country-selector__wrapper){border-radius:var(--radius);outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--color-error)}:where(.fui-policies){text-align:center;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-text-muted)}:where(.fui-policies a,.fui-policies button){--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}@media (hover:hover){:where(.fui-policies a,.fui-policies button):hover{text-decoration-line:underline}}.fui-provider__button[data-provider=google\.com][data-themed=true]{--google-primary:#131314;--color-primary:var(--google-primary);--color-primary-hover:var(--google-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=google\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--google-primary)85%,transparent)}}.fui-provider__button[data-provider=google\.com][data-themed=true]{--color-primary-surface:#fff;--color-border:var(--google-primary)}.fui-provider__button[data-provider=google\.com][data-themed=neutral]{--google-primary:#f2f2f2;--color-primary:var(--google-primary);--color-primary-hover:var(--google-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=google\.com][data-themed=neutral]{--color-primary-hover:color-mix(in oklab,var(--google-primary)85%,transparent)}}.fui-provider__button[data-provider=google\.com][data-themed=neutral]{--color-primary-surface:#1f1f1f;--color-border:transparent}@media (prefers-color-scheme:dark){.fui-provider__button[data-provider=google\.com][data-themed=true]{--google-primary:#fff;--color-primary:var(--google-primary);--color-primary-hover:var(--google-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=google\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--google-primary)85%,transparent)}}.fui-provider__button[data-provider=google\.com][data-themed=true]{--color-primary-surface:#1f1f1f;--color-border:#747775}}.fui-provider__button[data-provider=facebook\.com][data-themed=true]{--facebook-primary:#1877f2;--color-primary:var(--facebook-primary);--color-primary-hover:var(--facebook-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=facebook\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--facebook-primary)85%,transparent)}}.fui-provider__button[data-provider=facebook\.com][data-themed=true]{--color-primary-surface:var(--color-white);--color-border:var(--facebook-primary)}.fui-provider__button[data-provider=apple\.com][data-themed=true]{--apple-primary:#000;--color-primary:var(--apple-primary);--color-primary-hover:var(--apple-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=apple\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--apple-primary)85%,transparent)}}.fui-provider__button[data-provider=apple\.com][data-themed=true]{--color-primary-surface:#fff;--color-border:var(--apple-primary)}@media (prefers-color-scheme:dark){.fui-provider__button[data-provider=apple\.com][data-themed=true]{--apple-primary:var(--color-white);--color-primary:var(--apple-primary);--color-primary-hover:var(--apple-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=apple\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--apple-primary)85%,transparent)}}.fui-provider__button[data-provider=apple\.com][data-themed=true]{--color-primary-surface:var(--color-black);--color-border:var(--color-white)}}.fui-provider__button[data-provider=github\.com][data-themed=true]{--github-primary:#000;--color-primary:var(--github-primary);--color-primary-hover:var(--github-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=github\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--github-primary)85%,transparent)}}.fui-provider__button[data-provider=github\.com][data-themed=true]{--color-primary-surface:#fff;--color-border:var(--github-primary)}@media (prefers-color-scheme:dark){.fui-provider__button[data-provider=github\.com][data-themed=true]{--github-primary:var(--color-white);--color-primary:var(--github-primary);--color-primary-hover:var(--github-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=github\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--github-primary)85%,transparent)}}.fui-provider__button[data-provider=github\.com][data-themed=true]{--color-primary-surface:var(--color-black);--color-border:var(--color-white)}}.fui-provider__button[data-provider=microsoft\.com][data-themed=true]{--microsoft-primary:#2f2f2f;--color-primary:var(--microsoft-primary);--color-primary-hover:var(--microsoft-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=microsoft\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--microsoft-primary)85%,transparent)}}.fui-provider__button[data-provider=microsoft\.com][data-themed=true]{--color-primary-surface:#fff;--color-border:var(--microsoft-primary)}@media (prefers-color-scheme:dark){.fui-provider__button[data-provider=microsoft\.com][data-themed=true]{--microsoft-primary:var(--color-white);--color-primary:var(--microsoft-primary);--color-primary-hover:var(--microsoft-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=microsoft\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--microsoft-primary)85%,transparent)}}.fui-provider__button[data-provider=microsoft\.com][data-themed=true]{--color-primary-surface:#5e5e5e;--color-border:var(--color-white)}}.fui-provider__button[data-provider=twitter\.com][data-themed=true]{--twitter-primary:#1da1f2;--color-primary:var(--twitter-primary);--color-primary-hover:var(--twitter-primary)}@supports (color:color-mix(in lab, red, red)){.fui-provider__button[data-provider=twitter\.com][data-themed=true]{--color-primary-hover:color-mix(in oklab,var(--twitter-primary)85%,transparent)}}.fui-provider__button[data-provider=twitter\.com][data-themed=true]{--color-primary-surface:#fff;--color-border:var(--twitter-primary)}}@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:"<percentage>";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:"<percentage>";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:"<length>";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}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@firebase-oss/ui-styles",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"style": "./dist.css",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./dist.css": "./dist.css",
|
|
16
|
+
"./tailwind": "./src/base.css",
|
|
17
|
+
"./themes/*": "./src/themes/*.css"
|
|
18
|
+
},
|
|
19
|
+
"sideEffects": [
|
|
20
|
+
"**/*.css"
|
|
21
|
+
],
|
|
22
|
+
"files": [
|
|
23
|
+
"dist.css",
|
|
24
|
+
"src"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"prepare": "pnpm run build",
|
|
28
|
+
"build": "tsup && pnpm run build:css",
|
|
29
|
+
"build:css": "pnpm dlx @tailwindcss/cli -i ./src.css -o ./dist.css --minify",
|
|
30
|
+
"build:local": "pnpm run build && pnpm pack",
|
|
31
|
+
"lint": "tsc --noEmit",
|
|
32
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
33
|
+
"clean": "rimraf dist",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"test:watch": "vitest",
|
|
36
|
+
"publish:tags": "sh -c 'TAG=\"${npm_package_name}@${npm_package_version}\"; git tag --list \"$TAG\" | grep . || git tag \"$TAG\"; git push origin \"$TAG\"'",
|
|
37
|
+
"release": "pnpm run build && pnpm pack --pack-destination ../../releases/"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"rimraf": "catalog:",
|
|
41
|
+
"tailwindcss": "catalog:",
|
|
42
|
+
"tsup": "catalog:",
|
|
43
|
+
"typescript": "catalog:",
|
|
44
|
+
"vitest": "catalog:"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"cva": "1.0.0-beta.4"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/base.css
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Google LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
@layer theme {
|
|
18
|
+
:root {
|
|
19
|
+
/* The primary color is used for the button and link colors */
|
|
20
|
+
--fui-primary: var(--color-black);
|
|
21
|
+
/* The primary hover color is used for the button and link colors when hovered */
|
|
22
|
+
--fui-primary-hover: --alpha(var(--fui-primary) / 85%);
|
|
23
|
+
/* The primary surface color is used for the button text color */
|
|
24
|
+
--fui-primary-surface: var(--color-white);
|
|
25
|
+
/* The text color used for body text */
|
|
26
|
+
--fui-text: var(--color-black);
|
|
27
|
+
/* The muted text color used for body text, such as subtitles */
|
|
28
|
+
--fui-text-muted: var(--color-gray-800);
|
|
29
|
+
/* The background color of the cards */
|
|
30
|
+
--fui-background: var(--color-white);
|
|
31
|
+
/* The border color used for none input fields */
|
|
32
|
+
--fui-border: var(--color-gray-200);
|
|
33
|
+
/* The input color used for input fields */
|
|
34
|
+
--fui-input: var(--color-gray-300);
|
|
35
|
+
/* The error color used for error messages */
|
|
36
|
+
--fui-error: var(--color-red-500);
|
|
37
|
+
/* The radius used for the input fields */
|
|
38
|
+
--fui-radius: var(--radius-sm);
|
|
39
|
+
/* The radius used for the cards */
|
|
40
|
+
--fui-radius-card: var(--radius-xl);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Apply dark mode styles when the dark variant is applied */
|
|
44
|
+
/* See https://tailwindcss.com/docs/dark-mode */
|
|
45
|
+
@variant dark {
|
|
46
|
+
:root {
|
|
47
|
+
--fui-primary: var(--color-white);
|
|
48
|
+
--fui-primary-hover: --alpha(var(--fui-primary) / 85%);
|
|
49
|
+
--fui-primary-surface: var(--color-black);
|
|
50
|
+
--fui-text: var(--color-white);
|
|
51
|
+
--fui-text-muted: var(--color-gray-200);
|
|
52
|
+
--fui-background: var(--color-black);
|
|
53
|
+
--fui-border: var(--color-gray-200);
|
|
54
|
+
--fui-input: var(--color-gray-300);
|
|
55
|
+
--fui-error: var(--color-red-500);
|
|
56
|
+
--fui-radius: var(--radius-sm);
|
|
57
|
+
--fui-radius-card: var(--radius-xl);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@theme {
|
|
63
|
+
--color-primary: var(--fui-primary);
|
|
64
|
+
--color-primary-hover: var(--fui-primary-hover);
|
|
65
|
+
--color-primary-surface: var(--fui-primary-surface);
|
|
66
|
+
--color-text: var(--fui-text);
|
|
67
|
+
--color-text-muted: var(--fui-text-muted);
|
|
68
|
+
--color-background: var(--fui-background);
|
|
69
|
+
--color-border: var(--fui-border);
|
|
70
|
+
--color-input: var(--fui-input);
|
|
71
|
+
--color-error: var(--fui-error);
|
|
72
|
+
--radius: var(--fui-radius);
|
|
73
|
+
--radius-card: var(--fui-radius-card);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@layer components {
|
|
77
|
+
/* Using :where() to reduce CSS specificity - allows users to easily override with their own Tailwind classes */
|
|
78
|
+
:where(.fui-screen) {
|
|
79
|
+
@apply pt-24 max-w-md mx-auto;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
:where(.fui-screen .fui-screen__children) {
|
|
83
|
+
@apply space-y-2;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
:where(.fui-content) {
|
|
87
|
+
@apply space-y-2;
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
:where(.fui-card) {
|
|
92
|
+
@apply bg-background p-10 border border-border rounded-card space-y-6;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
:where(.fui-card__header) {
|
|
96
|
+
@apply text-center space-y-1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
:where(.fui-card__title) {
|
|
100
|
+
@apply text-xl font-bold text-text;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
:where(.fui-card__subtitle) {
|
|
104
|
+
@apply text-sm text-text-muted;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
:where(.fui-form) {
|
|
108
|
+
@apply space-y-6;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
:where(.fui-form fieldset),
|
|
112
|
+
:where(.fui-form fieldset label) {
|
|
113
|
+
@apply flex flex-col gap-2 text-text;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
:where(.fui-form fieldset label div[data-input-label]) {
|
|
117
|
+
@apply flex gap-3 text-sm font-medium;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
:where(.fui-form fieldset label div[data-input-label] > div:first-child) {
|
|
121
|
+
@apply grow;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
:where(.fui-form .fui-form__action) {
|
|
125
|
+
@apply px-1 hover:underline text-xs text-text-muted;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
:where(.fui-form fieldset label input) {
|
|
129
|
+
@apply w-full border border-input rounded px-2 py-2 text-sm focus:outline-2 focus:outline-primary shadow-xs bg-transparent;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
:where(.fui-form fieldset label input[aria-invalid="true"]) {
|
|
133
|
+
@apply border-error outline-error outline-2;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
:where(.fui-form fieldset label div[data-input-group]) {
|
|
137
|
+
@apply flex items-center gap-2;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
:where(.fui-form .fui-form__error) {
|
|
141
|
+
@apply text-error text-left text-xs;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
:where(.fui-success) {
|
|
145
|
+
@apply text-center text-xs;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
:where(.fui-button) {
|
|
149
|
+
@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;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
:where(.fui-button--secondary) {
|
|
153
|
+
@apply bg-transparent text-text border border-input hover:bg-background hover:border-primary;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
:where(.fui-button.fui-provider__button) {
|
|
157
|
+
@apply border border-border;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
:where(.fui-provider__button > svg, .fui-provider__button > img) {
|
|
161
|
+
@apply w-5 h-5;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
:where(.fui-divider) {
|
|
165
|
+
@apply flex items-center gap-3 my-4;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
:where(.fui-divider__line) {
|
|
169
|
+
@apply flex-1 h-px bg-border;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
:where(.fui-divider__text) {
|
|
173
|
+
@apply text-text-muted text-xs;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
:where(.fui-phone-input) {
|
|
177
|
+
@apply flex gap-2 items-center;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
:where(.fui-phone-input__number-input) {
|
|
181
|
+
@apply border-1 border-input rounded px-2 py-2 text-sm focus:outline-2 focus:outline-primary shadow-xs bg-transparent flex-1;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
:where(.fui-phone-input__number-input[aria-invalid="true"]) {
|
|
185
|
+
@apply outline-error outline-2;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
:where(.fui-country-selector) {
|
|
189
|
+
@apply relative inline-block w-[120px];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
:where(.fui-country-selector__wrapper) {
|
|
193
|
+
@apply relative flex items-center outline-1 outline-input rounded bg-transparent overflow-hidden;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
:where(.fui-country-selector__flag) {
|
|
197
|
+
@apply absolute left-2 text-lg pointer-events-none;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
:where(.fui-country-selector select) {
|
|
201
|
+
@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;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
:where(.fui-country-selector__dial-code) {
|
|
205
|
+
@apply absolute left-8 top-1/2 -translate-y-1/2 text-sm pointer-events-none text-text;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
:where(.fui-form fieldset label div[data-input-group]:has(input[aria-invalid="true"]) .fui-country-selector) {
|
|
209
|
+
@apply outline-error outline-2 rounded;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
:where(.fui-form fieldset label div[data-input-group]:has(input[aria-invalid="true"]) .fui-country-selector .fui-country-selector__wrapper) {
|
|
213
|
+
@apply outline-error outline-2 rounded;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
:where(.fui-policies) {
|
|
217
|
+
@apply text-text-muted text-center text-xs;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
:where(.fui-policies a, .fui-policies button) {
|
|
221
|
+
@apply hover:underline font-semibold;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.fui-provider__button[data-provider="google.com"][data-themed="true"] {
|
|
225
|
+
--google-primary: #131314;
|
|
226
|
+
--color-primary: var(--google-primary);
|
|
227
|
+
--color-primary-hover: --alpha(var(--google-primary) / 85%);
|
|
228
|
+
--color-primary-surface: #FFFFFF;
|
|
229
|
+
--color-border: var(--google-primary);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.fui-provider__button[data-provider="google.com"][data-themed="neutral"] {
|
|
233
|
+
--google-primary: #F2F2F2;
|
|
234
|
+
--color-primary: var(--google-primary);
|
|
235
|
+
--color-primary-hover: --alpha(var(--google-primary) / 85%);
|
|
236
|
+
--color-primary-surface: #1F1F1F;
|
|
237
|
+
--color-border: transparent;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@variant dark {
|
|
241
|
+
.fui-provider__button[data-provider="google.com"][data-themed="true"] {
|
|
242
|
+
--google-primary: #FFFFFF;
|
|
243
|
+
--color-primary: var(--google-primary);
|
|
244
|
+
--color-primary-hover: --alpha(var(--google-primary) / 85%);
|
|
245
|
+
--color-primary-surface: #1F1F1F;
|
|
246
|
+
--color-border: #747775;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.fui-provider__button[data-provider="facebook.com"][data-themed="true"] {
|
|
251
|
+
--facebook-primary: #1877F2;
|
|
252
|
+
--color-primary: var(--facebook-primary);
|
|
253
|
+
--color-primary-hover: --alpha(var(--facebook-primary) / 85%);
|
|
254
|
+
--color-primary-surface: var(--color-white);
|
|
255
|
+
--color-border: var(--facebook-primary);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.fui-provider__button[data-provider="apple.com"][data-themed="true"] {
|
|
259
|
+
--apple-primary: #000000;
|
|
260
|
+
--color-primary: var(--apple-primary);
|
|
261
|
+
--color-primary-hover: --alpha(var(--apple-primary) / 85%);
|
|
262
|
+
--color-primary-surface: #FFFFFF;
|
|
263
|
+
--color-border: var(--apple-primary);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
@variant dark {
|
|
267
|
+
.fui-provider__button[data-provider="apple.com"][data-themed="true"] {
|
|
268
|
+
--apple-primary: var(--color-white);
|
|
269
|
+
--color-primary: var(--apple-primary);
|
|
270
|
+
--color-primary-hover: --alpha(var(--apple-primary) / 85%);
|
|
271
|
+
--color-primary-surface: var(--color-black);
|
|
272
|
+
--color-border: var(--color-white);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.fui-provider__button[data-provider="github.com"][data-themed="true"] {
|
|
277
|
+
--github-primary: #000000;
|
|
278
|
+
--color-primary: var(--github-primary);
|
|
279
|
+
--color-primary-hover: --alpha(var(--github-primary) / 85%);
|
|
280
|
+
--color-primary-surface: #FFFFFF;
|
|
281
|
+
--color-border: var(--github-primary);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
@variant dark {
|
|
285
|
+
.fui-provider__button[data-provider="github.com"][data-themed="true"] {
|
|
286
|
+
--github-primary: var(--color-white);
|
|
287
|
+
--color-primary: var(--github-primary);
|
|
288
|
+
--color-primary-hover: --alpha(var(--github-primary) / 85%);
|
|
289
|
+
--color-primary-surface: var(--color-black);
|
|
290
|
+
--color-border: var(--color-white);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.fui-provider__button[data-provider="microsoft.com"][data-themed="true"] {
|
|
295
|
+
--microsoft-primary: #2F2F2F;
|
|
296
|
+
--color-primary: var(--microsoft-primary);
|
|
297
|
+
--color-primary-hover: --alpha(var(--microsoft-primary) / 85%);
|
|
298
|
+
--color-primary-surface: #FFFFFF;
|
|
299
|
+
--color-border: var(--microsoft-primary);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
@variant dark {
|
|
303
|
+
.fui-provider__button[data-provider="microsoft.com"][data-themed="true"] {
|
|
304
|
+
--microsoft-primary: var(--color-white);
|
|
305
|
+
--color-primary: var(--microsoft-primary);
|
|
306
|
+
--color-primary-hover: --alpha(var(--microsoft-primary) / 85%);
|
|
307
|
+
--color-primary-surface: #5E5E5E;
|
|
308
|
+
--color-border: var(--color-white);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.fui-provider__button[data-provider="twitter.com"][data-themed="true"] {
|
|
313
|
+
--twitter-primary: #1DA1F2;
|
|
314
|
+
--color-primary: var(--twitter-primary);
|
|
315
|
+
--color-primary-hover: --alpha(var(--twitter-primary) / 85%);
|
|
316
|
+
--color-primary-surface: #FFFFFF;
|
|
317
|
+
--color-border: var(--twitter-primary);
|
|
318
|
+
}
|
|
319
|
+
}
|
package/src/base.test.ts
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Google LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* CSS Specificity Test Suite
|
|
21
|
+
*
|
|
22
|
+
* This test suite verifies that our :where() pseudo-class implementation
|
|
23
|
+
* correctly reduces CSS specificity, allowing users to easily override
|
|
24
|
+
* component styles with their own Tailwind classes.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
describe("Tailwind :where() pseudo-class", () => {
|
|
28
|
+
let testContainer: HTMLDivElement;
|
|
29
|
+
let styleElement: HTMLStyleElement;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
// Create a test container
|
|
33
|
+
testContainer = document.createElement("div");
|
|
34
|
+
document.body.appendChild(testContainer);
|
|
35
|
+
|
|
36
|
+
// Create a style element to inject our CSS
|
|
37
|
+
styleElement = document.createElement("style");
|
|
38
|
+
document.head.appendChild(styleElement);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
// Clean up
|
|
43
|
+
document.body.removeChild(testContainer);
|
|
44
|
+
document.head.removeChild(styleElement);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Helper function to get computed styles for an element
|
|
49
|
+
*/
|
|
50
|
+
function getComputedStyleValue(element: HTMLElement, property: string): string {
|
|
51
|
+
return window.getComputedStyle(element).getPropertyValue(property);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Helper function to create a test element with specific classes
|
|
56
|
+
*/
|
|
57
|
+
function createTestElement(classes: string): HTMLElement {
|
|
58
|
+
const element = document.createElement("div");
|
|
59
|
+
element.className = classes;
|
|
60
|
+
testContainer.appendChild(element);
|
|
61
|
+
return element;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe("Basic :where() specificity test", () => {
|
|
65
|
+
it("should demonstrate that :where() has zero specificity", () => {
|
|
66
|
+
// Create CSS that shows specificity difference
|
|
67
|
+
styleElement.textContent = `
|
|
68
|
+
/* Regular class selector (specificity: 0,0,1,0) */
|
|
69
|
+
.regular-class {
|
|
70
|
+
color: red;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* :where() pseudo-class (specificity: 0,0,0,0) */
|
|
74
|
+
:where(.where-class) {
|
|
75
|
+
color: blue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* User class (specificity: 0,0,1,0) */
|
|
79
|
+
.user-override {
|
|
80
|
+
color: green;
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
|
|
84
|
+
// Test regular class vs user override
|
|
85
|
+
const regularElement = createTestElement("regular-class user-override");
|
|
86
|
+
expect(getComputedStyleValue(regularElement, "color")).toBe("rgb(0, 128, 0)"); // green wins
|
|
87
|
+
|
|
88
|
+
// Test :where() class vs user override
|
|
89
|
+
const whereElement = createTestElement("where-class user-override");
|
|
90
|
+
expect(getComputedStyleValue(whereElement, "color")).toBe("rgb(0, 128, 0)"); // green wins easily
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should allow user classes to override :where() styles", () => {
|
|
94
|
+
// Inject CSS with :where() pseudo-class
|
|
95
|
+
styleElement.textContent = `
|
|
96
|
+
:where(.fui-screen) {
|
|
97
|
+
padding-top: 6rem;
|
|
98
|
+
max-width: 28rem;
|
|
99
|
+
margin-left: auto;
|
|
100
|
+
margin-right: auto;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* User override classes */
|
|
104
|
+
.pt-32 { padding-top: 8rem !important; }
|
|
105
|
+
.max-w-lg { max-width: 32rem !important; }
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
// Create element with both fui-screen and user override classes
|
|
109
|
+
const element = createTestElement("fui-screen pt-32 max-w-lg");
|
|
110
|
+
|
|
111
|
+
// User classes should override the :where() styles
|
|
112
|
+
expect(getComputedStyleValue(element, "padding-top")).toBe("8rem"); // pt-32 overrides pt-24
|
|
113
|
+
expect(getComputedStyleValue(element, "max-width")).toBe("32rem"); // max-w-lg overrides max-w-md
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should maintain :where() styles when no user overrides are present", () => {
|
|
117
|
+
styleElement.textContent = `
|
|
118
|
+
:where(.fui-screen) {
|
|
119
|
+
padding-top: 6rem;
|
|
120
|
+
max-width: 28rem;
|
|
121
|
+
margin-left: auto;
|
|
122
|
+
margin-right: auto;
|
|
123
|
+
}
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
const element = createTestElement("fui-screen");
|
|
127
|
+
|
|
128
|
+
// Should maintain original styles
|
|
129
|
+
expect(getComputedStyleValue(element, "padding-top")).toBe("6rem");
|
|
130
|
+
expect(getComputedStyleValue(element, "max-width")).toBe("28rem");
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("Complex selectors with :where()", () => {
|
|
135
|
+
it("should handle nested selectors with :where() correctly", () => {
|
|
136
|
+
styleElement.textContent = `
|
|
137
|
+
:where(.fui-form fieldset > label > input) {
|
|
138
|
+
border: 1px solid #d1d5db;
|
|
139
|
+
border-radius: 0.25rem;
|
|
140
|
+
padding: 0.5rem;
|
|
141
|
+
font-size: 0.875rem;
|
|
142
|
+
background-color: transparent;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* User override classes */
|
|
146
|
+
.border-red-500 { border-color: rgb(239, 68, 68) !important; }
|
|
147
|
+
.rounded-lg { border-radius: 0.5rem !important; }
|
|
148
|
+
.p-3 { padding: 0.75rem !important; }
|
|
149
|
+
`;
|
|
150
|
+
|
|
151
|
+
// Create nested structure
|
|
152
|
+
const form = document.createElement("div");
|
|
153
|
+
form.className = "fui-form";
|
|
154
|
+
const fieldset = document.createElement("fieldset");
|
|
155
|
+
const label = document.createElement("label");
|
|
156
|
+
const input = document.createElement("input");
|
|
157
|
+
|
|
158
|
+
fieldset.appendChild(label);
|
|
159
|
+
label.appendChild(input);
|
|
160
|
+
form.appendChild(fieldset);
|
|
161
|
+
testContainer.appendChild(form);
|
|
162
|
+
|
|
163
|
+
// Add user override classes
|
|
164
|
+
input.className = "border-red-500 rounded-lg p-3";
|
|
165
|
+
|
|
166
|
+
// User classes should override
|
|
167
|
+
expect(getComputedStyleValue(input, "border-color")).toBe("rgb(239, 68, 68)"); // border-red-500
|
|
168
|
+
expect(getComputedStyleValue(input, "border-radius")).toBe("0.5rem"); // rounded-lg
|
|
169
|
+
expect(getComputedStyleValue(input, "padding")).toBe("0.75rem"); // p-3
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("Edge cases", () => {
|
|
174
|
+
it("should handle empty :where() selectors gracefully", () => {
|
|
175
|
+
styleElement.textContent = `
|
|
176
|
+
:where() {
|
|
177
|
+
color: red;
|
|
178
|
+
}
|
|
179
|
+
`;
|
|
180
|
+
|
|
181
|
+
const element = createTestElement("test-class");
|
|
182
|
+
// Should not apply any styles (empty selector)
|
|
183
|
+
expect(getComputedStyleValue(element, "color")).not.toBe("rgb(255, 0, 0)");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("should handle :where() with attribute selectors", () => {
|
|
187
|
+
styleElement.textContent = `
|
|
188
|
+
:where(.fui-form fieldset > label > input[aria-invalid="true"]) {
|
|
189
|
+
outline: 2px solid red;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* User override classes */
|
|
193
|
+
.outline-blue-500 { outline-color: rgb(59, 130, 246) !important; }
|
|
194
|
+
.outline-4 { outline-width: 4px !important; }
|
|
195
|
+
`;
|
|
196
|
+
|
|
197
|
+
const form = document.createElement("div");
|
|
198
|
+
form.className = "fui-form";
|
|
199
|
+
const fieldset = document.createElement("fieldset");
|
|
200
|
+
const label = document.createElement("label");
|
|
201
|
+
const input = document.createElement("input");
|
|
202
|
+
input.setAttribute("aria-invalid", "true");
|
|
203
|
+
|
|
204
|
+
fieldset.appendChild(label);
|
|
205
|
+
label.appendChild(input);
|
|
206
|
+
form.appendChild(fieldset);
|
|
207
|
+
testContainer.appendChild(form);
|
|
208
|
+
|
|
209
|
+
// Add user override
|
|
210
|
+
input.className = "outline-blue-500 outline-4";
|
|
211
|
+
|
|
212
|
+
// User classes should override
|
|
213
|
+
expect(getComputedStyleValue(input, "outline-color")).toBe("rgb(59, 130, 246)"); // outline-blue-500
|
|
214
|
+
expect(getComputedStyleValue(input, "outline-width")).toBe("4px"); // outline-4
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe("Component integration tests", () => {
|
|
219
|
+
it("should verify layout components (screen, card) styles can be overridden", () => {
|
|
220
|
+
styleElement.textContent = `
|
|
221
|
+
/* Base component styles with :where() for zero specificity */
|
|
222
|
+
:where(.fui-screen) { padding-top: 6rem; max-width: 28rem; }
|
|
223
|
+
:where(.fui-card) { background-color: white; padding: 2.5rem; }
|
|
224
|
+
|
|
225
|
+
/* User Tailwind override classes */
|
|
226
|
+
.pt-8 { padding-top: 2rem !important; }
|
|
227
|
+
.max-w-lg { max-width: 32rem !important; }
|
|
228
|
+
.p-6 { padding: 1.5rem !important; }
|
|
229
|
+
.bg-gray-50 { background-color: rgb(249, 250, 251) !important; }
|
|
230
|
+
`;
|
|
231
|
+
|
|
232
|
+
const screenElement = createTestElement("fui-screen pt-8 max-w-lg");
|
|
233
|
+
const cardElement = createTestElement("fui-card p-6 bg-gray-50");
|
|
234
|
+
|
|
235
|
+
expect(getComputedStyleValue(screenElement, "padding-top")).toBe("2rem");
|
|
236
|
+
expect(getComputedStyleValue(cardElement, "background-color")).toBe("rgb(249, 250, 251)");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("should verify button components styles can be overridden", () => {
|
|
240
|
+
styleElement.textContent = `
|
|
241
|
+
/* Base component styles with :where() for zero specificity */
|
|
242
|
+
:where(.fui-button) { background-color: black; color: white; padding: 0.5rem 1rem; }
|
|
243
|
+
:where(.fui-button--secondary) { background-color: transparent; border: 1px solid rgb(209, 213, 219); }
|
|
244
|
+
|
|
245
|
+
/* User Tailwind override classes */
|
|
246
|
+
.bg-blue-500 { background-color: rgb(59, 130, 246) !important; }
|
|
247
|
+
.px-6 { padding-left: 1.5rem !important; padding-right: 1.5rem !important; }
|
|
248
|
+
.bg-gray-100 { background-color: rgb(243, 244, 246) !important; }
|
|
249
|
+
`;
|
|
250
|
+
|
|
251
|
+
const primaryButton = createTestElement("fui-button bg-blue-500 px-6");
|
|
252
|
+
const secondaryButton = createTestElement("fui-button--secondary bg-gray-100");
|
|
253
|
+
|
|
254
|
+
expect(getComputedStyleValue(primaryButton, "background-color")).toBe("rgb(59, 130, 246)");
|
|
255
|
+
expect(getComputedStyleValue(secondaryButton, "background-color")).toBe("rgb(243, 244, 246)");
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("should verify form components styles can be overridden", () => {
|
|
259
|
+
styleElement.textContent = `
|
|
260
|
+
/* Base component styles with :where() for zero specificity */
|
|
261
|
+
:where(.fui-form fieldset > label > input) { border: 1px solid rgb(209, 213, 219); padding: 0.5rem; }
|
|
262
|
+
:where(.fui-form .fui-form__error) { color: rgb(239, 68, 68); }
|
|
263
|
+
|
|
264
|
+
/* User Tailwind override classes */
|
|
265
|
+
.border-gray-300 { border-color: rgb(209, 213, 219) !important; }
|
|
266
|
+
.px-3 { padding-left: 0.75rem !important; padding-right: 0.75rem !important; }
|
|
267
|
+
.text-red-600 { color: rgb(220, 38, 38) !important; }
|
|
268
|
+
`;
|
|
269
|
+
|
|
270
|
+
const inputElement = createTestElement("border-gray-300 px-3");
|
|
271
|
+
const errorElement = createTestElement("fui-form__error text-red-600");
|
|
272
|
+
|
|
273
|
+
expect(getComputedStyleValue(inputElement, "border-color")).toBe("rgb(209, 213, 219)");
|
|
274
|
+
expect(getComputedStyleValue(errorElement, "color")).toBe("rgb(220, 38, 38)");
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("should verify utility components (divider, success) styles can be overridden", () => {
|
|
278
|
+
styleElement.textContent = `
|
|
279
|
+
/* Base component styles with :where() for zero specificity */
|
|
280
|
+
:where(.fui-divider) { display: flex; gap: 0.75rem; }
|
|
281
|
+
:where(.fui-divider__line) { background-color: rgb(229, 231, 235); }
|
|
282
|
+
:where(.fui-success) { text-align: center; font-size: 0.75rem; }
|
|
283
|
+
|
|
284
|
+
/* User Tailwind override classes */
|
|
285
|
+
.gap-2 { gap: 0.5rem !important; }
|
|
286
|
+
.bg-gray-300 { background-color: rgb(209, 213, 219) !important; }
|
|
287
|
+
.text-left { text-align: left !important; }
|
|
288
|
+
.text-sm { font-size: 0.875rem !important; }
|
|
289
|
+
`;
|
|
290
|
+
|
|
291
|
+
const dividerElement = createTestElement("fui-divider gap-2");
|
|
292
|
+
const lineElement = createTestElement("fui-divider__line bg-gray-300");
|
|
293
|
+
const successElement = createTestElement("fui-success text-left text-sm");
|
|
294
|
+
|
|
295
|
+
expect(getComputedStyleValue(dividerElement, "gap")).toBe("0.5rem");
|
|
296
|
+
expect(getComputedStyleValue(lineElement, "background-color")).toBe("rgb(209, 213, 219)");
|
|
297
|
+
expect(getComputedStyleValue(successElement, "text-align")).toBe("left");
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("should verify complex input components (phone, country) styles can be overridden", () => {
|
|
301
|
+
styleElement.textContent = `
|
|
302
|
+
/* Base component styles with :where() for zero specificity */
|
|
303
|
+
:where(.fui-phone-input) { display: flex; gap: 0.5rem; }
|
|
304
|
+
:where(.fui-country-selector) { width: 80px; }
|
|
305
|
+
|
|
306
|
+
/* User Tailwind override classes */
|
|
307
|
+
.gap-2 { gap: 0.5rem !important; }
|
|
308
|
+
.w-24 { width: 6rem !important; }
|
|
309
|
+
.bg-gray-50 { background-color: rgb(249, 250, 251) !important; }
|
|
310
|
+
`;
|
|
311
|
+
|
|
312
|
+
const phoneElement = createTestElement("fui-phone-input gap-2");
|
|
313
|
+
const countryElement = createTestElement("fui-country-selector w-24 bg-gray-50");
|
|
314
|
+
|
|
315
|
+
expect(getComputedStyleValue(phoneElement, "gap")).toBe("0.5rem");
|
|
316
|
+
expect(getComputedStyleValue(countryElement, "width")).toBe("6rem");
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Google LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE/2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { describe, it, expect } from "vitest";
|
|
18
|
+
import { buttonVariant, type ButtonVariant } from "./index";
|
|
19
|
+
|
|
20
|
+
describe("buttonVariant", () => {
|
|
21
|
+
it("should return base class when no variant is provided", () => {
|
|
22
|
+
const result = buttonVariant();
|
|
23
|
+
expect(result).toBe("fui-button");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should return base class with primary variant (default)", () => {
|
|
27
|
+
const result = buttonVariant({ variant: "primary" });
|
|
28
|
+
expect(result).toBe("fui-button");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should return base class with secondary variant", () => {
|
|
32
|
+
const result = buttonVariant({ variant: "secondary" });
|
|
33
|
+
expect(result).toBe("fui-button fui-button--secondary");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should handle empty variant object", () => {
|
|
37
|
+
const result = buttonVariant({});
|
|
38
|
+
expect(result).toBe("fui-button");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should handle undefined variant", () => {
|
|
42
|
+
const result = buttonVariant({ variant: undefined });
|
|
43
|
+
expect(result).toBe("fui-button");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("ButtonVariant type", () => {
|
|
48
|
+
it("should accept valid variant values", () => {
|
|
49
|
+
const primaryVariant: ButtonVariant = "primary";
|
|
50
|
+
const secondaryVariant: ButtonVariant = "secondary";
|
|
51
|
+
|
|
52
|
+
expect(primaryVariant).toBe("primary");
|
|
53
|
+
expect(secondaryVariant).toBe("secondary");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should work with buttonVariant function", () => {
|
|
57
|
+
const variants: ButtonVariant[] = ["primary", "secondary"];
|
|
58
|
+
|
|
59
|
+
variants.forEach((variant) => {
|
|
60
|
+
const result = buttonVariant({ variant });
|
|
61
|
+
expect(typeof result).toBe("string");
|
|
62
|
+
expect(result).toContain("fui-button");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "cva";
|
|
2
|
+
|
|
3
|
+
export const buttonVariant = cva({
|
|
4
|
+
base: "fui-button",
|
|
5
|
+
variants: {
|
|
6
|
+
variant: {
|
|
7
|
+
primary: "",
|
|
8
|
+
secondary: "fui-button--secondary",
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
defaultVariants: {
|
|
12
|
+
variant: "primary",
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export type ButtonVariant = VariantProps<typeof buttonVariant>["variant"];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Google LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
@layer theme {
|
|
18
|
+
:root {
|
|
19
|
+
--fui-primary: black;
|
|
20
|
+
--fui-primary-surface: yellow;
|
|
21
|
+
--fui-text: black;
|
|
22
|
+
--fui-text-muted: black;
|
|
23
|
+
--fui-background: yellow;
|
|
24
|
+
--fui-border: green;
|
|
25
|
+
--fui-input: green;
|
|
26
|
+
--fui-error: blue;
|
|
27
|
+
--fui-radius: 0;
|
|
28
|
+
--fui-radius-card: 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Google LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
@layer theme {
|
|
18
|
+
:root {
|
|
19
|
+
--fui-primary: var(--color-white);
|
|
20
|
+
--fui-primary-hover: --alpha(var(--fui-primary) / 85%);
|
|
21
|
+
--fui-primary-surface: var(--color-black);
|
|
22
|
+
--fui-text: var(--color-white);
|
|
23
|
+
--fui-text-muted: var(--color-gray-200);
|
|
24
|
+
--fui-background: var(--color-black);
|
|
25
|
+
--fui-border: var(--color-gray-200);
|
|
26
|
+
--fui-input: var(--color-gray-300);
|
|
27
|
+
--fui-error: var(--color-red-500);
|
|
28
|
+
--fui-radius: var(--radius-sm);
|
|
29
|
+
--fui-radius-card: var(--radius-xl);
|
|
30
|
+
}
|
|
31
|
+
}
|