Repository: facebook/stylex (JavaScript)

Issue:
API for CSS variable fallback
## Feature request

I would like to be able to use fallback for css variables.

## Suggestion 1

I suggest an API like:

```tsx
const styles = stylex.create({
  slot: {
    color: stylex.fallback(vars.specificSlotColor, vars.textColor),
  },
});

<div {...stylex.props(styles.slot)}>text</div>
```

Which would be compiled to:

```css
.className {
  color: var(--specificSlotColor, --textColor);
}
```

## Suggestion 2

Today, `null` and `undefined` values for style properties are not used:

> Using null for a non-default condition has no effect and should be considered invalid.
https://stylexjs.com/docs/learn/styling-ui/defining-styles/#combining-conditions

Also, in this example:

```tsx
const vars = stylex.defineVars({
  textColor: 'black',
  specificSlotColor: undefined,
});

const styles = stylex.create({
  text: {
    color: vars.textColor,
  },
  specificSlot: {
    color: vars.specificSlotColor,
  },
});

<div {...stylex.props(styles.text, styles.specificSlot)}>text</div>
```

The final color is `undefined` because "The last style applied always wins".

I suggest that "the last *defined* style applied always wins", so that the `textColor` fallback is used.

## Workaround

```tsx
<div {...stylex.props(styles.text)}>
  <div {...stylex.props(styles.specificSlot)}>text</div>
</div>
```


Discussion: The existing `stylex.firstThatWorks` will soon be extended to support this use-case.
Shouldn't `undefined` be disallowed for values in `defineVars`, and then the type system can tell you if you're using a var that's actually `undefined`? The value in `defineVars` is the fallback as currently defined.
@necolas There are cases when you need a variable to either be defined or not defined. This is particularly useful with Container Style Queries. For this use-case `undefined` and `null` are valid values for `defineVars`, although you need to manually type cast them to `null | string` so that a theme can set a value for it.
Has this been released yet? 

When I try:

```
backgroundColor: stylex.firstThatWorks("color-mix(in srgb, currentColor 20%, transparent)", cssVars.accent),
```

I get: 

<img width="378" alt="image" src="https://github.com/facebook/stylex/assets/833067/e264e71a-d583-437f-b234-83a7315af560">


Looks like its unpacking the string as an array for some reason? 🤔 


@pnegahdar Please share the definition for `cssVars` so I can test this and verify.
Reopening Issue to investigate bug report.
@nmn The basic example seems to cause the issue:

```
const cssVars = stylex.defineVars({
    accent: "#42D80D"
})
```

Then used with a color-mix style: 

```
const styles = stlyex.create({
    root: {
        backgroundColor: stylex.firstThatWorks("color-mix(in srgb, currentColor 20%, transparent)", cssVars.accent),
   }
})
```



Fix (patch):
diff --git a/packages/shared/src/stylex-first-that-works.js b/packages/shared/src/stylex-first-that-works.js
index 0aaf7108d..cba5ec866 100644
--- a/packages/shared/src/stylex-first-that-works.js
+++ b/packages/shared/src/stylex-first-that-works.js
@@ -7,8 +7,40 @@
  * @flow strict
  */
 
-export default function stylexFirstThatWorks<T: string>(
-  ...args: $ReadOnlyArray<T>
-): $ReadOnlyArray<T> {
-  return [...args].reverse();
+const isVar = (arg: string) =>
+  typeof arg === 'string' && arg.match(/^var\(--[a-zA-Z0-9-_]+\)$/);
+
+export default function stylexFirstThatWorks(
+  ...args: $ReadOnlyArray<string>
+): $ReadOnlyArray<string> | string {
+  const firstVar = args.findIndex(isVar);
+
+  if (firstVar === -1) {
+    return [...args].reverse();
+  }
+  const priorities = args.slice(0, firstVar).reverse();
+  const rest = args.slice(firstVar);
+  const firstNonVar = rest.findIndex((arg) => !isVar(arg));
+  const varParts = rest
+    .slice(0, firstNonVar === -1 ? rest.length : firstNonVar + 1)
+    .reverse();
+
+  const vars = varParts.map((arg) => (isVar(arg) ? arg.slice(4, -1) : arg));
+
+  const returnValue = [
+    vars.reduce<string>(
+      (soFar, varName) =>
+        soFar
+          ? `var(${varName}, ${String(soFar)})`
+          : varName.startsWith('--')
+            ? `var(${varName})`
+            : varName,
+      '',
+    ),
+    ...priorities,
+  ];
+  if (returnValue.length === 1) {
+    return returnValue[0];
+  }
+  return returnValue;
 }