Browse Source

client/web: adjust colors and some UI margins

Updates #10261

Signed-off-by: Sonia Appasamy <[email protected]>
Sonia Appasamy 2 years ago
parent
commit
7d61b827e8

+ 7 - 4
client/web/src/components/app.tsx

@@ -102,14 +102,17 @@ function Header({
   auth: AuthResponse
   newSession: () => Promise<void>
 }) {
-  const [loc] = useLocation()
+  const [loc, setLocation] = useLocation()
 
   return (
     <>
       <div className="flex justify-between mb-12">
         <div className="flex gap-3">
-          <TailscaleIcon />
-          <div className="inline text-neutral-800 text-lg font-medium leading-snug">
+          <TailscaleIcon
+            className="cursor-pointer"
+            onClick={() => setLocation("/")}
+          />
+          <div className="inline text-gray-800 text-lg font-medium leading-snug">
             {node.DomainName}
           </div>
         </div>
@@ -118,7 +121,7 @@ function Header({
       {loc !== "/" && loc !== "/update" && (
         <Link
           to="/"
-          className="text-indigo-500 font-medium leading-snug block mb-[10px]"
+          className="text-blue-500 font-medium leading-snug block mb-[10px]"
         >
           &larr; Back to {node.DeviceName}
         </Link>

+ 6 - 6
client/web/src/components/exit-node-selector.tsx

@@ -78,7 +78,7 @@ export default function ExitNodeSelector({
           {
             "border-gray-200": none,
             "bg-amber-600 border-amber-600": advertising,
-            "bg-indigo-500 border-indigo-500": using,
+            "bg-blue-500 border-blue-500": using,
           },
           className
         )}
@@ -87,7 +87,7 @@ export default function ExitNodeSelector({
           className={cx("flex-1 px-2 py-1.5 rounded-[1px]", {
             "bg-white hover:bg-stone-100": none,
             "bg-amber-600 hover:bg-orange-400": advertising,
-            "bg-indigo-500 hover:bg-indigo-400": using,
+            "bg-blue-500 hover:bg-blue-400": using,
             "cursor-not-allowed": disabled,
           })}
           onClick={() => setOpen(!open)}
@@ -95,7 +95,7 @@ export default function ExitNodeSelector({
         >
           <p
             className={cx(
-              "text-neutral-500 text-xs text-left font-medium uppercase tracking-wide mb-1",
+              "text-gray-500 text-xs text-left font-medium uppercase tracking-wide mb-1",
               { "bg-opacity-70 text-white": advertising || using }
             )}
           >
@@ -103,7 +103,7 @@ export default function ExitNodeSelector({
           </p>
           <div className="flex items-center">
             <p
-              className={cx("text-neutral-800", {
+              className={cx("text-gray-800", {
                 "text-white": advertising || using,
               })}
             >
@@ -128,7 +128,7 @@ export default function ExitNodeSelector({
           <button
             className={cx("px-3 py-2 rounded-sm text-white", {
               "bg-orange-400": advertising,
-              "bg-indigo-400": using,
+              "bg-blue-400": using,
               "cursor-not-allowed": disabled,
             })}
             onClick={(e) => {
@@ -213,7 +213,7 @@ function ExitNodeSelectorInner({
                   className="pb-1 mb-1 border-b last:border-b-0 last:mb-0"
                 >
                   {group.name && (
-                    <div className="px-4 py-2 text-neutral-500 text-xs font-medium uppercase tracking-wide">
+                    <div className="px-4 py-2 text-gray-500 text-xs font-medium uppercase tracking-wide">
                       {group.name}
                     </div>
                   )}

+ 4 - 4
client/web/src/components/login-toggle.tsx

@@ -143,7 +143,7 @@ function LoginPopoverContent({
       {!auth.canManageNode &&
         (!auth.viewerIdentity || auth.authNeeded === AuthType.tailscale ? (
           <>
-            <p className="text-neutral-500 text-xs">
+            <p className="text-gray-500 text-xs">
               {auth.viewerIdentity ? (
                 <>
                   To make changes, sign in to confirm your identity. This extra
@@ -158,7 +158,7 @@ function LoginPopoverContent({
             </p>
             <button
               className={cx(
-                "w-full px-3 py-2 bg-indigo-500 rounded shadow text-center text-white text-sm font-medium mt-2",
+                "w-full px-3 py-2 bg-blue-500 rounded shadow text-center text-white text-sm font-medium mt-2",
                 {
                   "mb-2": auth.viewerIdentity,
                   "cursor-not-allowed": !canConnectOverTS,
@@ -173,7 +173,7 @@ function LoginPopoverContent({
             </button>
           </>
         ) : (
-          <p className="text-neutral-500 text-xs">
+          <p className="text-gray-500 text-xs">
             You don’t have permission to make changes to this device, but you
             can view most of its details.
           </p>
@@ -183,7 +183,7 @@ function LoginPopoverContent({
           <hr className="my-2" />
           <div className="flex items-center">
             <User className="flex-shrink-0" />
-            <p className="text-neutral-500 text-xs ml-2">
+            <p className="text-gray-500 text-xs ml-2">
               We recognize you because you are accessing this page from{" "}
               <span className="font-medium">
                 {auth.viewerIdentity.nodeName || auth.viewerIdentity.nodeIP}

+ 5 - 5
client/web/src/components/views/device-details-view.tsx

@@ -23,7 +23,7 @@ export default function DeviceDetailsView({
     <>
       <h1 className="mb-10">Device details</h1>
       <div className="flex flex-col gap-4">
-        <div className="card">
+        <div className="-mx-5 card">
           <div className="flex items-center justify-between">
             <div className="flex items-center gap-2">
               <h1>{node.DeviceName}</h1>
@@ -36,7 +36,7 @@ export default function DeviceDetailsView({
             </div>
             <button
               className={cx(
-                "px-3 py-2 bg-stone-50 rounded shadow border border-stone-200 text-neutral-800 text-sm font-medium",
+                "px-3 py-2 bg-stone-50 rounded shadow border border-stone-200 text-gray-800 text-sm font-medium",
                 { "cursor-not-allowed": readonly }
               )}
               onClick={() =>
@@ -55,7 +55,7 @@ export default function DeviceDetailsView({
           !readonly && (
             <UpdateAvailableNotification details={node.ClientVersion} />
           )}
-        <div className="card">
+        <div className="-mx-5 card">
           <h2 className="mb-2">General</h2>
           <table>
             <tbody>
@@ -95,7 +95,7 @@ export default function DeviceDetailsView({
             </tbody>
           </table>
         </div>
-        <div className="card">
+        <div className="-mx-5 card">
           <h2 className="mb-2">Addresses</h2>
           <table>
             <tbody>
@@ -121,7 +121,7 @@ export default function DeviceDetailsView({
           </table>
         </div>
         <Control.AdminContainer
-          className="text-neutral-500 text-sm leading-tight text-center"
+          className="text-gray-500 text-sm leading-tight text-center"
           node={node}
         >
           Want even more details? Visit{" "}

+ 6 - 11
client/web/src/components/views/home-view.tsx

@@ -28,12 +28,10 @@ export default function HomeView({
             <div className="ml-3">
               <h1>{node.DeviceName}</h1>
               {/* TODO(sonia): display actual status */}
-              <p className="text-neutral-500 text-sm">Connected</p>
+              <p className="text-gray-500 text-sm">Connected</p>
             </div>
           </div>
-          <p className="text-neutral-800 text-lg leading-[25.20px]">
-            {node.IP}
-          </p>
+          <p className="text-gray-800 text-lg leading-[25.20px]">{node.IP}</p>
         </div>
         <ExitNodeSelector
           className="mb-5"
@@ -41,10 +39,7 @@ export default function HomeView({
           nodeUpdaters={nodeUpdaters}
           disabled={readonly}
         />
-        <Link
-          className="text-indigo-500 font-medium leading-snug"
-          to="/details"
-        >
+        <Link className="text-blue-500 font-medium leading-snug" to="/details">
           View device details &rarr;
         </Link>
       </div>
@@ -105,19 +100,19 @@ function SettingsCard({
     >
       <div>
         <div className="flex gap-2">
-          <p className="text-neutral-800 font-medium leading-tight mb-2">
+          <p className="text-gray-800 font-medium leading-tight mb-2">
             {title}
           </p>
           {badge && (
             <div className="h-5 px-2 bg-stone-100 rounded-full flex items-center gap-2">
               {badge.icon}
-              <div className="text-neutral-500 text-xs font-medium">
+              <div className="text-gray-500 text-xs font-medium">
                 {badge.text}
               </div>
             </div>
           )}
         </div>
-        <p className="text-neutral-500 text-sm leading-tight">{body}</p>
+        <p className="text-gray-500 text-sm leading-tight">{body}</p>
       </div>
       <div>
         <ArrowRight className="ml-3" />

+ 2 - 2
client/web/src/components/views/ssh-view.tsx

@@ -23,7 +23,7 @@ export default function SSHView({
         your tailnet to SSH into it.{" "}
         <a
           href="https://tailscale.com/kb/1193/tailscale-ssh/"
-          className="text-indigo-700"
+          className="text-blue-700"
           target="_blank"
           rel="noreferrer"
         >
@@ -46,7 +46,7 @@ export default function SSHView({
         </div>
       </div>
       <Control.AdminContainer
-        className="text-neutral-500 text-sm leading-tight"
+        className="text-gray-500 text-sm leading-tight"
         node={node}
       >
         Remember to make sure that the{" "}

+ 6 - 6
client/web/src/components/views/subnet-router-view.tsx

@@ -35,7 +35,7 @@ export default function SubnetRouterView({
         Add devices to your tailnet without installing Tailscale.{" "}
         <a
           href="https://tailscale.com/kb/1019/subnets/"
-          className="text-indigo-700"
+          className="text-blue-700"
           target="_blank"
           rel="noreferrer"
         >
@@ -52,7 +52,7 @@ export default function SubnetRouterView({
             value={inputText}
             onChange={(e) => setInputText(e.target.value)}
           />
-          <p className="my-2 h-6 text-neutral-500 text-sm leading-tight">
+          <p className="my-2 h-6 text-gray-500 text-sm leading-tight">
             Add multiple routes by providing a comma-separated list.
           </p>
           <Button
@@ -87,7 +87,7 @@ export default function SubnetRouterView({
                   className="flex justify-between items-center pb-2.5 mb-2.5 border-b border-b-gray-200 last:pb-0 last:mb-0 last:border-b-0"
                   key={r.Route}
                 >
-                  <div className="text-neutral-800 leading-snug">{r.Route}</div>
+                  <div className="text-gray-800 leading-snug">{r.Route}</div>
                   <div className="flex items-center gap-3">
                     <div className="flex items-center gap-1.5">
                       {r.Approved ? (
@@ -100,7 +100,7 @@ export default function SubnetRouterView({
                           Approved
                         </div>
                       ) : (
-                        <div className="text-neutral-500 text-sm leading-tight">
+                        <div className="text-gray-500 text-sm leading-tight">
                           Pending approval
                         </div>
                       )}
@@ -124,7 +124,7 @@ export default function SubnetRouterView({
               ))}
             </div>
             <Control.AdminContainer
-              className="mt-3 w-full text-center text-neutral-500 text-sm leading-tight"
+              className="mt-3 w-full text-center text-gray-500 text-sm leading-tight"
               node={node}
             >
               To approve routes, in the admin console go to{" "}
@@ -135,7 +135,7 @@ export default function SubnetRouterView({
             </Control.AdminContainer>
           </>
         ) : (
-          <div className="px-5 py-4 bg-stone-50 rounded-lg border border-gray-200 text-center text-neutral-500">
+          <div className="px-5 py-4 bg-stone-50 rounded-lg border border-gray-200 text-center text-gray-500">
             Not advertising any routes
           </div>
         )}

+ 153 - 12
client/web/src/index.css

@@ -14,13 +14,154 @@
       U+FFFD, U+E06B-E080, U+02E2, U+02E2, U+02B0, U+1D34, U+1D57, U+1D40,
       U+207F, U+1D3A, U+1D48, U+1D30, U+02B3, U+1D3F;
   }
+  
+  html {
+    /**
+     * These lines force the page to occupy the full width of the browser,
+     * ignoring the scrollbar, and prevent horizontal scrolling. This eliminates
+     * shifting when moving between pages with a scrollbar and those without, by
+     * ignoring the width of the scrollbar.
+     *
+     * It also disables horizontal scrolling of the body wholesale, so, as always
+     * avoid content flowing off the page.
+     */
+    width: 100vw;
+    overflow-x: hidden;
+  }
+
+  :root {
+    --color-white: 255 255 255;
+
+    --color-gray-0: 250 249 248;
+    --color-gray-50: 249 247 246;
+    --color-gray-100: 247 245 244;
+    --color-gray-200: 238 235 234;
+    --color-gray-300: 218 214 213;
+    --color-gray-400: 175 172 171;
+    --color-gray-500: 112 110 109;
+    --color-gray-600: 68 67 66;
+    --color-gray-700: 46 45 45;
+    --color-gray-800: 35 34 34;
+    --color-gray-900: 31 30 30;
+
+    --color-red-0: 255 246 244;
+    --color-red-50: 255 211 207;
+    --color-red-100: 255 177 171;
+    --color-red-200: 246 143 135;
+    --color-red-300: 228 108 99;
+    --color-red-400: 208 72 65;
+    --color-red-500: 178 45 48;
+    --color-red-600: 148 8 33;
+    --color-red-700: 118 0 18;
+    --color-red-800: 90 0 0;
+    --color-red-900: 66 0 0;
+
+    --color-yellow-0: 252 249 233;
+    --color-yellow-50: 248 229 185;
+    --color-yellow-100: 239 192 120;
+    --color-yellow-200: 229 153 62;
+    --color-yellow-300: 217 121 23;
+    --color-yellow-400: 187 85 4;
+    --color-yellow-500: 152 55 5;
+    --color-yellow-600: 118 43 11;
+    --color-yellow-700: 87 31 13;
+    --color-yellow-800: 58 22 7;
+    --color-yellow-900: 58 22 7;
+
+    --color-orange-0: 255 250 238;
+    --color-orange-50: 254 227 192;
+    --color-orange-100: 248 184 134;
+    --color-orange-200: 245 146 94;
+    --color-orange-300: 229 111 74;
+    --color-orange-400: 196 76 52;
+    --color-orange-500: 158 47 40;
+    --color-orange-600: 126 30 35;
+    --color-orange-700: 93 22 27;
+    --color-orange-800: 66 14 17;
+    --color-orange-900: 66 14 17;
+
+    --color-green-0: 239 255 237;
+    --color-green-50: 203 244 201;
+    --color-green-100: 133 217 150;
+    --color-green-200: 51 194 127;
+    --color-green-300: 30 166 114;
+    --color-green-400: 9 130 93;
+    --color-green-500: 14 98 69;
+    --color-green-600: 13 75 59;
+    --color-green-700: 11 55 51;
+    --color-green-800: 8 36 41;
+    --color-green-900: 8 36 41;
+
+    --color-blue-0: 240 245 255;
+    --color-blue-50: 206 222 253;
+    --color-blue-100: 173 199 252;
+    --color-blue-200: 133 170 245;
+    --color-blue-300: 108 148 236;
+    --color-blue-400: 90 130 222;
+    --color-blue-500: 75 112 204;
+    --color-blue-600: 63 93 179;
+    --color-blue-700: 50 73 148;
+    --color-blue-800: 37 53 112;
+    --color-blue-900: 25 34 74;
+
+    --color-text-base: rgb(var(--color-gray-800) / 1);
+    --color-text-muted: rgb(var(--color-gray-500) / 1);
+    --color-text-disabled: rgb(var(--color-gray-400) / 1);
+    --color-text-primary: rgb(var(--color-blue-600) / 1);
+    --color-text-warning: rgb(var(--color-orange-600) / 1);
+    --color-text-danger: rgb(var(--color-red-600) / 1);
+
+    --color-bg-app: rgb(var(--color-gray-100) / 1);
+    --color-bg-menu-item-hover: rgb(var(--color-gray-100) / 1);
+
+    --color-border-base: rgb(var(--color-gray-200) / 1);
+  }
+
+  html,
+  body,
+  #app-root {
+    min-height: 100vh;
+  }
+
+  body {
+    @apply text-text-base font-sans w-full antialiased;
+    font-size: 16px;
+    line-height: 1.4;
+    letter-spacing: -0.015em; /* Inter is a little loose by default */
+    text-rendering: optimizeLegibility;
+    -webkit-text-size-adjust: 100%;
+    -webkit-tap-highlight-color: transparent;
+  }
+
+  ::selection {
+    background-color: rgba(97, 122, 255, 0.2);
+  }
+
+  strong {
+    @apply font-semibold;
+  }
+
+  button {
+    text-align: inherit; /* don't center buttons by default */
+    letter-spacing: inherit; /* inherit existing letter spacing, rather than using browser defaults */
+    vertical-align: top; /* fix alignment of display: inline-block buttons */
+  }
+
+  a:focus,
+  button:focus {
+    outline: none;
+  }
+  a:focus-visible,
+  button:focus-visible {
+    outline: auto;
+  }
 
   h1 {
-    @apply text-neutral-800 text-[22px] font-medium leading-[30.80px];
+    @apply text-gray-800 text-[22px] font-medium leading-[30.80px];
   }
 
   h2 {
-    @apply text-neutral-500 text-sm font-medium uppercase leading-tight tracking-wide;
+    @apply text-gray-500 text-sm font-medium uppercase leading-tight tracking-wide;
   }
 }
 
@@ -29,23 +170,23 @@
     @apply p-5 bg-white rounded-lg border border-gray-200;
   }
   .card h1 {
-    @apply text-neutral-800 text-lg font-medium leading-snug;
+    @apply text-gray-800 text-lg font-medium leading-snug;
   }
   .card h2 {
-    @apply text-neutral-500 text-xs font-semibold uppercase tracking-wide;
+    @apply text-gray-500 text-xs font-semibold uppercase tracking-wide;
   }
   .card tbody {
     @apply flex flex-col gap-2;
   }
   .card td:first-child {
-    @apply w-40 text-neutral-500 text-sm leading-tight flex-shrink-0;
+    @apply w-40 text-gray-500 text-sm leading-tight flex-shrink-0;
   }
   .card td:last-child {
-    @apply text-neutral-800 text-sm leading-tight;
+    @apply text-gray-800 text-sm leading-tight;
   }
 
   .description {
-    @apply text-neutral-500 leading-snug;
+    @apply text-gray-500 leading-snug;
   }
 
   /**
@@ -53,21 +194,21 @@
    * You can use the -large and -small modifiers for size variants.
    */
   .toggle {
-    @apply appearance-none relative w-10 h-5 rounded-full bg-neutral-300 cursor-pointer;
+    @apply appearance-none relative w-10 h-5 rounded-full bg-gray-300 cursor-pointer;
     transition: background-color 200ms ease-in-out;
   }
 
   .toggle:disabled {
-    @apply bg-neutral-200;
+    @apply bg-gray-200;
     @apply cursor-not-allowed;
   }
 
   .toggle:checked {
-    @apply bg-indigo-500;
+    @apply bg-blue-500;
   }
 
   .toggle:checked:disabled {
-    @apply bg-indigo-300;
+    @apply bg-blue-300;
   }
 
   .toggle:focus {
@@ -86,7 +227,7 @@
   }
 
   .toggle:checked:disabled::after {
-    @apply bg-indigo-50;
+    @apply bg-blue-50;
   }
 
   .toggle:enabled:active::after {

+ 3 - 3
client/web/src/ui/button.tsx

@@ -16,9 +16,9 @@ export default function Button(props: Props) {
       className={cx(
         "px-3 py-2 rounded shadow justify-center items-center gap-2.5 inline-flex font-medium",
         {
-          "bg-indigo-500 text-white": intent === "primary" && !disabled,
-          "bg-indigo-400 text-indigo-200": intent === "primary" && disabled,
-          "bg-stone-50 shadow border border-stone-200 text-neutral-800":
+          "bg-blue-500 text-white": intent === "primary" && !disabled,
+          "bg-blue-400 text-blue-200": intent === "primary" && disabled,
+          "bg-stone-50 shadow border border-stone-200 text-gray-800":
             intent === "secondary",
           "cursor-not-allowed": disabled,
         },

+ 85 - 0
client/web/styles.json

@@ -0,0 +1,85 @@
+{
+  "colors": {
+    "transparent": "transparent",
+    "current": "currentColor",
+    "white": "rgb(var(--color-white) / <alpha-value>)",
+    "gray": {
+      "0": "rgb(var(--color-gray-0) / <alpha-value>)",
+      "50": "rgb(var(--color-gray-50) / <alpha-value>)",
+      "100": "rgb(var(--color-gray-100) / <alpha-value>)",
+      "200": "rgb(var(--color-gray-200) / <alpha-value>)",
+      "300": "rgb(var(--color-gray-300) / <alpha-value>)",
+      "400": "rgb(var(--color-gray-400) / <alpha-value>)",
+      "500": "rgb(var(--color-gray-500) / <alpha-value>)",
+      "600": "rgb(var(--color-gray-600) / <alpha-value>)",
+      "700": "rgb(var(--color-gray-700) / <alpha-value>)",
+      "800": "rgb(var(--color-gray-800) / <alpha-value>)",
+      "900": "rgb(var(--color-gray-900) / <alpha-value>)"
+    },
+    "blue": {
+      "0": "rgb(var(--color-blue-0) / <alpha-value>)",
+      "50": "rgb(var(--color-blue-50) / <alpha-value>)",
+      "100": "rgb(var(--color-blue-100) / <alpha-value>)",
+      "200": "rgb(var(--color-blue-200) / <alpha-value>)",
+      "300": "rgb(var(--color-blue-300) / <alpha-value>)",
+      "400": "rgb(var(--color-blue-400) / <alpha-value>)",
+      "500": "rgb(var(--color-blue-500) / <alpha-value>)",
+      "600": "rgb(var(--color-blue-600) / <alpha-value>)",
+      "700": "rgb(var(--color-blue-700) / <alpha-value>)",
+      "800": "rgb(var(--color-blue-800) / <alpha-value>)",
+      "900": "rgb(var(--color-blue-900) / <alpha-value>)"
+    },
+    "green": {
+      "0": "rgb(var(--color-green-0) / <alpha-value>)",
+      "50": "rgb(var(--color-green-50) / <alpha-value>)",
+      "100": "rgb(var(--color-green-100) / <alpha-value>)",
+      "200": "rgb(var(--color-green-200) / <alpha-value>)",
+      "300": "rgb(var(--color-green-300) / <alpha-value>)",
+      "400": "rgb(var(--color-green-400) / <alpha-value>)",
+      "500": "rgb(var(--color-green-500) / <alpha-value>)",
+      "600": "rgb(var(--color-green-600) / <alpha-value>)",
+      "700": "rgb(var(--color-green-700) / <alpha-value>)",
+      "800": "rgb(var(--color-green-800) / <alpha-value>)",
+      "900": "rgb(var(--color-green-900) / <alpha-value>)"
+    },
+    "red": {
+      "0": "rgb(var(--color-red-0) / <alpha-value>)",
+      "50": "rgb(var(--color-red-50) / <alpha-value>)",
+      "100": "rgb(var(--color-red-100) / <alpha-value>)",
+      "200": "rgb(var(--color-red-200) / <alpha-value>)",
+      "300": "rgb(var(--color-red-300) / <alpha-value>)",
+      "400": "rgb(var(--color-red-400) / <alpha-value>)",
+      "500": "rgb(var(--color-red-500) / <alpha-value>)",
+      "600": "rgb(var(--color-red-600) / <alpha-value>)",
+      "700": "rgb(var(--color-red-700) / <alpha-value>)",
+      "800": "rgb(var(--color-red-800) / <alpha-value>)",
+      "900": "rgb(var(--color-red-900) / <alpha-value>)"
+    },
+    "yellow": {
+      "0": "rgb(var(--color-yellow-0) / <alpha-value>)",
+      "50": "rgb(var(--color-yellow-50) / <alpha-value>)",
+      "100": "rgb(var(--color-yellow-100) / <alpha-value>)",
+      "200": "rgb(var(--color-yellow-200) / <alpha-value>)",
+      "300": "rgb(var(--color-yellow-300) / <alpha-value>)",
+      "400": "rgb(var(--color-yellow-400) / <alpha-value>)",
+      "500": "rgb(var(--color-yellow-500) / <alpha-value>)",
+      "600": "rgb(var(--color-yellow-600) / <alpha-value>)",
+      "700": "rgb(var(--color-yellow-700) / <alpha-value>)",
+      "800": "rgb(var(--color-yellow-800) / <alpha-value>)",
+      "900": "rgb(var(--color-yellow-900) / <alpha-value>)"
+    },
+    "orange": {
+      "0": "rgb(var(--color-orange-0) / <alpha-value>)",
+      "50": "rgb(var(--color-orange-50) / <alpha-value>)",
+      "100": "rgb(var(--color-orange-100) / <alpha-value>)",
+      "200": "rgb(var(--color-orange-200) / <alpha-value>)",
+      "300": "rgb(var(--color-orange-300) / <alpha-value>)",
+      "400": "rgb(var(--color-orange-400) / <alpha-value>)",
+      "500": "rgb(var(--color-orange-500) / <alpha-value>)",
+      "600": "rgb(var(--color-orange-600) / <alpha-value>)",
+      "700": "rgb(var(--color-orange-700) / <alpha-value>)",
+      "800": "rgb(var(--color-orange-800) / <alpha-value>)",
+      "900": "rgb(var(--color-orange-900) / <alpha-value>)"
+    }
+  }
+}

+ 72 - 3
client/web/tailwind.config.js

@@ -1,9 +1,13 @@
 const plugin = require("tailwindcss/plugin")
+const styles = require("./styles.json")
 
-/** @type {import('tailwindcss').Config} */
 module.exports = {
-  content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
   theme: {
+    screens: {
+      sm: "420px",
+      md: "768px",
+      lg: "1024px",
+    },
     fontFamily: {
       sans: [
         "Inter",
@@ -29,8 +33,66 @@ module.exports = {
       semibold: "600",
       bold: "700",
     },
-    extend: {},
+    colors: styles.colors,
+    extend: {
+      colors: {
+        ...styles.colors,
+        "bg-app": "var(--color-bg-app)",
+        "bg-menu-item-hover": "var(--color-bg-menu-item-hover)",
+
+        "border-base": "var(--color-border-base)",
+
+        "text-base": "var(--color-text-base)",
+        "text-muted": "var(--color-text-muted)",
+        "text-disabled": "var(--color-text-disabled)",
+        "text-primary": "var(--color-text-primary)",
+        "text-warning": "var(--color-text-warning)",
+        "text-danger": "var(--color-text-danger)",
+      },
+      borderColor: {
+        DEFAULT: "var(--color-border-base)",
+      },
+      boxShadow: {
+        dialog: "0 10px 40px rgba(0,0,0,0.12), 0 0 16px rgba(0,0,0,0.08)",
+        form: "0 1px 1px rgba(0, 0, 0, 0.04)",
+        soft: "0 4px 12px 0 rgba(0, 0, 0, 0.03)",
+        popover:
+          "0 0 0 1px rgba(136, 152, 170, 0.1), 0 15px 35px 0 rgba(49, 49, 93, 0.1), 0 5px 15px 0 rgba(0, 0, 0, 0.08)",
+      },
+      animation: {
+        "scale-in": "scale-in 120ms cubic-bezier(0.16, 1, 0.3, 1)",
+        "scale-out": "scale-out 120ms cubic-bezier(0.16, 1, 0.3, 1)",
+      },
+      transformOrigin: {
+        "radix-hovercard": "var(--radix-hover-card-content-transform-origin)",
+        "radix-popover": "var(--radix-popover-content-transform-origin)",
+        "radix-tooltip": "var(--radix-tooltip-content-transform-origin)",
+      },
+      keyframes: {
+        "scale-in": {
+          "0%": {
+            transform: "scale(0.94)",
+            opacity: "0",
+          },
+          "100%": {
+            transform: "scale(1)",
+            opacity: "1",
+          },
+        },
+        "scale-out": {
+          "0%": {
+            transform: "scale(1)",
+            opacity: "1",
+          },
+          "100%": {
+            transform: "scale(0.94)",
+            opacity: "0",
+          },
+        },
+      },
+    },
   },
+
   plugins: [
     plugin(function ({ addVariant }) {
       addVariant("state-open", [
@@ -41,6 +103,13 @@ module.exports = {
         '&[data-state="closed"]',
         '[data-state="closed"] &',
       ])
+      addVariant("state-delayed-open", [
+        '&[data-state="delayed-open"]',
+        '[data-state="delayed-open"] &',
+      ])
+      addVariant("state-active", ['&[data-state="active"]'])
+      addVariant("state-inactive", ['&[data-state="inactive"]'])
     }),
   ],
+  content: ["./src/**/*.html", "./src/**/*.{ts,tsx}", "./index.html"],
 }