| author | |
| committer | |
| log | 018d7f2775daabd2ef07898af29586915a0b7f67 |
| tree | bd856f872c5a262273a9f4a07d64fb4114db4bd9 |
| parent | 9257bd860a44ce2a324c917ca66a1a81e73607bd |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>111 files changed, 3636 insertions(+), 1221 deletions(-)
apps/mobile/generated-uniwind-default-theme-variables.json created+136| ... | @@ -0,0 +1,136 @@ | ||
| 1 | { | ||
| 2 | "light": { | ||
| 3 | "--color-screen": "#f2f2f7", | ||
| 4 | "--color-sheet": "rgba(242, 242, 247, 0.98)", | ||
| 5 | "--color-sheet-solid": "#f2f2f7", | ||
| 6 | "--color-card": "#ffffff", | ||
| 7 | "--color-card-alt": "#f5f5f5", | ||
| 8 | "--color-card-translucent": "rgba(255, 255, 255, 0.8)", | ||
| 9 | "--color-foreground": "#262626", | ||
| 10 | "--color-foreground-secondary": "#525252", | ||
| 11 | "--color-foreground-muted": "#737373", | ||
| 12 | "--color-foreground-tertiary": "#8e8e93", | ||
| 13 | "--color-border": "rgba(0, 0, 0, 0.08)", | ||
| 14 | "--color-border-subtle": "rgba(0, 0, 0, 0.06)", | ||
| 15 | "--color-separator": "rgba(0, 0, 0, 0.04)", | ||
| 16 | "--color-subtle": "rgba(0, 0, 0, 0.04)", | ||
| 17 | "--color-subtle-strong": "rgba(0, 0, 0, 0.08)", | ||
| 18 | "--color-inline-skill-background": "rgba(217, 70, 239, 0.12)", | ||
| 19 | "--color-inline-skill-border": "rgba(217, 70, 239, 0.25)", | ||
| 20 | "--color-inline-skill-foreground": "#a21caf", | ||
| 21 | "--color-primary": "#262626", | ||
| 22 | "--color-primary-foreground": "#ffffff", | ||
| 23 | "--color-primary-shadow": "#000000", | ||
| 24 | "--color-secondary": "#ffffff", | ||
| 25 | "--color-secondary-foreground": "#262626", | ||
| 26 | "--color-secondary-border": "rgba(0, 0, 0, 0.08)", | ||
| 27 | "--color-switch-active-track": "#34c759", | ||
| 28 | "--color-switch-active-thumb": "#ffffff", | ||
| 29 | "--color-switch-inactive-track": "rgba(0, 0, 0, 0.08)", | ||
| 30 | "--color-switch-inactive-thumb": "#8e8e93", | ||
| 31 | "--color-danger": "#fef2f2", | ||
| 32 | "--color-danger-border": "rgba(239, 68, 68, 0.12)", | ||
| 33 | "--color-danger-foreground": "#dc2626", | ||
| 34 | "--color-input": "#ffffff", | ||
| 35 | "--color-input-border": "rgba(0, 0, 0, 0.1)", | ||
| 36 | "--color-sidebar-search": "rgba(118, 118, 128, 0.12)", | ||
| 37 | "--color-placeholder": "#737373", | ||
| 38 | "--color-icon": "#262626", | ||
| 39 | "--color-icon-muted": "#525252", | ||
| 40 | "--color-icon-subtle": "#a3a3a3", | ||
| 41 | "--color-header": "rgba(255, 255, 255, 0.97)", | ||
| 42 | "--color-header-border": "rgba(0, 0, 0, 0.06)", | ||
| 43 | "--color-glass-surface": "rgba(255, 255, 255, 0.72)", | ||
| 44 | "--color-glass-tint": "rgba(255, 255, 255, 0.18)", | ||
| 45 | "--color-status-bar": "#f2f2f7", | ||
| 46 | "--color-md-body": "#111111", | ||
| 47 | "--color-md-strong": "#000000", | ||
| 48 | "--color-md-link": "#2563eb", | ||
| 49 | "--color-md-blockquote-border": "rgba(0, 0, 0, 0.08)", | ||
| 50 | "--color-md-blockquote-bg": "rgba(0, 0, 0, 0.02)", | ||
| 51 | "--color-md-code-bg": "rgba(0, 0, 0, 0.04)", | ||
| 52 | "--color-md-code-text": "#262626", | ||
| 53 | "--color-md-user-code-bg": "rgba(255, 255, 255, 0.22)", | ||
| 54 | "--color-md-user-code-text": "#ffffff", | ||
| 55 | "--color-md-user-fence-bg": "rgba(0, 0, 0, 0.16)", | ||
| 56 | "--color-md-user-fence-text": "#ffffff", | ||
| 57 | "--color-md-hr": "rgba(0, 0, 0, 0.08)", | ||
| 58 | "--color-user-bubble": "#007aff", | ||
| 59 | "--color-user-bubble-foreground": "#ffffff", | ||
| 60 | "--color-user-bubble-foreground-muted": "rgba(255, 255, 255, 0.78)", | ||
| 61 | "--color-user-bubble-skill-foreground": "#f0abfc", | ||
| 62 | "--color-backdrop": "rgba(0, 0, 0, 0.22)", | ||
| 63 | "--color-drawer": "rgba(255, 255, 255, 0.99)", | ||
| 64 | "--color-drawer-shadow": "rgba(0, 0, 0, 0.12)", | ||
| 65 | "--color-dot-separator": "rgba(0, 0, 0, 0.2)", | ||
| 66 | "--color-wordmark": "#262626", | ||
| 67 | "--color-chevron": "rgba(0, 0, 0, 0.2)" | ||
| 68 | }, | ||
| 69 | "dark": { | ||
| 70 | "--color-screen": "#0a0a0a", | ||
| 71 | "--color-sheet": "rgba(14, 14, 14, 0.98)", | ||
| 72 | "--color-sheet-solid": "#0e0e0e", | ||
| 73 | "--color-card": "#171717", | ||
| 74 | "--color-card-alt": "#1c1c1c", | ||
| 75 | "--color-card-translucent": "rgba(17, 17, 17, 0.8)", | ||
| 76 | "--color-foreground": "#f5f5f5", | ||
| 77 | "--color-foreground-secondary": "#a3a3a3", | ||
| 78 | "--color-foreground-muted": "#8e8e93", | ||
| 79 | "--color-foreground-tertiary": "#636366", | ||
| 80 | "--color-border": "rgba(255, 255, 255, 0.06)", | ||
| 81 | "--color-border-subtle": "rgba(255, 255, 255, 0.04)", | ||
| 82 | "--color-separator": "rgba(255, 255, 255, 0.03)", | ||
| 83 | "--color-subtle": "rgba(255, 255, 255, 0.04)", | ||
| 84 | "--color-subtle-strong": "rgba(255, 255, 255, 0.08)", | ||
| 85 | "--color-inline-skill-background": "rgba(217, 70, 239, 0.12)", | ||
| 86 | "--color-inline-skill-border": "rgba(217, 70, 239, 0.25)", | ||
| 87 | "--color-inline-skill-foreground": "#f0abfc", | ||
| 88 | "--color-primary": "#f5f5f5", | ||
| 89 | "--color-primary-foreground": "#0a0a0a", | ||
| 90 | "--color-primary-shadow": "#000000", | ||
| 91 | "--color-secondary": "rgba(255, 255, 255, 0.04)", | ||
| 92 | "--color-secondary-foreground": "#f5f5f5", | ||
| 93 | "--color-secondary-border": "rgba(255, 255, 255, 0.06)", | ||
| 94 | "--color-switch-active-track": "#30d158", | ||
| 95 | "--color-switch-active-thumb": "#ffffff", | ||
| 96 | "--color-switch-inactive-track": "rgba(255, 255, 255, 0.06)", | ||
| 97 | "--color-switch-inactive-thumb": "#8e8e93", | ||
| 98 | "--color-danger": "rgba(239, 68, 68, 0.14)", | ||
| 99 | "--color-danger-border": "rgba(248, 113, 113, 0.18)", | ||
| 100 | "--color-danger-foreground": "#fca5a5", | ||
| 101 | "--color-input": "#141414", | ||
| 102 | "--color-input-border": "rgba(255, 255, 255, 0.08)", | ||
| 103 | "--color-sidebar-search": "rgba(118, 118, 128, 0.24)", | ||
| 104 | "--color-placeholder": "#8e8e93", | ||
| 105 | "--color-icon": "#f5f5f5", | ||
| 106 | "--color-icon-muted": "#a3a3a3", | ||
| 107 | "--color-icon-subtle": "#8e8e93", | ||
| 108 | "--color-header": "rgba(10, 10, 10, 0.97)", | ||
| 109 | "--color-header-border": "rgba(255, 255, 255, 0.06)", | ||
| 110 | "--color-glass-surface": "rgba(23, 23, 23, 0.78)", | ||
| 111 | "--color-glass-tint": "rgba(23, 23, 23, 0.24)", | ||
| 112 | "--color-status-bar": "#0a0a0a", | ||
| 113 | "--color-md-body": "#e5e5e5", | ||
| 114 | "--color-md-strong": "#f5f5f5", | ||
| 115 | "--color-md-link": "#60a5fa", | ||
| 116 | "--color-md-blockquote-border": "rgba(255, 255, 255, 0.1)", | ||
| 117 | "--color-md-blockquote-bg": "rgba(255, 255, 255, 0.03)", | ||
| 118 | "--color-md-code-bg": "rgba(255, 255, 255, 0.06)", | ||
| 119 | "--color-md-code-text": "#e5e5e5", | ||
| 120 | "--color-md-user-code-bg": "rgba(255, 255, 255, 0.18)", | ||
| 121 | "--color-md-user-code-text": "#ffffff", | ||
| 122 | "--color-md-user-fence-bg": "rgba(0, 0, 0, 0.28)", | ||
| 123 | "--color-md-user-fence-text": "#ffffff", | ||
| 124 | "--color-md-hr": "rgba(255, 255, 255, 0.08)", | ||
| 125 | "--color-user-bubble": "#0a84ff", | ||
| 126 | "--color-user-bubble-foreground": "#ffffff", | ||
| 127 | "--color-user-bubble-foreground-muted": "rgba(255, 255, 255, 0.78)", | ||
| 128 | "--color-user-bubble-skill-foreground": "#f0abfc", | ||
| 129 | "--color-backdrop": "rgba(0, 0, 0, 0.48)", | ||
| 130 | "--color-drawer": "rgba(14, 14, 14, 0.99)", | ||
| 131 | "--color-drawer-shadow": "rgba(0, 0, 0, 0.32)", | ||
| 132 | "--color-dot-separator": "rgba(255, 255, 255, 0.2)", | ||
| 133 | "--color-wordmark": "#f5f5f5", | ||
| 134 | "--color-chevron": "rgba(255, 255, 255, 0.2)" | ||
| 135 | } | ||
| 136 | } | ||
apps/mobile/generated-uniwind-theme-names.json created+12| ... | @@ -0,0 +1,12 @@ | ||
| 1 | [ | ||
| 2 | "t3-chat-light", | ||
| 3 | "t3-chat-dark", | ||
| 4 | "grove-light", | ||
| 5 | "grove-dark", | ||
| 6 | "ocean-light", | ||
| 7 | "ocean-dark", | ||
| 8 | "ember-light", | ||
| 9 | "ember-dark", | ||
| 10 | "iris-light", | ||
| 11 | "iris-dark" | ||
| 12 | ] | ||
apps/mobile/generated-uniwind-themes.css created+1374| ... | @@ -0,0 +1,1374 @@ | ||
| 1 | /* Generated by scripts/generate-uniwind-themes.mts. Do not edit manually. */ | ||
| 2 | @layer theme { | ||
| 3 | :root { | ||
| 4 | @variant light { | ||
| 5 | --color-adaptive-amber-50-950-a40: oklch(98.7% 0.022 95.277); | ||
| 6 | --color-adaptive-amber-200-900-a60: oklch(92.4% 0.12 95.746); | ||
| 7 | --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 12%); | ||
| 8 | --color-adaptive-amber-700-300: oklch(55.5% 0.163 48.998); | ||
| 9 | --color-adaptive-amber-700-400: oklch(55.5% 0.163 48.998); | ||
| 10 | --color-adaptive-amber-800-200: oklch(47.3% 0.137 46.201); | ||
| 11 | --color-adaptive-blue-50-blue-400-a14: oklch(97% 0.014 254.604); | ||
| 12 | --color-adaptive-blue-300-a50-blue-400-a28: oklch(80.9% 0.105 251.813 / 50%); | ||
| 13 | --color-adaptive-blue-500-a20-blue-400-a15: oklch(62.3% 0.214 259.815 / 20%); | ||
| 14 | --color-adaptive-blue-500-400: oklch(62.3% 0.214 259.815); | ||
| 15 | --color-adaptive-blue-600-400: oklch(54.6% 0.245 262.881); | ||
| 16 | --color-adaptive-black-a10-a25: rgb(0 0 0 / 10%); | ||
| 17 | --color-adaptive-black-a15-a35: rgb(0 0 0 / 15%); | ||
| 18 | --color-adaptive-emerald-500-a12-a16: oklch(69.6% 0.17 162.48 / 12%); | ||
| 19 | --color-adaptive-emerald-600-400: oklch(59.6% 0.145 163.225); | ||
| 20 | --color-adaptive-emerald-700-300: oklch(50.8% 0.118 165.612); | ||
| 21 | --color-adaptive-indigo-500-a12-a16: oklch(58.5% 0.233 277.117 / 12%); | ||
| 22 | --color-adaptive-indigo-600-300: oklch(51.1% 0.262 276.966); | ||
| 23 | --color-adaptive-indigo-700-300: oklch(45.7% 0.24 277.023); | ||
| 24 | --color-adaptive-neutral-100-900: oklch(97% 0 0); | ||
| 25 | --color-adaptive-neutral-200-700-a60: oklch(92.2% 0 0); | ||
| 26 | --color-adaptive-neutral-200-800: oklch(92.2% 0 0); | ||
| 27 | --color-adaptive-neutral-200-a70-white-a8: oklch(92.2% 0 0 / 70%); | ||
| 28 | --color-adaptive-neutral-200-white-a6: oklch(92.2% 0 0); | ||
| 29 | --color-adaptive-neutral-200-white-a8: oklch(92.2% 0 0); | ||
| 30 | --color-adaptive-neutral-200-a80-white-a8: oklch(92.2% 0 0 / 80%); | ||
| 31 | --color-adaptive-neutral-300-a60-white-a12: oklch(87% 0 0 / 60%); | ||
| 32 | --color-adaptive-neutral-400-500: oklch(70.8% 0 0); | ||
| 33 | --color-adaptive-neutral-400-a60-500-a60: oklch(70.8% 0 0 / 60%); | ||
| 34 | --color-adaptive-neutral-400-a80-500-a80: oklch(70.8% 0 0 / 80%); | ||
| 35 | --color-adaptive-neutral-500-a10-a16: oklch(55.6% 0 0 / 10%); | ||
| 36 | --color-adaptive-neutral-500-400: oklch(55.6% 0 0); | ||
| 37 | --color-adaptive-neutral-500-500: oklch(55.6% 0 0); | ||
| 38 | --color-adaptive-neutral-600-300: oklch(43.9% 0 0); | ||
| 39 | --color-adaptive-neutral-600-400: oklch(43.9% 0 0); | ||
| 40 | --color-adaptive-neutral-950-50: oklch(14.5% 0 0); | ||
| 41 | --color-adaptive-red-50-950-a80: oklch(97.1% 0.013 17.38); | ||
| 42 | --color-adaptive-red-200-800: oklch(88.5% 0.062 18.334); | ||
| 43 | --color-adaptive-red-600-a80-400-a80: oklch(57.7% 0.245 27.325 / 80%); | ||
| 44 | --color-adaptive-red-700-300: oklch(50.5% 0.213 27.518); | ||
| 45 | --color-adaptive-rose-100-500-a18: oklch(94.1% 0.03 12.58); | ||
| 46 | --color-adaptive-rose-100-a80-500-a12: oklch(94.1% 0.03 12.58 / 80%); | ||
| 47 | --color-adaptive-rose-300-a70-400-a28: oklch(81% 0.117 11.638 / 70%); | ||
| 48 | --color-adaptive-rose-500-a12-a16: oklch(64.5% 0.246 16.439 / 12%); | ||
| 49 | --color-adaptive-rose-500-400: oklch(64.5% 0.246 16.439); | ||
| 50 | --color-adaptive-rose-600-400: oklch(58.6% 0.253 17.585); | ||
| 51 | --color-adaptive-rose-700-300: oklch(51.4% 0.222 16.935); | ||
| 52 | --color-adaptive-sky-500-a12-a16: oklch(68.5% 0.169 237.323 / 12%); | ||
| 53 | --color-adaptive-sky-600-400: oklch(58.8% 0.158 241.966); | ||
| 54 | --color-adaptive-sky-700-300: oklch(50% 0.134 242.749); | ||
| 55 | --color-adaptive-violet-500-a12-a16: oklch(60.6% 0.25 292.717 / 12%); | ||
| 56 | --color-adaptive-violet-600-400: oklch(54.1% 0.281 293.009); | ||
| 57 | --color-adaptive-violet-700-300: oklch(49.1% 0.27 292.581); | ||
| 58 | --color-adaptive-white-neutral-950-a70: #fff; | ||
| 59 | --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%); | ||
| 60 | --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938); | ||
| 61 | --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786); | ||
| 62 | } | ||
| 63 | |||
| 64 | @variant dark { | ||
| 65 | --color-adaptive-amber-50-950-a40: oklch(27.9% 0.077 45.635 / 40%); | ||
| 66 | --color-adaptive-amber-200-900-a60: oklch(41.4% 0.112 45.904 / 60%); | ||
| 67 | --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 16%); | ||
| 68 | --color-adaptive-amber-700-300: oklch(87.9% 0.169 91.605); | ||
| 69 | --color-adaptive-amber-700-400: oklch(82.8% 0.189 84.429); | ||
| 70 | --color-adaptive-amber-800-200: oklch(92.4% 0.12 95.746); | ||
| 71 | --color-adaptive-blue-50-blue-400-a14: oklch(70.7% 0.165 254.624 / 14%); | ||
| 72 | --color-adaptive-blue-300-a50-blue-400-a28: oklch(70.7% 0.165 254.624 / 28%); | ||
| 73 | --color-adaptive-blue-500-a20-blue-400-a15: oklch(70.7% 0.165 254.624 / 15%); | ||
| 74 | --color-adaptive-blue-500-400: oklch(70.7% 0.165 254.624); | ||
| 75 | --color-adaptive-blue-600-400: oklch(70.7% 0.165 254.624); | ||
| 76 | --color-adaptive-black-a10-a25: rgb(0 0 0 / 25%); | ||
| 77 | --color-adaptive-black-a15-a35: rgb(0 0 0 / 35%); | ||
| 78 | --color-adaptive-emerald-500-a12-a16: oklch(69.6% 0.17 162.48 / 16%); | ||
| 79 | --color-adaptive-emerald-600-400: oklch(76.5% 0.177 163.223); | ||
| 80 | --color-adaptive-emerald-700-300: oklch(84.5% 0.143 164.978); | ||
| 81 | --color-adaptive-indigo-500-a12-a16: oklch(58.5% 0.233 277.117 / 16%); | ||
| 82 | --color-adaptive-indigo-600-300: oklch(78.5% 0.115 274.713); | ||
| 83 | --color-adaptive-indigo-700-300: oklch(78.5% 0.115 274.713); | ||
| 84 | --color-adaptive-neutral-100-900: oklch(20.5% 0 0); | ||
| 85 | --color-adaptive-neutral-200-700-a60: oklch(37.1% 0 0 / 60%); | ||
| 86 | --color-adaptive-neutral-200-800: oklch(26.9% 0 0); | ||
| 87 | --color-adaptive-neutral-200-a70-white-a8: rgb(255 255 255 / 8%); | ||
| 88 | --color-adaptive-neutral-200-white-a6: rgb(255 255 255 / 6%); | ||
| 89 | --color-adaptive-neutral-200-white-a8: rgb(255 255 255 / 8%); | ||
| 90 | --color-adaptive-neutral-200-a80-white-a8: rgb(255 255 255 / 8%); | ||
| 91 | --color-adaptive-neutral-300-a60-white-a12: rgb(255 255 255 / 12%); | ||
| 92 | --color-adaptive-neutral-400-500: oklch(55.6% 0 0); | ||
| 93 | --color-adaptive-neutral-400-a60-500-a60: oklch(55.6% 0 0 / 60%); | ||
| 94 | --color-adaptive-neutral-400-a80-500-a80: oklch(55.6% 0 0 / 80%); | ||
| 95 | --color-adaptive-neutral-500-a10-a16: oklch(55.6% 0 0 / 16%); | ||
| 96 | --color-adaptive-neutral-500-400: oklch(70.8% 0 0); | ||
| 97 | --color-adaptive-neutral-500-500: oklch(55.6% 0 0); | ||
| 98 | --color-adaptive-neutral-600-300: oklch(87% 0 0); | ||
| 99 | --color-adaptive-neutral-600-400: oklch(70.8% 0 0); | ||
| 100 | --color-adaptive-neutral-950-50: oklch(98.5% 0 0); | ||
| 101 | --color-adaptive-red-50-950-a80: oklch(25.8% 0.092 26.042 / 80%); | ||
| 102 | --color-adaptive-red-200-800: oklch(44.4% 0.177 26.899); | ||
| 103 | --color-adaptive-red-600-a80-400-a80: oklch(70.4% 0.191 22.216 / 80%); | ||
| 104 | --color-adaptive-red-700-300: oklch(80.8% 0.114 19.571); | ||
| 105 | --color-adaptive-rose-100-500-a18: oklch(64.5% 0.246 16.439 / 18%); | ||
| 106 | --color-adaptive-rose-100-a80-500-a12: oklch(64.5% 0.246 16.439 / 12%); | ||
| 107 | --color-adaptive-rose-300-a70-400-a28: oklch(71.2% 0.194 13.428 / 28%); | ||
| 108 | --color-adaptive-rose-500-a12-a16: oklch(64.5% 0.246 16.439 / 16%); | ||
| 109 | --color-adaptive-rose-500-400: oklch(71.2% 0.194 13.428); | ||
| 110 | --color-adaptive-rose-600-400: oklch(71.2% 0.194 13.428); | ||
| 111 | --color-adaptive-rose-700-300: oklch(81% 0.117 11.638); | ||
| 112 | --color-adaptive-sky-500-a12-a16: oklch(68.5% 0.169 237.323 / 16%); | ||
| 113 | --color-adaptive-sky-600-400: oklch(74.6% 0.16 232.661); | ||
| 114 | --color-adaptive-sky-700-300: oklch(82.8% 0.111 230.318); | ||
| 115 | --color-adaptive-violet-500-a12-a16: oklch(60.6% 0.25 292.717 / 16%); | ||
| 116 | --color-adaptive-violet-600-400: oklch(70.2% 0.183 293.541); | ||
| 117 | --color-adaptive-violet-700-300: oklch(81.1% 0.111 293.571); | ||
| 118 | --color-adaptive-white-neutral-950-a70: oklch(14.5% 0 0 / 70%); | ||
| 119 | --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%); | ||
| 120 | --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067); | ||
| 121 | --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286); | ||
| 122 | } | ||
| 123 | |||
| 124 | @variant t3-chat-light { | ||
| 125 | --color-screen: #fdf7fd; | ||
| 126 | --color-sheet: rgba(253, 247, 253, 0.98); | ||
| 127 | --color-sheet-solid: #fdf7fd; | ||
| 128 | --color-card: #fdfafd; | ||
| 129 | --color-card-alt: #faf3fb; | ||
| 130 | --color-card-translucent: rgba(253, 250, 253, 0.8); | ||
| 131 | --color-foreground: #501854; | ||
| 132 | --color-foreground-secondary: #ac1668; | ||
| 133 | --color-foreground-muted: #8d1255; | ||
| 134 | --color-foreground-tertiary: #ac1668; | ||
| 135 | --color-border: #eee1ed; | ||
| 136 | --color-border-subtle: rgba(238, 225, 237, 0.7); | ||
| 137 | --color-separator: rgba(238, 225, 237, 0.55); | ||
| 138 | --color-subtle: #eaa7cb; | ||
| 139 | --color-subtle-strong: #f1c4e6; | ||
| 140 | --color-inline-skill-background: #f3e6f5; | ||
| 141 | --color-inline-skill-border: rgba(219, 39, 119, 0.42); | ||
| 142 | --color-inline-skill-foreground: #454554; | ||
| 143 | --color-primary: #db2777; | ||
| 144 | --color-primary-foreground: #ffffff; | ||
| 145 | --color-primary-shadow: #000000; | ||
| 146 | --color-secondary: #f1c4e6; | ||
| 147 | --color-secondary-foreground: #77347c; | ||
| 148 | --color-secondary-border: #eee1ed; | ||
| 149 | --color-switch-active-track: #db2777; | ||
| 150 | --color-switch-active-thumb: #ffffff; | ||
| 151 | --color-switch-inactive-track: #f1c4e6; | ||
| 152 | --color-switch-inactive-thumb: #8d1255; | ||
| 153 | --color-danger: #fde4f1; | ||
| 154 | --color-danger-border: rgba(247, 8, 108, 0.32); | ||
| 155 | --color-danger-foreground: #9d174d; | ||
| 156 | --color-input: #fdfafd; | ||
| 157 | --color-input-border: #e7c1dc; | ||
| 158 | --color-sidebar-search: #f8f8f7; | ||
| 159 | --color-placeholder: #8b5f90; | ||
| 160 | --color-icon: #501854; | ||
| 161 | --color-icon-muted: #ac1668; | ||
| 162 | --color-icon-subtle: #ac1668; | ||
| 163 | --color-header: rgba(253, 247, 253, 0.97); | ||
| 164 | --color-header-border: #efbdeb; | ||
| 165 | --color-glass-surface: rgba(255, 255, 255, 0.74); | ||
| 166 | --color-glass-tint: rgba(255, 255, 255, 0.22); | ||
| 167 | --color-status-bar: #fdf7fd; | ||
| 168 | --color-md-body: #501854; | ||
| 169 | --color-md-strong: #501854; | ||
| 170 | --color-md-link: #db2777; | ||
| 171 | --color-md-blockquote-border: #eee1ed; | ||
| 172 | --color-md-blockquote-bg: #eaa7cb; | ||
| 173 | --color-md-code-bg: #f5ecf9; | ||
| 174 | --color-md-code-text: #673c8b; | ||
| 175 | --color-md-user-code-bg: rgba(73, 44, 97, 0.18); | ||
| 176 | --color-md-user-code-text: #492c61; | ||
| 177 | --color-md-user-fence-bg: rgba(0, 0, 0, 0.16); | ||
| 178 | --color-md-user-fence-text: #492c61; | ||
| 179 | --color-md-hr: #eee1ed; | ||
| 180 | --color-user-bubble: #f7def2; | ||
| 181 | --color-user-bubble-foreground: #492c61; | ||
| 182 | --color-user-bubble-foreground-muted: rgba(73, 44, 97, 0.78); | ||
| 183 | --color-user-bubble-skill-foreground: #c12269; | ||
| 184 | --color-backdrop: rgba(0, 0, 0, 0.22); | ||
| 185 | --color-drawer: rgba(242, 225, 244, 0.99); | ||
| 186 | --color-drawer-shadow: rgba(0, 0, 0, 0.12); | ||
| 187 | --color-dot-separator: rgba(172, 22, 104, 0.35); | ||
| 188 | --color-wordmark: #501854; | ||
| 189 | --color-chevron: rgba(172, 22, 104, 0.42); | ||
| 190 | --color-adaptive-amber-50-950-a40: oklch(98.7% 0.022 95.277); | ||
| 191 | --color-adaptive-amber-200-900-a60: oklch(92.4% 0.12 95.746); | ||
| 192 | --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 12%); | ||
| 193 | --color-adaptive-amber-700-300: oklch(55.5% 0.163 48.998); | ||
| 194 | --color-adaptive-amber-700-400: oklch(55.5% 0.163 48.998); | ||
| 195 | --color-adaptive-amber-800-200: oklch(47.3% 0.137 46.201); | ||
| 196 | --color-adaptive-blue-50-blue-400-a14: oklch(97% 0.014 254.604); | ||
| 197 | --color-adaptive-blue-300-a50-blue-400-a28: oklch(80.9% 0.105 251.813 / 50%); | ||
| 198 | --color-adaptive-blue-500-a20-blue-400-a15: oklch(62.3% 0.214 259.815 / 20%); | ||
| 199 | --color-adaptive-blue-500-400: oklch(62.3% 0.214 259.815); | ||
| 200 | --color-adaptive-blue-600-400: oklch(54.6% 0.245 262.881); | ||
| 201 | --color-adaptive-black-a10-a25: rgb(0 0 0 / 10%); | ||
| 202 | --color-adaptive-black-a15-a35: rgb(0 0 0 / 15%); | ||
| 203 | --color-adaptive-emerald-500-a12-a16: oklch(69.6% 0.17 162.48 / 12%); | ||
| 204 | --color-adaptive-emerald-600-400: oklch(59.6% 0.145 163.225); | ||
| 205 | --color-adaptive-emerald-700-300: oklch(50.8% 0.118 165.612); | ||
| 206 | --color-adaptive-indigo-500-a12-a16: oklch(58.5% 0.233 277.117 / 12%); | ||
| 207 | --color-adaptive-indigo-600-300: oklch(51.1% 0.262 276.966); | ||
| 208 | --color-adaptive-indigo-700-300: oklch(45.7% 0.24 277.023); | ||
| 209 | --color-adaptive-neutral-100-900: oklch(97% 0 0); | ||
| 210 | --color-adaptive-neutral-200-700-a60: oklch(92.2% 0 0); | ||
| 211 | --color-adaptive-neutral-200-800: oklch(92.2% 0 0); | ||
| 212 | --color-adaptive-neutral-200-a70-white-a8: oklch(92.2% 0 0 / 70%); | ||
| 213 | --color-adaptive-neutral-200-white-a6: oklch(92.2% 0 0); | ||
| 214 | --color-adaptive-neutral-200-white-a8: oklch(92.2% 0 0); | ||
| 215 | --color-adaptive-neutral-200-a80-white-a8: oklch(92.2% 0 0 / 80%); | ||
| 216 | --color-adaptive-neutral-300-a60-white-a12: oklch(87% 0 0 / 60%); | ||
| 217 | --color-adaptive-neutral-400-500: oklch(70.8% 0 0); | ||
| 218 | --color-adaptive-neutral-400-a60-500-a60: oklch(70.8% 0 0 / 60%); | ||
| 219 | --color-adaptive-neutral-400-a80-500-a80: oklch(70.8% 0 0 / 80%); | ||
| 220 | --color-adaptive-neutral-500-a10-a16: oklch(55.6% 0 0 / 10%); | ||
| 221 | --color-adaptive-neutral-500-400: oklch(55.6% 0 0); | ||
| 222 | --color-adaptive-neutral-500-500: oklch(55.6% 0 0); | ||
| 223 | --color-adaptive-neutral-600-300: oklch(43.9% 0 0); | ||
| 224 | --color-adaptive-neutral-600-400: oklch(43.9% 0 0); | ||
| 225 | --color-adaptive-neutral-950-50: oklch(14.5% 0 0); | ||
| 226 | --color-adaptive-red-50-950-a80: oklch(97.1% 0.013 17.38); | ||
| 227 | --color-adaptive-red-200-800: oklch(88.5% 0.062 18.334); | ||
| 228 | --color-adaptive-red-600-a80-400-a80: oklch(57.7% 0.245 27.325 / 80%); | ||
| 229 | --color-adaptive-red-700-300: oklch(50.5% 0.213 27.518); | ||
| 230 | --color-adaptive-rose-100-500-a18: oklch(94.1% 0.03 12.58); | ||
| 231 | --color-adaptive-rose-100-a80-500-a12: oklch(94.1% 0.03 12.58 / 80%); | ||
| 232 | --color-adaptive-rose-300-a70-400-a28: oklch(81% 0.117 11.638 / 70%); | ||
| 233 | --color-adaptive-rose-500-a12-a16: oklch(64.5% 0.246 16.439 / 12%); | ||
| 234 | --color-adaptive-rose-500-400: oklch(64.5% 0.246 16.439); | ||
| 235 | --color-adaptive-rose-600-400: oklch(58.6% 0.253 17.585); | ||
| 236 | --color-adaptive-rose-700-300: oklch(51.4% 0.222 16.935); | ||
| 237 | --color-adaptive-sky-500-a12-a16: oklch(68.5% 0.169 237.323 / 12%); | ||
| 238 | --color-adaptive-sky-600-400: oklch(58.8% 0.158 241.966); | ||
| 239 | --color-adaptive-sky-700-300: oklch(50% 0.134 242.749); | ||
| 240 | --color-adaptive-violet-500-a12-a16: oklch(60.6% 0.25 292.717 / 12%); | ||
| 241 | --color-adaptive-violet-600-400: oklch(54.1% 0.281 293.009); | ||
| 242 | --color-adaptive-violet-700-300: oklch(49.1% 0.27 292.581); | ||
| 243 | --color-adaptive-white-neutral-950-a70: #fff; | ||
| 244 | --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%); | ||
| 245 | --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938); | ||
| 246 | --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786); | ||
| 247 | } | ||
| 248 | |||
| 249 | @variant t3-chat-dark { | ||
| 250 | --color-screen: #1f1a24; | ||
| 251 | --color-sheet: rgba(31, 26, 36, 0.98); | ||
| 252 | --color-sheet-solid: #1f1a24; | ||
| 253 | --color-card: #2c2631; | ||
| 254 | --color-card-alt: #29232d; | ||
| 255 | --color-card-translucent: rgba(44, 38, 49, 0.8); | ||
| 256 | --color-foreground: #f9f8fb; | ||
| 257 | --color-foreground-secondary: #e7d0dd; | ||
| 258 | --color-foreground-muted: #e7d0dd; | ||
| 259 | --color-foreground-tertiary: #e7d0dd; | ||
| 260 | --color-border: #27242c; | ||
| 261 | --color-border-subtle: rgba(39, 36, 44, 0.7); | ||
| 262 | --color-separator: rgba(39, 36, 44, 0.55); | ||
| 263 | --color-subtle: #423a45; | ||
| 264 | --color-subtle-strong: #362d3d; | ||
| 265 | --color-inline-skill-background: #463753; | ||
| 266 | --color-inline-skill-border: rgba(163, 0, 76, 0.42); | ||
| 267 | --color-inline-skill-foreground: #f8f1f5; | ||
| 268 | --color-primary: #a3004c; | ||
| 269 | --color-primary-foreground: #fbd0e8; | ||
| 270 | --color-primary-shadow: #000000; | ||
| 271 | --color-secondary: #362d3d; | ||
| 272 | --color-secondary-foreground: #d4c7e1; | ||
| 273 | --color-secondary-border: #27242c; | ||
| 274 | --color-switch-active-track: #a3004c; | ||
| 275 | --color-switch-active-thumb: #fbd0e8; | ||
| 276 | --color-switch-inactive-track: #362d3d; | ||
| 277 | --color-switch-inactive-thumb: #e7d0dd; | ||
| 278 | --color-danger: #331a2b; | ||
| 279 | --color-danger-border: rgba(157, 23, 77, 0.32); | ||
| 280 | --color-danger-foreground: #fbd0e8; | ||
| 281 | --color-input: #2c2631; | ||
| 282 | --color-input-border: #302029; | ||
| 283 | --color-sidebar-search: #261922; | ||
| 284 | --color-placeholder: #968d9f; | ||
| 285 | --color-icon: #f9f8fb; | ||
| 286 | --color-icon-muted: #d4c7e1; | ||
| 287 | --color-icon-subtle: #e7d0dd; | ||
| 288 | --color-header: rgba(31, 26, 36, 0.97); | ||
| 289 | --color-header-border: #27242c; | ||
| 290 | --color-glass-surface: rgba(16, 10, 14, 0.74); | ||
| 291 | --color-glass-tint: rgba(16, 10, 14, 0.22); | ||
| 292 | --color-status-bar: #1f1a24; | ||
| 293 | --color-md-body: #f9f8fb; | ||
| 294 | --color-md-strong: #f9f8fb; | ||
| 295 | --color-md-link: #a3004c; | ||
| 296 | --color-md-blockquote-border: #27242c; | ||
| 297 | --color-md-blockquote-bg: #423a45; | ||
| 298 | --color-md-code-bg: #1f1a24; | ||
| 299 | --color-md-code-text: #d8c3ef; | ||
| 300 | --color-md-user-code-bg: rgba(242, 235, 250, 0.18); | ||
| 301 | --color-md-user-code-text: #f2ebfa; | ||
| 302 | --color-md-user-fence-bg: rgba(0, 0, 0, 0.28); | ||
| 303 | --color-md-user-fence-text: #f2ebfa; | ||
| 304 | --color-md-hr: #27242c; | ||
| 305 | --color-user-bubble: #2b2431; | ||
| 306 | --color-user-bubble-foreground: #f2ebfa; | ||
| 307 | --color-user-bubble-foreground-muted: rgba(242, 235, 250, 0.78); | ||
| 308 | --color-user-bubble-skill-foreground: #cb709a; | ||
| 309 | --color-backdrop: rgba(0, 0, 0, 0.48); | ||
| 310 | --color-drawer: rgba(23, 16, 24, 0.99); | ||
| 311 | --color-drawer-shadow: rgba(0, 0, 0, 0.32); | ||
| 312 | --color-dot-separator: rgba(231, 208, 221, 0.35); | ||
| 313 | --color-wordmark: #f9f8fb; | ||
| 314 | --color-chevron: rgba(231, 208, 221, 0.42); | ||
| 315 | --color-adaptive-amber-50-950-a40: oklch(27.9% 0.077 45.635 / 40%); | ||
| 316 | --color-adaptive-amber-200-900-a60: oklch(41.4% 0.112 45.904 / 60%); | ||
| 317 | --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 16%); | ||
| 318 | --color-adaptive-amber-700-300: oklch(87.9% 0.169 91.605); | ||
| 319 | --color-adaptive-amber-700-400: oklch(82.8% 0.189 84.429); | ||
| 320 | --color-adaptive-amber-800-200: oklch(92.4% 0.12 95.746); | ||
| 321 | --color-adaptive-blue-50-blue-400-a14: oklch(70.7% 0.165 254.624 / 14%); | ||
| 322 | --color-adaptive-blue-300-a50-blue-400-a28: oklch(70.7% 0.165 254.624 / 28%); | ||
| 323 | --color-adaptive-blue-500-a20-blue-400-a15: oklch(70.7% 0.165 254.624 / 15%); | ||
| 324 | --color-adaptive-blue-500-400: oklch(70.7% 0.165 254.624); | ||
| 325 | --color-adaptive-blue-600-400: oklch(70.7% 0.165 254.624); | ||
| 326 | --color-adaptive-black-a10-a25: rgb(0 0 0 / 25%); | ||
| 327 | --color-adaptive-black-a15-a35: rgb(0 0 0 / 35%); | ||
| 328 | --color-adaptive-emerald-500-a12-a16: oklch(69.6% 0.17 162.48 / 16%); | ||
| 329 | --color-adaptive-emerald-600-400: oklch(76.5% 0.177 163.223); | ||
| 330 | --color-adaptive-emerald-700-300: oklch(84.5% 0.143 164.978); | ||
| 331 | --color-adaptive-indigo-500-a12-a16: oklch(58.5% 0.233 277.117 / 16%); | ||
| 332 | --color-adaptive-indigo-600-300: oklch(78.5% 0.115 274.713); | ||
| 333 | --color-adaptive-indigo-700-300: oklch(78.5% 0.115 274.713); | ||
| 334 | --color-adaptive-neutral-100-900: oklch(20.5% 0 0); | ||
| 335 | --color-adaptive-neutral-200-700-a60: oklch(37.1% 0 0 / 60%); | ||
| 336 | --color-adaptive-neutral-200-800: oklch(26.9% 0 0); | ||
| 337 | --color-adaptive-neutral-200-a70-white-a8: rgb(255 255 255 / 8%); | ||
| 338 | --color-adaptive-neutral-200-white-a6: rgb(255 255 255 / 6%); | ||
| 339 | --color-adaptive-neutral-200-white-a8: rgb(255 255 255 / 8%); | ||
| 340 | --color-adaptive-neutral-200-a80-white-a8: rgb(255 255 255 / 8%); | ||
| 341 | --color-adaptive-neutral-300-a60-white-a12: rgb(255 255 255 / 12%); | ||
| 342 | --color-adaptive-neutral-400-500: oklch(55.6% 0 0); | ||
| 343 | --color-adaptive-neutral-400-a60-500-a60: oklch(55.6% 0 0 / 60%); | ||
| 344 | --color-adaptive-neutral-400-a80-500-a80: oklch(55.6% 0 0 / 80%); | ||
| 345 | --color-adaptive-neutral-500-a10-a16: oklch(55.6% 0 0 / 16%); | ||
| 346 | --color-adaptive-neutral-500-400: oklch(70.8% 0 0); | ||
| 347 | --color-adaptive-neutral-500-500: oklch(55.6% 0 0); | ||
| 348 | --color-adaptive-neutral-600-300: oklch(87% 0 0); | ||
| 349 | --color-adaptive-neutral-600-400: oklch(70.8% 0 0); | ||
| 350 | --color-adaptive-neutral-950-50: oklch(98.5% 0 0); | ||
| 351 | --color-adaptive-red-50-950-a80: oklch(25.8% 0.092 26.042 / 80%); | ||
| 352 | --color-adaptive-red-200-800: oklch(44.4% 0.177 26.899); | ||
| 353 | --color-adaptive-red-600-a80-400-a80: oklch(70.4% 0.191 22.216 / 80%); | ||
| 354 | --color-adaptive-red-700-300: oklch(80.8% 0.114 19.571); | ||
| 355 | --color-adaptive-rose-100-500-a18: oklch(64.5% 0.246 16.439 / 18%); | ||
| 356 | --color-adaptive-rose-100-a80-500-a12: oklch(64.5% 0.246 16.439 / 12%); | ||
| 357 | --color-adaptive-rose-300-a70-400-a28: oklch(71.2% 0.194 13.428 / 28%); | ||
| 358 | --color-adaptive-rose-500-a12-a16: oklch(64.5% 0.246 16.439 / 16%); | ||
| 359 | --color-adaptive-rose-500-400: oklch(71.2% 0.194 13.428); | ||
| 360 | --color-adaptive-rose-600-400: oklch(71.2% 0.194 13.428); | ||
| 361 | --color-adaptive-rose-700-300: oklch(81% 0.117 11.638); | ||
| 362 | --color-adaptive-sky-500-a12-a16: oklch(68.5% 0.169 237.323 / 16%); | ||
| 363 | --color-adaptive-sky-600-400: oklch(74.6% 0.16 232.661); | ||
| 364 | --color-adaptive-sky-700-300: oklch(82.8% 0.111 230.318); | ||
| 365 | --color-adaptive-violet-500-a12-a16: oklch(60.6% 0.25 292.717 / 16%); | ||
| 366 | --color-adaptive-violet-600-400: oklch(70.2% 0.183 293.541); | ||
| 367 | --color-adaptive-violet-700-300: oklch(81.1% 0.111 293.571); | ||
| 368 | --color-adaptive-white-neutral-950-a70: oklch(14.5% 0 0 / 70%); | ||
| 369 | --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%); | ||
| 370 | --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067); | ||
| 371 | --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286); | ||
| 372 | } | ||
| 373 | |||
| 374 | @variant grove-light { | ||
| 375 | --color-screen: #f3f7f4; | ||
| 376 | --color-sheet: rgba(243, 247, 244, 0.98); | ||
| 377 | --color-sheet-solid: #f3f7f4; | ||
| 378 | --color-card: #ecefed; | ||
| 379 | --color-card-alt: #f3f7f4; | ||
| 380 | --color-card-translucent: rgba(236, 239, 237, 0.8); | ||
| 381 | --color-foreground: #241523; | ||
| 382 | --color-foreground-secondary: #746c73; | ||
| 383 | --color-foreground-muted: #6e696f; | ||
| 384 | --color-foreground-tertiary: #746c73; | ||
| 385 | --color-border: #cbd5d1; | ||
| 386 | --color-border-subtle: rgba(203, 213, 209, 0.7); | ||
| 387 | --color-separator: rgba(203, 213, 209, 0.55); | ||
| 388 | --color-subtle: #e6f0ea; | ||
| 389 | --color-subtle-strong: #e2ede7; | ||
| 390 | --color-inline-skill-background: #d5e6dd; | ||
| 391 | --color-inline-skill-border: rgba(27, 125, 80, 0.42); | ||
| 392 | --color-inline-skill-foreground: #241523; | ||
| 393 | --color-primary: #1b7d50; | ||
| 394 | --color-primary-foreground: #fffaff; | ||
| 395 | --color-primary-shadow: #000000; | ||
| 396 | --color-secondary: #e2ede7; | ||
| 397 | --color-secondary-foreground: #241523; | ||
| 398 | --color-secondary-border: #cbd5d1; | ||
| 399 | --color-switch-active-track: #1b7d50; | ||
| 400 | --color-switch-active-thumb: #fffaff; | ||
| 401 | --color-switch-inactive-track: #e2ede7; | ||
| 402 | --color-switch-inactive-thumb: #6e696f; | ||
| 403 | --color-danger: #f4e7e5; | ||
| 404 | --color-danger-border: rgba(251, 44, 54, 0.32); | ||
| 405 | --color-danger-foreground: #c10007; | ||
| 406 | --color-input: #ecefed; | ||
| 407 | --color-input-border: #becbc5; | ||
| 408 | --color-sidebar-search: #d3dcd8; | ||
| 409 | --color-placeholder: #716971; | ||
| 410 | --color-icon: #241523; | ||
| 411 | --color-icon-muted: #746c73; | ||
| 412 | --color-icon-subtle: #746c73; | ||
| 413 | --color-header: rgba(243, 247, 244, 0.97); | ||
| 414 | --color-header-border: #d5e6dd; | ||
| 415 | --color-glass-surface: rgba(231, 233, 232, 0.74); | ||
| 416 | --color-glass-tint: rgba(231, 233, 232, 0.22); | ||
| 417 | --color-status-bar: #f3f7f4; | ||
| 418 | --color-md-body: #241523; | ||
| 419 | --color-md-strong: #241523; | ||
| 420 | --color-md-link: #1b7d50; | ||
| 421 | --color-md-blockquote-border: #cbd5d1; | ||
| 422 | --color-md-blockquote-bg: #e6f0ea; | ||
| 423 | --color-md-code-bg: #eef1ef; | ||
| 424 | --color-md-code-text: #241523; | ||
| 425 | --color-md-user-code-bg: rgba(36, 21, 35, 0.18); | ||
| 426 | --color-md-user-code-text: #241523; | ||
| 427 | --color-md-user-fence-bg: rgba(0, 0, 0, 0.16); | ||
| 428 | --color-md-user-fence-text: #241523; | ||
| 429 | --color-md-hr: #cbd5d1; | ||
| 430 | --color-user-bubble: #cce1d7; | ||
| 431 | --color-user-bubble-foreground: #241523; | ||
| 432 | --color-user-bubble-foreground-muted: rgba(36, 21, 35, 0.78); | ||
| 433 | --color-user-bubble-skill-foreground: #815a0e; | ||
| 434 | --color-backdrop: rgba(0, 0, 0, 0.22); | ||
| 435 | --color-drawer: rgba(226, 237, 231, 0.99); | ||
| 436 | --color-drawer-shadow: rgba(0, 0, 0, 0.12); | ||
| 437 | --color-dot-separator: rgba(116, 108, 115, 0.35); | ||
| 438 | --color-wordmark: #241523; | ||
| 439 | --color-chevron: rgba(116, 108, 115, 0.42); | ||
| 440 | --color-adaptive-amber-50-950-a40: oklch(98.7% 0.022 95.277); | ||
| 441 | --color-adaptive-amber-200-900-a60: oklch(92.4% 0.12 95.746); | ||
| 442 | --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 12%); | ||
| 443 | --color-adaptive-amber-700-300: oklch(55.5% 0.163 48.998); | ||
| 444 | --color-adaptive-amber-700-400: oklch(55.5% 0.163 48.998); | ||
| 445 | --color-adaptive-amber-800-200: oklch(47.3% 0.137 46.201); | ||
| 446 | --color-adaptive-blue-50-blue-400-a14: oklch(97% 0.014 254.604); | ||
| 447 | --color-adaptive-blue-300-a50-blue-400-a28: oklch(80.9% 0.105 251.813 / 50%); | ||
| 448 | --color-adaptive-blue-500-a20-blue-400-a15: oklch(62.3% 0.214 259.815 / 20%); | ||
| 449 | --color-adaptive-blue-500-400: oklch(62.3% 0.214 259.815); | ||
| 450 | --color-adaptive-blue-600-400: oklch(54.6% 0.245 262.881); | ||
| 451 | --color-adaptive-black-a10-a25: rgb(0 0 0 / 10%); | ||
| 452 | --color-adaptive-black-a15-a35: rgb(0 0 0 / 15%); | ||
| 453 | --color-adaptive-emerald-500-a12-a16: oklch(69.6% 0.17 162.48 / 12%); | ||
| 454 | --color-adaptive-emerald-600-400: oklch(59.6% 0.145 163.225); | ||
| 455 | --color-adaptive-emerald-700-300: oklch(50.8% 0.118 165.612); | ||
| 456 | --color-adaptive-indigo-500-a12-a16: oklch(58.5% 0.233 277.117 / 12%); | ||
| 457 | --color-adaptive-indigo-600-300: oklch(51.1% 0.262 276.966); | ||
| 458 | --color-adaptive-indigo-700-300: oklch(45.7% 0.24 277.023); | ||
| 459 | --color-adaptive-neutral-100-900: oklch(97% 0 0); | ||
| 460 | --color-adaptive-neutral-200-700-a60: oklch(92.2% 0 0); | ||
| 461 | --color-adaptive-neutral-200-800: oklch(92.2% 0 0); | ||
| 462 | --color-adaptive-neutral-200-a70-white-a8: oklch(92.2% 0 0 / 70%); | ||
| 463 | --color-adaptive-neutral-200-white-a6: oklch(92.2% 0 0); | ||
| 464 | --color-adaptive-neutral-200-white-a8: oklch(92.2% 0 0); | ||
| 465 | --color-adaptive-neutral-200-a80-white-a8: oklch(92.2% 0 0 / 80%); | ||
| 466 | --color-adaptive-neutral-300-a60-white-a12: oklch(87% 0 0 / 60%); | ||
| 467 | --color-adaptive-neutral-400-500: oklch(70.8% 0 0); | ||
| 468 | --color-adaptive-neutral-400-a60-500-a60: oklch(70.8% 0 0 / 60%); | ||
| 469 | --color-adaptive-neutral-400-a80-500-a80: oklch(70.8% 0 0 / 80%); | ||
| 470 | --color-adaptive-neutral-500-a10-a16: oklch(55.6% 0 0 / 10%); | ||
| 471 | --color-adaptive-neutral-500-400: oklch(55.6% 0 0); | ||
| 472 | --color-adaptive-neutral-500-500: oklch(55.6% 0 0); | ||
| 473 | --color-adaptive-neutral-600-300: oklch(43.9% 0 0); | ||
| 474 | --color-adaptive-neutral-600-400: oklch(43.9% 0 0); | ||
| 475 | --color-adaptive-neutral-950-50: oklch(14.5% 0 0); | ||
| 476 | --color-adaptive-red-50-950-a80: oklch(97.1% 0.013 17.38); | ||
| 477 | --color-adaptive-red-200-800: oklch(88.5% 0.062 18.334); | ||
| 478 | --color-adaptive-red-600-a80-400-a80: oklch(57.7% 0.245 27.325 / 80%); | ||
| 479 | --color-adaptive-red-700-300: oklch(50.5% 0.213 27.518); | ||
| 480 | --color-adaptive-rose-100-500-a18: oklch(94.1% 0.03 12.58); | ||
| 481 | --color-adaptive-rose-100-a80-500-a12: oklch(94.1% 0.03 12.58 / 80%); | ||
| 482 | --color-adaptive-rose-300-a70-400-a28: oklch(81% 0.117 11.638 / 70%); | ||
| 483 | --color-adaptive-rose-500-a12-a16: oklch(64.5% 0.246 16.439 / 12%); | ||
| 484 | --color-adaptive-rose-500-400: oklch(64.5% 0.246 16.439); | ||
| 485 | --color-adaptive-rose-600-400: oklch(58.6% 0.253 17.585); | ||
| 486 | --color-adaptive-rose-700-300: oklch(51.4% 0.222 16.935); | ||
| 487 | --color-adaptive-sky-500-a12-a16: oklch(68.5% 0.169 237.323 / 12%); | ||
| 488 | --color-adaptive-sky-600-400: oklch(58.8% 0.158 241.966); | ||
| 489 | --color-adaptive-sky-700-300: oklch(50% 0.134 242.749); | ||
| 490 | --color-adaptive-violet-500-a12-a16: oklch(60.6% 0.25 292.717 / 12%); | ||
| 491 | --color-adaptive-violet-600-400: oklch(54.1% 0.281 293.009); | ||
| 492 | --color-adaptive-violet-700-300: oklch(49.1% 0.27 292.581); | ||
| 493 | --color-adaptive-white-neutral-950-a70: #fff; | ||
| 494 | --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%); | ||
| 495 | --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938); | ||
| 496 | --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786); | ||
| 497 | } | ||
| 498 | |||
| 499 | @variant grove-dark { | ||
| 500 | --color-screen: #1b2821; | ||
| 501 | --color-sheet: rgba(27, 40, 33, 0.98); | ||
| 502 | --color-sheet-solid: #1b2821; | ||
| 503 | --color-card: #36413c; | ||
| 504 | --color-card-alt: #1b2821; | ||
| 505 | --color-card-translucent: rgba(54, 65, 60, 0.8); | ||
| 506 | --color-foreground: #fffaff; | ||
| 507 | --color-foreground-secondary: #919595; | ||
| 508 | --color-foreground-muted: #9da5a2; | ||
| 509 | --color-foreground-tertiary: #919595; | ||
| 510 | --color-border: #415f4f; | ||
| 511 | --color-border-subtle: rgba(65, 95, 79, 0.7); | ||
| 512 | --color-separator: rgba(65, 95, 79, 0.55); | ||
| 513 | --color-subtle: #253e31; | ||
| 514 | --color-subtle-strong: #2a4b39; | ||
| 515 | --color-inline-skill-background: #325c46; | ||
| 516 | --color-inline-skill-border: rgba(105, 214, 154, 0.42); | ||
| 517 | --color-inline-skill-foreground: #fffaff; | ||
| 518 | --color-primary: #69d69a; | ||
| 519 | --color-primary-foreground: #241523; | ||
| 520 | --color-primary-shadow: #000000; | ||
| 521 | --color-secondary: #2a4b39; | ||
| 522 | --color-secondary-foreground: #fffaff; | ||
| 523 | --color-secondary-border: #415f4f; | ||
| 524 | --color-switch-active-track: #69d69a; | ||
| 525 | --color-switch-active-thumb: #241523; | ||
| 526 | --color-switch-inactive-track: #2a4b39; | ||
| 527 | --color-switch-inactive-thumb: #9da5a2; | ||
| 528 | --color-danger: #3f2c28; | ||
| 529 | --color-danger-border: rgba(251, 65, 74, 0.32); | ||
| 530 | --color-danger-foreground: #ff6668; | ||
| 531 | --color-input: #36413c; | ||
| 532 | --color-input-border: #4f725f; | ||
| 533 | --color-sidebar-search: #45554d; | ||
| 534 | --color-placeholder: #a9abab; | ||
| 535 | --color-icon: #fffaff; | ||
| 536 | --color-icon-muted: #919595; | ||
| 537 | --color-icon-subtle: #919595; | ||
| 538 | --color-header: rgba(27, 40, 33, 0.97); | ||
| 539 | --color-header-border: #36654c; | ||
| 540 | --color-glass-surface: rgba(68, 77, 73, 0.74); | ||
| 541 | --color-glass-tint: rgba(68, 77, 73, 0.22); | ||
| 542 | --color-status-bar: #1b2821; | ||
| 543 | --color-md-body: #fffaff; | ||
| 544 | --color-md-strong: #fffaff; | ||
| 545 | --color-md-link: #69d69a; | ||
| 546 | --color-md-blockquote-border: #415f4f; | ||
| 547 | --color-md-blockquote-bg: #253e31; | ||
| 548 | --color-md-code-bg: #28342e; | ||
| 549 | --color-md-code-text: #fffaff; | ||
| 550 | --color-md-user-code-bg: rgba(255, 250, 255, 0.18); | ||
| 551 | --color-md-user-code-text: #fffaff; | ||
| 552 | --color-md-user-fence-bg: rgba(0, 0, 0, 0.28); | ||
| 553 | --color-md-user-fence-text: #fffaff; | ||
| 554 | --color-md-hr: #415f4f; | ||
| 555 | --color-user-bubble: #37664d; | ||
| 556 | --color-user-bubble-foreground: #fffaff; | ||
| 557 | --color-user-bubble-foreground-muted: rgba(255, 250, 255, 0.78); | ||
| 558 | --color-user-bubble-skill-foreground: #eed295; | ||
| 559 | --color-backdrop: rgba(0, 0, 0, 0.48); | ||
| 560 | --color-drawer: rgba(33, 54, 43, 0.99); | ||
| 561 | --color-drawer-shadow: rgba(0, 0, 0, 0.32); | ||
| 562 | --color-dot-separator: rgba(145, 149, 149, 0.35); | ||
| 563 | --color-wordmark: #fffaff; | ||
| 564 | --color-chevron: rgba(145, 149, 149, 0.42); | ||
| 565 | --color-adaptive-amber-50-950-a40: oklch(27.9% 0.077 45.635 / 40%); | ||
| 566 | --color-adaptive-amber-200-900-a60: oklch(41.4% 0.112 45.904 / 60%); | ||
| 567 | --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 16%); | ||
| 568 | --color-adaptive-amber-700-300: oklch(87.9% 0.169 91.605); | ||
| 569 | --color-adaptive-amber-700-400: oklch(82.8% 0.189 84.429); | ||
| 570 | --color-adaptive-amber-800-200: oklch(92.4% 0.12 95.746); | ||
| 571 | --color-adaptive-blue-50-blue-400-a14: oklch(70.7% 0.165 254.624 / 14%); | ||
| 572 | --color-adaptive-blue-300-a50-blue-400-a28: oklch(70.7% 0.165 254.624 / 28%); | ||
| 573 | --color-adaptive-blue-500-a20-blue-400-a15: oklch(70.7% 0.165 254.624 / 15%); | ||
| 574 | --color-adaptive-blue-500-400: oklch(70.7% 0.165 254.624); | ||
| 575 | --color-adaptive-blue-600-400: oklch(70.7% 0.165 254.624); | ||
| 576 | --color-adaptive-black-a10-a25: rgb(0 0 0 / 25%); | ||
| 577 | --color-adaptive-black-a15-a35: rgb(0 0 0 / 35%); | ||
| 578 | --color-adaptive-emerald-500-a12-a16: oklch(69.6% 0.17 162.48 / 16%); | ||
| 579 | --color-adaptive-emerald-600-400: oklch(76.5% 0.177 163.223); | ||
| 580 | --color-adaptive-emerald-700-300: oklch(84.5% 0.143 164.978); | ||
| 581 | --color-adaptive-indigo-500-a12-a16: oklch(58.5% 0.233 277.117 / 16%); | ||
| 582 | --color-adaptive-indigo-600-300: oklch(78.5% 0.115 274.713); | ||
| 583 | --color-adaptive-indigo-700-300: oklch(78.5% 0.115 274.713); | ||
| 584 | --color-adaptive-neutral-100-900: oklch(20.5% 0 0); | ||
| 585 | --color-adaptive-neutral-200-700-a60: oklch(37.1% 0 0 / 60%); | ||
| 586 | --color-adaptive-neutral-200-800: oklch(26.9% 0 0); | ||
| 587 | --color-adaptive-neutral-200-a70-white-a8: rgb(255 255 255 / 8%); | ||
| 588 | --color-adaptive-neutral-200-white-a6: rgb(255 255 255 / 6%); | ||
| 589 | --color-adaptive-neutral-200-white-a8: rgb(255 255 255 / 8%); | ||
| 590 | --color-adaptive-neutral-200-a80-white-a8: rgb(255 255 255 / 8%); | ||
| 591 | --color-adaptive-neutral-300-a60-white-a12: rgb(255 255 255 / 12%); | ||
| 592 | --color-adaptive-neutral-400-500: oklch(55.6% 0 0); | ||
| 593 | --color-adaptive-neutral-400-a60-500-a60: oklch(55.6% 0 0 / 60%); | ||
| 594 | --color-adaptive-neutral-400-a80-500-a80: oklch(55.6% 0 0 / 80%); | ||
| 595 | --color-adaptive-neutral-500-a10-a16: oklch(55.6% 0 0 / 16%); | ||
| 596 | --color-adaptive-neutral-500-400: oklch(70.8% 0 0); | ||
| 597 | --color-adaptive-neutral-500-500: oklch(55.6% 0 0); | ||
| 598 | --color-adaptive-neutral-600-300: oklch(87% 0 0); | ||
| 599 | --color-adaptive-neutral-600-400: oklch(70.8% 0 0); | ||
| 600 | --color-adaptive-neutral-950-50: oklch(98.5% 0 0); | ||
| 601 | --color-adaptive-red-50-950-a80: oklch(25.8% 0.092 26.042 / 80%); | ||
| 602 | --color-adaptive-red-200-800: oklch(44.4% 0.177 26.899); | ||
| 603 | --color-adaptive-red-600-a80-400-a80: oklch(70.4% 0.191 22.216 / 80%); | ||
| 604 | --color-adaptive-red-700-300: oklch(80.8% 0.114 19.571); | ||
| 605 | --color-adaptive-rose-100-500-a18: oklch(64.5% 0.246 16.439 / 18%); | ||
| 606 | --color-adaptive-rose-100-a80-500-a12: oklch(64.5% 0.246 16.439 / 12%); | ||
| 607 | --color-adaptive-rose-300-a70-400-a28: oklch(71.2% 0.194 13.428 / 28%); | ||
| 608 | --color-adaptive-rose-500-a12-a16: oklch(64.5% 0.246 16.439 / 16%); | ||
| 609 | --color-adaptive-rose-500-400: oklch(71.2% 0.194 13.428); | ||
| 610 | --color-adaptive-rose-600-400: oklch(71.2% 0.194 13.428); | ||
| 611 | --color-adaptive-rose-700-300: oklch(81% 0.117 11.638); | ||
| 612 | --color-adaptive-sky-500-a12-a16: oklch(68.5% 0.169 237.323 / 16%); | ||
| 613 | --color-adaptive-sky-600-400: oklch(74.6% 0.16 232.661); | ||
| 614 | --color-adaptive-sky-700-300: oklch(82.8% 0.111 230.318); | ||
| 615 | --color-adaptive-violet-500-a12-a16: oklch(60.6% 0.25 292.717 / 16%); | ||
| 616 | --color-adaptive-violet-600-400: oklch(70.2% 0.183 293.541); | ||
| 617 | --color-adaptive-violet-700-300: oklch(81.1% 0.111 293.571); | ||
| 618 | --color-adaptive-white-neutral-950-a70: oklch(14.5% 0 0 / 70%); | ||
| 619 | --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%); | ||
| 620 | --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067); | ||
| 621 | --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286); | ||
| 622 | } | ||
| 623 | |||
| 624 | @variant ocean-light { | ||
| 625 | --color-screen: #f5f7f8; | ||
| 626 | --color-sheet: rgba(245, 247, 248, 0.98); | ||
| 627 | --color-sheet-solid: #f5f7f8; | ||
| 628 | --color-card: #edeff1; | ||
| 629 | --color-card-alt: #f5f7f8; | ||
| 630 | --color-card-translucent: rgba(237, 239, 241, 0.8); | ||
| 631 | --color-foreground: #241523; | ||
| 632 | --color-foreground-secondary: #746c75; | ||
| 633 | --color-foreground-muted: #6f6873; | ||
| 634 | --color-foreground-tertiary: #746c75; | ||
| 635 | --color-border: #cdd4dc; | ||
| 636 | --color-border-subtle: rgba(205, 212, 220, 0.7); | ||
| 637 | --color-separator: rgba(205, 212, 220, 0.55); | ||
| 638 | --color-subtle: #e8eff4; | ||
| 639 | --color-subtle-strong: #e4ecf2; | ||
| 640 | --color-inline-skill-background: #d8e4ee; | ||
| 641 | --color-inline-skill-border: rgba(38, 114, 175, 0.42); | ||
| 642 | --color-inline-skill-foreground: #241523; | ||
| 643 | --color-primary: #2672af; | ||
| 644 | --color-primary-foreground: #fffaff; | ||
| 645 | --color-primary-shadow: #000000; | ||
| 646 | --color-secondary: #e4ecf2; | ||
| 647 | --color-secondary-foreground: #241523; | ||
| 648 | --color-secondary-border: #cdd4dc; | ||
| 649 | --color-switch-active-track: #2672af; | ||
| 650 | --color-switch-active-thumb: #fffaff; | ||
| 651 | --color-switch-inactive-track: #e4ecf2; | ||
| 652 | --color-switch-inactive-thumb: #6f6873; | ||
| 653 | --color-danger: #f5e6e9; | ||
| 654 | --color-danger-border: rgba(251, 44, 54, 0.32); | ||
| 655 | --color-danger-foreground: #c10007; | ||
| 656 | --color-input: #edeff1; | ||
| 657 | --color-input-border: #c0c9d4; | ||
| 658 | --color-sidebar-search: #d5dbe2; | ||
| 659 | --color-placeholder: #716972; | ||
| 660 | --color-icon: #241523; | ||
| 661 | --color-icon-muted: #746c75; | ||
| 662 | --color-icon-subtle: #746c75; | ||
| 663 | --color-header: rgba(245, 247, 248, 0.97); | ||
| 664 | --color-header-border: #d8e4ee; | ||
| 665 | --color-glass-surface: rgba(232, 233, 235, 0.74); | ||
| 666 | --color-glass-tint: rgba(232, 233, 235, 0.22); | ||
| 667 | --color-status-bar: #f5f7f8; | ||
| 668 | --color-md-body: #241523; | ||
| 669 | --color-md-strong: #241523; | ||
| 670 | --color-md-link: #2672af; | ||
| 671 | --color-md-blockquote-border: #cdd4dc; | ||
| 672 | --color-md-blockquote-bg: #e8eff4; | ||
| 673 | --color-md-code-bg: #f0f1f3; | ||
| 674 | --color-md-code-text: #241523; | ||
| 675 | --color-md-user-code-bg: rgba(36, 21, 35, 0.18); | ||
| 676 | --color-md-user-code-text: #241523; | ||
| 677 | --color-md-user-fence-bg: rgba(0, 0, 0, 0.16); | ||
| 678 | --color-md-user-fence-text: #241523; | ||
| 679 | --color-md-hr: #cdd4dc; | ||
| 680 | --color-user-bubble: #d0dfeb; | ||
| 681 | --color-user-bubble-foreground: #241523; | ||
| 682 | --color-user-bubble-foreground-muted: rgba(36, 21, 35, 0.78); | ||
| 683 | --color-user-bubble-skill-foreground: #0a6c72; | ||
| 684 | --color-backdrop: rgba(0, 0, 0, 0.22); | ||
| 685 | --color-drawer: rgba(228, 236, 242, 0.99); | ||
| 686 | --color-drawer-shadow: rgba(0, 0, 0, 0.12); | ||
| 687 | --color-dot-separator: rgba(116, 108, 117, 0.35); | ||
| 688 | --color-wordmark: #241523; | ||
| 689 | --color-chevron: rgba(116, 108, 117, 0.42); | ||
| 690 | --color-adaptive-amber-50-950-a40: oklch(98.7% 0.022 95.277); | ||
| 691 | --color-adaptive-amber-200-900-a60: oklch(92.4% 0.12 95.746); | ||
| 692 | --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 12%); | ||
| 693 | --color-adaptive-amber-700-300: oklch(55.5% 0.163 48.998); | ||
| 694 | --color-adaptive-amber-700-400: oklch(55.5% 0.163 48.998); | ||
| 695 | --color-adaptive-amber-800-200: oklch(47.3% 0.137 46.201); | ||
| 696 | --color-adaptive-blue-50-blue-400-a14: oklch(97% 0.014 254.604); | ||
| 697 | --color-adaptive-blue-300-a50-blue-400-a28: oklch(80.9% 0.105 251.813 / 50%); | ||
| 698 | --color-adaptive-blue-500-a20-blue-400-a15: oklch(62.3% 0.214 259.815 / 20%); | ||
| 699 | --color-adaptive-blue-500-400: oklch(62.3% 0.214 259.815); | ||
| 700 | --color-adaptive-blue-600-400: oklch(54.6% 0.245 262.881); | ||
| 701 | --color-adaptive-black-a10-a25: rgb(0 0 0 / 10%); | ||
| 702 | --color-adaptive-black-a15-a35: rgb(0 0 0 / 15%); | ||
| 703 | --color-adaptive-emerald-500-a12-a16: oklch(69.6% 0.17 162.48 / 12%); | ||
| 704 | --color-adaptive-emerald-600-400: oklch(59.6% 0.145 163.225); | ||
| 705 | --color-adaptive-emerald-700-300: oklch(50.8% 0.118 165.612); | ||
| 706 | --color-adaptive-indigo-500-a12-a16: oklch(58.5% 0.233 277.117 / 12%); | ||
| 707 | --color-adaptive-indigo-600-300: oklch(51.1% 0.262 276.966); | ||
| 708 | --color-adaptive-indigo-700-300: oklch(45.7% 0.24 277.023); | ||
| 709 | --color-adaptive-neutral-100-900: oklch(97% 0 0); | ||
| 710 | --color-adaptive-neutral-200-700-a60: oklch(92.2% 0 0); | ||
| 711 | --color-adaptive-neutral-200-800: oklch(92.2% 0 0); | ||
| 712 | --color-adaptive-neutral-200-a70-white-a8: oklch(92.2% 0 0 / 70%); | ||
| 713 | --color-adaptive-neutral-200-white-a6: oklch(92.2% 0 0); | ||
| 714 | --color-adaptive-neutral-200-white-a8: oklch(92.2% 0 0); | ||
| 715 | --color-adaptive-neutral-200-a80-white-a8: oklch(92.2% 0 0 / 80%); | ||
| 716 | --color-adaptive-neutral-300-a60-white-a12: oklch(87% 0 0 / 60%); | ||
| 717 | --color-adaptive-neutral-400-500: oklch(70.8% 0 0); | ||
| 718 | --color-adaptive-neutral-400-a60-500-a60: oklch(70.8% 0 0 / 60%); | ||
| 719 | --color-adaptive-neutral-400-a80-500-a80: oklch(70.8% 0 0 / 80%); | ||
| 720 | --color-adaptive-neutral-500-a10-a16: oklch(55.6% 0 0 / 10%); | ||
| 721 | --color-adaptive-neutral-500-400: oklch(55.6% 0 0); | ||
| 722 | --color-adaptive-neutral-500-500: oklch(55.6% 0 0); | ||
| 723 | --color-adaptive-neutral-600-300: oklch(43.9% 0 0); | ||
| 724 | --color-adaptive-neutral-600-400: oklch(43.9% 0 0); | ||
| 725 | --color-adaptive-neutral-950-50: oklch(14.5% 0 0); | ||
| 726 | --color-adaptive-red-50-950-a80: oklch(97.1% 0.013 17.38); | ||
| 727 | --color-adaptive-red-200-800: oklch(88.5% 0.062 18.334); | ||
| 728 | --color-adaptive-red-600-a80-400-a80: oklch(57.7% 0.245 27.325 / 80%); | ||
| 729 | --color-adaptive-red-700-300: oklch(50.5% 0.213 27.518); | ||
| 730 | --color-adaptive-rose-100-500-a18: oklch(94.1% 0.03 12.58); | ||
| 731 | --color-adaptive-rose-100-a80-500-a12: oklch(94.1% 0.03 12.58 / 80%); | ||
| 732 | --color-adaptive-rose-300-a70-400-a28: oklch(81% 0.117 11.638 / 70%); | ||
| 733 | --color-adaptive-rose-500-a12-a16: oklch(64.5% 0.246 16.439 / 12%); | ||
| 734 | --color-adaptive-rose-500-400: oklch(64.5% 0.246 16.439); | ||
| 735 | --color-adaptive-rose-600-400: oklch(58.6% 0.253 17.585); | ||
| 736 | --color-adaptive-rose-700-300: oklch(51.4% 0.222 16.935); | ||
| 737 | --color-adaptive-sky-500-a12-a16: oklch(68.5% 0.169 237.323 / 12%); | ||
| 738 | --color-adaptive-sky-600-400: oklch(58.8% 0.158 241.966); | ||
| 739 | --color-adaptive-sky-700-300: oklch(50% 0.134 242.749); | ||
| 740 | --color-adaptive-violet-500-a12-a16: oklch(60.6% 0.25 292.717 / 12%); | ||
| 741 | --color-adaptive-violet-600-400: oklch(54.1% 0.281 293.009); | ||
| 742 | --color-adaptive-violet-700-300: oklch(49.1% 0.27 292.581); | ||
| 743 | --color-adaptive-white-neutral-950-a70: #fff; | ||
| 744 | --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%); | ||
| 745 | --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938); | ||
| 746 | --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786); | ||
| 747 | } | ||
| 748 | |||
| 749 | @variant ocean-dark { | ||
| 750 | --color-screen: #17212b; | ||
| 751 | --color-sheet: rgba(23, 33, 43, 0.98); | ||
| 752 | --color-sheet-solid: #17212b; | ||
| 753 | --color-card: #333b45; | ||
| 754 | --color-card-alt: #17212b; | ||
| 755 | --color-card-translucent: rgba(51, 59, 69, 0.8); | ||
| 756 | --color-foreground: #fffaff; | ||
| 757 | --color-foreground-secondary: #8d8f97; | ||
| 758 | --color-foreground-muted: #969ca6; | ||
| 759 | --color-foreground-tertiary: #8d8f97; | ||
| 760 | --color-border: #405567; | ||
| 761 | --color-border-subtle: rgba(64, 85, 103, 0.7); | ||
| 762 | --color-separator: rgba(64, 85, 103, 0.55); | ||
| 763 | --color-subtle: #233544; | ||
| 764 | --color-subtle-strong: #293f52; | ||
| 765 | --color-inline-skill-background: #324e66; | ||
| 766 | --color-inline-skill-border: rgba(112, 185, 238, 0.42); | ||
| 767 | --color-inline-skill-foreground: #fffaff; | ||
| 768 | --color-primary: #70b9ee; | ||
| 769 | --color-primary-foreground: #241523; | ||
| 770 | --color-primary-shadow: #000000; | ||
| 771 | --color-secondary: #293f52; | ||
| 772 | --color-secondary-foreground: #fffaff; | ||
| 773 | --color-secondary-border: #405567; | ||
| 774 | --color-switch-active-track: #70b9ee; | ||
| 775 | --color-switch-active-thumb: #241523; | ||
| 776 | --color-switch-inactive-track: #293f52; | ||
| 777 | --color-switch-inactive-thumb: #969ca6; | ||
| 778 | --color-danger: #3c2630; | ||
| 779 | --color-danger-border: rgba(251, 65, 74, 0.32); | ||
| 780 | --color-danger-foreground: #ff6467; | ||
| 781 | --color-input: #333b45; | ||
| 782 | --color-input-border: #4f677b; | ||
| 783 | --color-sidebar-search: #424e5a; | ||
| 784 | --color-placeholder: #a4a4ac; | ||
| 785 | --color-icon: #fffaff; | ||
| 786 | --color-icon-muted: #8d8f97; | ||
| 787 | --color-icon-subtle: #8d8f97; | ||
| 788 | --color-header: rgba(23, 33, 43, 0.97); | ||
| 789 | --color-header-border: #36566f; | ||
| 790 | --color-glass-surface: rgba(65, 72, 81, 0.74); | ||
| 791 | --color-glass-tint: rgba(65, 72, 81, 0.22); | ||
| 792 | --color-status-bar: #17212b; | ||
| 793 | --color-md-body: #fffaff; | ||
| 794 | --color-md-strong: #fffaff; | ||
| 795 | --color-md-link: #70b9ee; | ||
| 796 | --color-md-blockquote-border: #405567; | ||
| 797 | --color-md-blockquote-bg: #233544; | ||
| 798 | --color-md-code-bg: #252e38; | ||
| 799 | --color-md-code-text: #fffaff; | ||
| 800 | --color-md-user-code-bg: rgba(255, 250, 255, 0.18); | ||
| 801 | --color-md-user-code-text: #fffaff; | ||
| 802 | --color-md-user-fence-bg: rgba(0, 0, 0, 0.28); | ||
| 803 | --color-md-user-fence-text: #fffaff; | ||
| 804 | --color-md-hr: #405567; | ||
| 805 | --color-user-bubble: #375871; | ||
| 806 | --color-user-bubble-foreground: #fffaff; | ||
| 807 | --color-user-bubble-foreground-muted: rgba(255, 250, 255, 0.78); | ||
| 808 | --color-user-bubble-skill-foreground: #75d8dd; | ||
| 809 | --color-backdrop: rgba(0, 0, 0, 0.48); | ||
| 810 | --color-drawer: rgba(30, 45, 59, 0.99); | ||
| 811 | --color-drawer-shadow: rgba(0, 0, 0, 0.32); | ||
| 812 | --color-dot-separator: rgba(141, 143, 151, 0.35); | ||
| 813 | --color-wordmark: #fffaff; | ||
| 814 | --color-chevron: rgba(141, 143, 151, 0.42); | ||
| 815 | --color-adaptive-amber-50-950-a40: oklch(27.9% 0.077 45.635 / 40%); | ||
| 816 | --color-adaptive-amber-200-900-a60: oklch(41.4% 0.112 45.904 / 60%); | ||
| 817 | --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 16%); | ||
| 818 | --color-adaptive-amber-700-300: oklch(87.9% 0.169 91.605); | ||
| 819 | --color-adaptive-amber-700-400: oklch(82.8% 0.189 84.429); | ||
| 820 | --color-adaptive-amber-800-200: oklch(92.4% 0.12 95.746); | ||
| 821 | --color-adaptive-blue-50-blue-400-a14: oklch(70.7% 0.165 254.624 / 14%); | ||
| 822 | --color-adaptive-blue-300-a50-blue-400-a28: oklch(70.7% 0.165 254.624 / 28%); | ||
| 823 | --color-adaptive-blue-500-a20-blue-400-a15: oklch(70.7% 0.165 254.624 / 15%); | ||
| 824 | --color-adaptive-blue-500-400: oklch(70.7% 0.165 254.624); | ||
| 825 | --color-adaptive-blue-600-400: oklch(70.7% 0.165 254.624); | ||
| 826 | --color-adaptive-black-a10-a25: rgb(0 0 0 / 25%); | ||
| 827 | --color-adaptive-black-a15-a35: rgb(0 0 0 / 35%); | ||
| 828 | --color-adaptive-emerald-500-a12-a16: oklch(69.6% 0.17 162.48 / 16%); | ||
| 829 | --color-adaptive-emerald-600-400: oklch(76.5% 0.177 163.223); | ||
| 830 | --color-adaptive-emerald-700-300: oklch(84.5% 0.143 164.978); | ||
| 831 | --color-adaptive-indigo-500-a12-a16: oklch(58.5% 0.233 277.117 / 16%); | ||
| 832 | --color-adaptive-indigo-600-300: oklch(78.5% 0.115 274.713); | ||
| 833 | --color-adaptive-indigo-700-300: oklch(78.5% 0.115 274.713); | ||
| 834 | --color-adaptive-neutral-100-900: oklch(20.5% 0 0); | ||
| 835 | --color-adaptive-neutral-200-700-a60: oklch(37.1% 0 0 / 60%); | ||
| 836 | --color-adaptive-neutral-200-800: oklch(26.9% 0 0); | ||
| 837 | --color-adaptive-neutral-200-a70-white-a8: rgb(255 255 255 / 8%); | ||
| 838 | --color-adaptive-neutral-200-white-a6: rgb(255 255 255 / 6%); | ||
| 839 | --color-adaptive-neutral-200-white-a8: rgb(255 255 255 / 8%); | ||
| 840 | --color-adaptive-neutral-200-a80-white-a8: rgb(255 255 255 / 8%); | ||
| 841 | --color-adaptive-neutral-300-a60-white-a12: rgb(255 255 255 / 12%); | ||
| 842 | --color-adaptive-neutral-400-500: oklch(55.6% 0 0); | ||
| 843 | --color-adaptive-neutral-400-a60-500-a60: oklch(55.6% 0 0 / 60%); | ||
| 844 | --color-adaptive-neutral-400-a80-500-a80: oklch(55.6% 0 0 / 80%); | ||
| 845 | --color-adaptive-neutral-500-a10-a16: oklch(55.6% 0 0 / 16%); | ||
| 846 | --color-adaptive-neutral-500-400: oklch(70.8% 0 0); | ||
| 847 | --color-adaptive-neutral-500-500: oklch(55.6% 0 0); | ||
| 848 | --color-adaptive-neutral-600-300: oklch(87% 0 0); | ||
| 849 | --color-adaptive-neutral-600-400: oklch(70.8% 0 0); | ||
| 850 | --color-adaptive-neutral-950-50: oklch(98.5% 0 0); | ||
| 851 | --color-adaptive-red-50-950-a80: oklch(25.8% 0.092 26.042 / 80%); | ||
| 852 | --color-adaptive-red-200-800: oklch(44.4% 0.177 26.899); | ||
| 853 | --color-adaptive-red-600-a80-400-a80: oklch(70.4% 0.191 22.216 / 80%); | ||
| 854 | --color-adaptive-red-700-300: oklch(80.8% 0.114 19.571); | ||
| 855 | --color-adaptive-rose-100-500-a18: oklch(64.5% 0.246 16.439 / 18%); | ||
| 856 | --color-adaptive-rose-100-a80-500-a12: oklch(64.5% 0.246 16.439 / 12%); | ||
| 857 | --color-adaptive-rose-300-a70-400-a28: oklch(71.2% 0.194 13.428 / 28%); | ||
| 858 | --color-adaptive-rose-500-a12-a16: oklch(64.5% 0.246 16.439 / 16%); | ||
| 859 | --color-adaptive-rose-500-400: oklch(71.2% 0.194 13.428); | ||
| 860 | --color-adaptive-rose-600-400: oklch(71.2% 0.194 13.428); | ||
| 861 | --color-adaptive-rose-700-300: oklch(81% 0.117 11.638); | ||
| 862 | --color-adaptive-sky-500-a12-a16: oklch(68.5% 0.169 237.323 / 16%); | ||
| 863 | --color-adaptive-sky-600-400: oklch(74.6% 0.16 232.661); | ||
| 864 | --color-adaptive-sky-700-300: oklch(82.8% 0.111 230.318); | ||
| 865 | --color-adaptive-violet-500-a12-a16: oklch(60.6% 0.25 292.717 / 16%); | ||
| 866 | --color-adaptive-violet-600-400: oklch(70.2% 0.183 293.541); | ||
| 867 | --color-adaptive-violet-700-300: oklch(81.1% 0.111 293.571); | ||
| 868 | --color-adaptive-white-neutral-950-a70: oklch(14.5% 0 0 / 70%); | ||
| 869 | --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%); | ||
| 870 | --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067); | ||
| 871 | --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286); | ||
| 872 | } | ||
| 873 | |||
| 874 | @variant ember-light { | ||
| 875 | --color-screen: #f9f7f5; | ||
| 876 | --color-sheet: rgba(249, 247, 245, 0.98); | ||
| 877 | --color-sheet-solid: #f9f7f5; | ||
| 878 | --color-card: #f1efee; | ||
| 879 | --color-card-alt: #f9f7f5; | ||
| 880 | --color-card-translucent: rgba(241, 239, 238, 0.8); | ||
| 881 | --color-foreground: #241523; | ||
| 882 | --color-foreground-secondary: #766c74; | ||
| 883 | --color-foreground-muted: #74686f; | ||
| 884 | --color-foreground-tertiary: #766c74; | ||
| 885 | --color-border: #ddd2ce; | ||
| 886 | --color-border-subtle: rgba(221, 210, 206, 0.7); | ||
| 887 | --color-separator: rgba(221, 210, 206, 0.55); | ||
| 888 | --color-subtle: #f4ede9; | ||
| 889 | --color-subtle-strong: #f3eae5; | ||
| 890 | --color-inline-skill-background: #eee0d9; | ||
| 891 | --color-inline-skill-border: rgba(174, 85, 42, 0.42); | ||
| 892 | --color-inline-skill-foreground: #241523; | ||
| 893 | --color-primary: #ae552a; | ||
| 894 | --color-primary-foreground: #fffaff; | ||
| 895 | --color-primary-shadow: #000000; | ||
| 896 | --color-secondary: #f3eae5; | ||
| 897 | --color-secondary-foreground: #241523; | ||
| 898 | --color-secondary-border: #ddd2ce; | ||
| 899 | --color-switch-active-track: #ae552a; | ||
| 900 | --color-switch-active-thumb: #fffaff; | ||
| 901 | --color-switch-inactive-track: #f3eae5; | ||
| 902 | --color-switch-inactive-thumb: #74686f; | ||
| 903 | --color-danger: #f9e7e6; | ||
| 904 | --color-danger-border: rgba(251, 44, 54, 0.32); | ||
| 905 | --color-danger-foreground: #c10007; | ||
| 906 | --color-input: #f1efee; | ||
| 907 | --color-input-border: #d4c6c1; | ||
| 908 | --color-sidebar-search: #e2d9d6; | ||
| 909 | --color-placeholder: #736971; | ||
| 910 | --color-icon: #241523; | ||
| 911 | --color-icon-muted: #766c74; | ||
| 912 | --color-icon-subtle: #766c74; | ||
| 913 | --color-header: rgba(249, 247, 245, 0.97); | ||
| 914 | --color-header-border: #eee0d9; | ||
| 915 | --color-glass-surface: rgba(236, 233, 233, 0.74); | ||
| 916 | --color-glass-tint: rgba(236, 233, 233, 0.22); | ||
| 917 | --color-status-bar: #f9f7f5; | ||
| 918 | --color-md-body: #241523; | ||
| 919 | --color-md-strong: #241523; | ||
| 920 | --color-md-link: #ae552a; | ||
| 921 | --color-md-blockquote-border: #ddd2ce; | ||
| 922 | --color-md-blockquote-bg: #f4ede9; | ||
| 923 | --color-md-code-bg: #f3f1f0; | ||
| 924 | --color-md-code-text: #241523; | ||
| 925 | --color-md-user-code-bg: rgba(36, 21, 35, 0.18); | ||
| 926 | --color-md-user-code-text: #241523; | ||
| 927 | --color-md-user-fence-bg: rgba(0, 0, 0, 0.16); | ||
| 928 | --color-md-user-fence-text: #241523; | ||
| 929 | --color-md-hr: #ddd2ce; | ||
| 930 | --color-user-bubble: #ebdad1; | ||
| 931 | --color-user-bubble-foreground: #241523; | ||
| 932 | --color-user-bubble-foreground-muted: rgba(36, 21, 35, 0.78); | ||
| 933 | --color-user-bubble-skill-foreground: #b13535; | ||
| 934 | --color-backdrop: rgba(0, 0, 0, 0.22); | ||
| 935 | --color-drawer: rgba(243, 234, 229, 0.99); | ||
| 936 | --color-drawer-shadow: rgba(0, 0, 0, 0.12); | ||
| 937 | --color-dot-separator: rgba(118, 108, 116, 0.35); | ||
| 938 | --color-wordmark: #241523; | ||
| 939 | --color-chevron: rgba(118, 108, 116, 0.42); | ||
| 940 | --color-adaptive-amber-50-950-a40: oklch(98.7% 0.022 95.277); | ||
| 941 | --color-adaptive-amber-200-900-a60: oklch(92.4% 0.12 95.746); | ||
| 942 | --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 12%); | ||
| 943 | --color-adaptive-amber-700-300: oklch(55.5% 0.163 48.998); | ||
| 944 | --color-adaptive-amber-700-400: oklch(55.5% 0.163 48.998); | ||
| 945 | --color-adaptive-amber-800-200: oklch(47.3% 0.137 46.201); | ||
| 946 | --color-adaptive-blue-50-blue-400-a14: oklch(97% 0.014 254.604); | ||
| 947 | --color-adaptive-blue-300-a50-blue-400-a28: oklch(80.9% 0.105 251.813 / 50%); | ||
| 948 | --color-adaptive-blue-500-a20-blue-400-a15: oklch(62.3% 0.214 259.815 / 20%); | ||
| 949 | --color-adaptive-blue-500-400: oklch(62.3% 0.214 259.815); | ||
| 950 | --color-adaptive-blue-600-400: oklch(54.6% 0.245 262.881); | ||
| 951 | --color-adaptive-black-a10-a25: rgb(0 0 0 / 10%); | ||
| 952 | --color-adaptive-black-a15-a35: rgb(0 0 0 / 15%); | ||
| 953 | --color-adaptive-emerald-500-a12-a16: oklch(69.6% 0.17 162.48 / 12%); | ||
| 954 | --color-adaptive-emerald-600-400: oklch(59.6% 0.145 163.225); | ||
| 955 | --color-adaptive-emerald-700-300: oklch(50.8% 0.118 165.612); | ||
| 956 | --color-adaptive-indigo-500-a12-a16: oklch(58.5% 0.233 277.117 / 12%); | ||
| 957 | --color-adaptive-indigo-600-300: oklch(51.1% 0.262 276.966); | ||
| 958 | --color-adaptive-indigo-700-300: oklch(45.7% 0.24 277.023); | ||
| 959 | --color-adaptive-neutral-100-900: oklch(97% 0 0); | ||
| 960 | --color-adaptive-neutral-200-700-a60: oklch(92.2% 0 0); | ||
| 961 | --color-adaptive-neutral-200-800: oklch(92.2% 0 0); | ||
| 962 | --color-adaptive-neutral-200-a70-white-a8: oklch(92.2% 0 0 / 70%); | ||
| 963 | --color-adaptive-neutral-200-white-a6: oklch(92.2% 0 0); | ||
| 964 | --color-adaptive-neutral-200-white-a8: oklch(92.2% 0 0); | ||
| 965 | --color-adaptive-neutral-200-a80-white-a8: oklch(92.2% 0 0 / 80%); | ||
| 966 | --color-adaptive-neutral-300-a60-white-a12: oklch(87% 0 0 / 60%); | ||
| 967 | --color-adaptive-neutral-400-500: oklch(70.8% 0 0); | ||
| 968 | --color-adaptive-neutral-400-a60-500-a60: oklch(70.8% 0 0 / 60%); | ||
| 969 | --color-adaptive-neutral-400-a80-500-a80: oklch(70.8% 0 0 / 80%); | ||
| 970 | --color-adaptive-neutral-500-a10-a16: oklch(55.6% 0 0 / 10%); | ||
| 971 | --color-adaptive-neutral-500-400: oklch(55.6% 0 0); | ||
| 972 | --color-adaptive-neutral-500-500: oklch(55.6% 0 0); | ||
| 973 | --color-adaptive-neutral-600-300: oklch(43.9% 0 0); | ||
| 974 | --color-adaptive-neutral-600-400: oklch(43.9% 0 0); | ||
| 975 | --color-adaptive-neutral-950-50: oklch(14.5% 0 0); | ||
| 976 | --color-adaptive-red-50-950-a80: oklch(97.1% 0.013 17.38); | ||
| 977 | --color-adaptive-red-200-800: oklch(88.5% 0.062 18.334); | ||
| 978 | --color-adaptive-red-600-a80-400-a80: oklch(57.7% 0.245 27.325 / 80%); | ||
| 979 | --color-adaptive-red-700-300: oklch(50.5% 0.213 27.518); | ||
| 980 | --color-adaptive-rose-100-500-a18: oklch(94.1% 0.03 12.58); | ||
| 981 | --color-adaptive-rose-100-a80-500-a12: oklch(94.1% 0.03 12.58 / 80%); | ||
| 982 | --color-adaptive-rose-300-a70-400-a28: oklch(81% 0.117 11.638 / 70%); | ||
| 983 | --color-adaptive-rose-500-a12-a16: oklch(64.5% 0.246 16.439 / 12%); | ||
| 984 | --color-adaptive-rose-500-400: oklch(64.5% 0.246 16.439); | ||
| 985 | --color-adaptive-rose-600-400: oklch(58.6% 0.253 17.585); | ||
| 986 | --color-adaptive-rose-700-300: oklch(51.4% 0.222 16.935); | ||
| 987 | --color-adaptive-sky-500-a12-a16: oklch(68.5% 0.169 237.323 / 12%); | ||
| 988 | --color-adaptive-sky-600-400: oklch(58.8% 0.158 241.966); | ||
| 989 | --color-adaptive-sky-700-300: oklch(50% 0.134 242.749); | ||
| 990 | --color-adaptive-violet-500-a12-a16: oklch(60.6% 0.25 292.717 / 12%); | ||
| 991 | --color-adaptive-violet-600-400: oklch(54.1% 0.281 293.009); | ||
| 992 | --color-adaptive-violet-700-300: oklch(49.1% 0.27 292.581); | ||
| 993 | --color-adaptive-white-neutral-950-a70: #fff; | ||
| 994 | --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%); | ||
| 995 | --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938); | ||
| 996 | --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786); | ||
| 997 | } | ||
| 998 | |||
| 999 | @variant ember-dark { | ||
| 1000 | --color-screen: #291e1a; | ||
| 1001 | --color-sheet: rgba(41, 30, 26, 0.98); | ||
| 1002 | --color-sheet-solid: #291e1a; | ||
| 1003 | --color-card: #433835; | ||
| 1004 | --color-card-alt: #291e1a; | ||
| 1005 | --color-card-translucent: rgba(67, 56, 53, 0.8); | ||
| 1006 | --color-foreground: #fffaff; | ||
| 1007 | --color-foreground-secondary: #968e8f; | ||
| 1008 | --color-foreground-muted: #a59996; | ||
| 1009 | --color-foreground-tertiary: #968e8f; | ||
| 1010 | --color-border: #664c3f; | ||
| 1011 | --color-border-subtle: rgba(102, 76, 63, 0.7); | ||
| 1012 | --color-separator: rgba(102, 76, 63, 0.55); | ||
| 1013 | --color-subtle: #432e23; | ||
| 1014 | --color-subtle-strong: #513728; | ||
| 1015 | --color-inline-skill-background: #644330; | ||
| 1016 | --color-inline-skill-border: rgba(240, 154, 100, 0.42); | ||
| 1017 | --color-inline-skill-foreground: #fffaff; | ||
| 1018 | --color-primary: #f09a64; | ||
| 1019 | --color-primary-foreground: #241523; | ||
| 1020 | --color-primary-shadow: #000000; | ||
| 1021 | --color-secondary: #513728; | ||
| 1022 | --color-secondary-foreground: #fffaff; | ||
| 1023 | --color-secondary-border: #664c3f; | ||
| 1024 | --color-switch-active-track: #f09a64; | ||
| 1025 | --color-switch-active-thumb: #241523; | ||
| 1026 | --color-switch-inactive-track: #513728; | ||
| 1027 | --color-switch-inactive-thumb: #a59996; | ||
| 1028 | --color-danger: #4a2321; | ||
| 1029 | --color-danger-border: rgba(251, 65, 74, 0.32); | ||
| 1030 | --color-danger-foreground: #ff6467; | ||
| 1031 | --color-input: #433835; | ||
| 1032 | --color-input-border: #7a5d4d; | ||
| 1033 | --color-sidebar-search: #584943; | ||
| 1034 | --color-placeholder: #aba3a5; | ||
| 1035 | --color-icon: #fffaff; | ||
| 1036 | --color-icon-muted: #968e8f; | ||
| 1037 | --color-icon-subtle: #968e8f; | ||
| 1038 | --color-header: rgba(41, 30, 26, 0.97); | ||
| 1039 | --color-header-border: #6e4934; | ||
| 1040 | --color-glass-surface: rgba(79, 69, 67, 0.74); | ||
| 1041 | --color-glass-tint: rgba(79, 69, 67, 0.22); | ||
| 1042 | --color-status-bar: #291e1a; | ||
| 1043 | --color-md-body: #fffaff; | ||
| 1044 | --color-md-strong: #fffaff; | ||
| 1045 | --color-md-link: #f09a64; | ||
| 1046 | --color-md-blockquote-border: #664c3f; | ||
| 1047 | --color-md-blockquote-bg: #432e23; | ||
| 1048 | --color-md-code-bg: #362b27; | ||
| 1049 | --color-md-code-text: #fffaff; | ||
| 1050 | --color-md-user-code-bg: rgba(255, 250, 255, 0.18); | ||
| 1051 | --color-md-user-code-text: #fffaff; | ||
| 1052 | --color-md-user-fence-bg: rgba(0, 0, 0, 0.28); | ||
| 1053 | --color-md-user-fence-text: #fffaff; | ||
| 1054 | --color-md-hr: #664c3f; | ||
| 1055 | --color-user-bubble: #704b34; | ||
| 1056 | --color-user-bubble-foreground: #fffaff; | ||
| 1057 | --color-user-bubble-foreground-muted: rgba(255, 250, 255, 0.78); | ||
| 1058 | --color-user-bubble-skill-foreground: #fab6ad; | ||
| 1059 | --color-backdrop: rgba(0, 0, 0, 0.48); | ||
| 1060 | --color-drawer: rgba(57, 40, 31, 0.99); | ||
| 1061 | --color-drawer-shadow: rgba(0, 0, 0, 0.32); | ||
| 1062 | --color-dot-separator: rgba(150, 142, 143, 0.35); | ||
| 1063 | --color-wordmark: #fffaff; | ||
| 1064 | --color-chevron: rgba(150, 142, 143, 0.42); | ||
| 1065 | --color-adaptive-amber-50-950-a40: oklch(27.9% 0.077 45.635 / 40%); | ||
| 1066 | --color-adaptive-amber-200-900-a60: oklch(41.4% 0.112 45.904 / 60%); | ||
| 1067 | --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 16%); | ||
| 1068 | --color-adaptive-amber-700-300: oklch(87.9% 0.169 91.605); | ||
| 1069 | --color-adaptive-amber-700-400: oklch(82.8% 0.189 84.429); | ||
| 1070 | --color-adaptive-amber-800-200: oklch(92.4% 0.12 95.746); | ||
| 1071 | --color-adaptive-blue-50-blue-400-a14: oklch(70.7% 0.165 254.624 / 14%); | ||
| 1072 | --color-adaptive-blue-300-a50-blue-400-a28: oklch(70.7% 0.165 254.624 / 28%); | ||
| 1073 | --color-adaptive-blue-500-a20-blue-400-a15: oklch(70.7% 0.165 254.624 / 15%); | ||
| 1074 | --color-adaptive-blue-500-400: oklch(70.7% 0.165 254.624); | ||
| 1075 | --color-adaptive-blue-600-400: oklch(70.7% 0.165 254.624); | ||
| 1076 | --color-adaptive-black-a10-a25: rgb(0 0 0 / 25%); | ||
| 1077 | --color-adaptive-black-a15-a35: rgb(0 0 0 / 35%); | ||
| 1078 | --color-adaptive-emerald-500-a12-a16: oklch(69.6% 0.17 162.48 / 16%); | ||
| 1079 | --color-adaptive-emerald-600-400: oklch(76.5% 0.177 163.223); | ||
| 1080 | --color-adaptive-emerald-700-300: oklch(84.5% 0.143 164.978); | ||
| 1081 | --color-adaptive-indigo-500-a12-a16: oklch(58.5% 0.233 277.117 / 16%); | ||
| 1082 | --color-adaptive-indigo-600-300: oklch(78.5% 0.115 274.713); | ||
| 1083 | --color-adaptive-indigo-700-300: oklch(78.5% 0.115 274.713); | ||
| 1084 | --color-adaptive-neutral-100-900: oklch(20.5% 0 0); | ||
| 1085 | --color-adaptive-neutral-200-700-a60: oklch(37.1% 0 0 / 60%); | ||
| 1086 | --color-adaptive-neutral-200-800: oklch(26.9% 0 0); | ||
| 1087 | --color-adaptive-neutral-200-a70-white-a8: rgb(255 255 255 / 8%); | ||
| 1088 | --color-adaptive-neutral-200-white-a6: rgb(255 255 255 / 6%); | ||
| 1089 | --color-adaptive-neutral-200-white-a8: rgb(255 255 255 / 8%); | ||
| 1090 | --color-adaptive-neutral-200-a80-white-a8: rgb(255 255 255 / 8%); | ||
| 1091 | --color-adaptive-neutral-300-a60-white-a12: rgb(255 255 255 / 12%); | ||
| 1092 | --color-adaptive-neutral-400-500: oklch(55.6% 0 0); | ||
| 1093 | --color-adaptive-neutral-400-a60-500-a60: oklch(55.6% 0 0 / 60%); | ||
| 1094 | --color-adaptive-neutral-400-a80-500-a80: oklch(55.6% 0 0 / 80%); | ||
| 1095 | --color-adaptive-neutral-500-a10-a16: oklch(55.6% 0 0 / 16%); | ||
| 1096 | --color-adaptive-neutral-500-400: oklch(70.8% 0 0); | ||
| 1097 | --color-adaptive-neutral-500-500: oklch(55.6% 0 0); | ||
| 1098 | --color-adaptive-neutral-600-300: oklch(87% 0 0); | ||
| 1099 | --color-adaptive-neutral-600-400: oklch(70.8% 0 0); | ||
| 1100 | --color-adaptive-neutral-950-50: oklch(98.5% 0 0); | ||
| 1101 | --color-adaptive-red-50-950-a80: oklch(25.8% 0.092 26.042 / 80%); | ||
| 1102 | --color-adaptive-red-200-800: oklch(44.4% 0.177 26.899); | ||
| 1103 | --color-adaptive-red-600-a80-400-a80: oklch(70.4% 0.191 22.216 / 80%); | ||
| 1104 | --color-adaptive-red-700-300: oklch(80.8% 0.114 19.571); | ||
| 1105 | --color-adaptive-rose-100-500-a18: oklch(64.5% 0.246 16.439 / 18%); | ||
| 1106 | --color-adaptive-rose-100-a80-500-a12: oklch(64.5% 0.246 16.439 / 12%); | ||
| 1107 | --color-adaptive-rose-300-a70-400-a28: oklch(71.2% 0.194 13.428 / 28%); | ||
| 1108 | --color-adaptive-rose-500-a12-a16: oklch(64.5% 0.246 16.439 / 16%); | ||
| 1109 | --color-adaptive-rose-500-400: oklch(71.2% 0.194 13.428); | ||
| 1110 | --color-adaptive-rose-600-400: oklch(71.2% 0.194 13.428); | ||
| 1111 | --color-adaptive-rose-700-300: oklch(81% 0.117 11.638); | ||
| 1112 | --color-adaptive-sky-500-a12-a16: oklch(68.5% 0.169 237.323 / 16%); | ||
| 1113 | --color-adaptive-sky-600-400: oklch(74.6% 0.16 232.661); | ||
| 1114 | --color-adaptive-sky-700-300: oklch(82.8% 0.111 230.318); | ||
| 1115 | --color-adaptive-violet-500-a12-a16: oklch(60.6% 0.25 292.717 / 16%); | ||
| 1116 | --color-adaptive-violet-600-400: oklch(70.2% 0.183 293.541); | ||
| 1117 | --color-adaptive-violet-700-300: oklch(81.1% 0.111 293.571); | ||
| 1118 | --color-adaptive-white-neutral-950-a70: oklch(14.5% 0 0 / 70%); | ||
| 1119 | --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%); | ||
| 1120 | --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067); | ||
| 1121 | --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286); | ||
| 1122 | } | ||
| 1123 | |||
| 1124 | @variant iris-light { | ||
| 1125 | --color-screen: #f8f7f9; | ||
| 1126 | --color-sheet: rgba(248, 247, 249, 0.98); | ||
| 1127 | --color-sheet-solid: #f8f7f9; | ||
| 1128 | --color-card: #f0eff2; | ||
| 1129 | --color-card-alt: #f8f7f9; | ||
| 1130 | --color-card-translucent: rgba(240, 239, 242, 0.8); | ||
| 1131 | --color-foreground: #241523; | ||
| 1132 | --color-foreground-secondary: #766c76; | ||
| 1133 | --color-foreground-muted: #726874; | ||
| 1134 | --color-foreground-tertiary: #766c76; | ||
| 1135 | --color-border: #d6d1de; | ||
| 1136 | --color-border-subtle: rgba(214, 209, 222, 0.7); | ||
| 1137 | --color-separator: rgba(214, 209, 222, 0.55); | ||
| 1138 | --color-subtle: #f0edf6; | ||
| 1139 | --color-subtle-strong: #edeaf4; | ||
| 1140 | --color-inline-skill-background: #e5e0f0; | ||
| 1141 | --color-inline-skill-border: rgba(114, 83, 185, 0.42); | ||
| 1142 | --color-inline-skill-foreground: #241523; | ||
| 1143 | --color-primary: #7253b9; | ||
| 1144 | --color-primary-foreground: #fffaff; | ||
| 1145 | --color-primary-shadow: #000000; | ||
| 1146 | --color-secondary: #edeaf4; | ||
| 1147 | --color-secondary-foreground: #241523; | ||
| 1148 | --color-secondary-border: #d6d1de; | ||
| 1149 | --color-switch-active-track: #7253b9; | ||
| 1150 | --color-switch-active-thumb: #fffaff; | ||
| 1151 | --color-switch-inactive-track: #edeaf4; | ||
| 1152 | --color-switch-inactive-thumb: #726874; | ||
| 1153 | --color-danger: #f8e6ea; | ||
| 1154 | --color-danger-border: rgba(251, 44, 54, 0.32); | ||
| 1155 | --color-danger-foreground: #c10007; | ||
| 1156 | --color-input: #f0eff2; | ||
| 1157 | --color-input-border: #ccc5d6; | ||
| 1158 | --color-sidebar-search: #ddd9e3; | ||
| 1159 | --color-placeholder: #736973; | ||
| 1160 | --color-icon: #241523; | ||
| 1161 | --color-icon-muted: #766c76; | ||
| 1162 | --color-icon-subtle: #766c76; | ||
| 1163 | --color-header: rgba(248, 247, 249, 0.97); | ||
| 1164 | --color-header-border: #e5e0f0; | ||
| 1165 | --color-glass-surface: rgba(235, 233, 237, 0.74); | ||
| 1166 | --color-glass-tint: rgba(235, 233, 237, 0.22); | ||
| 1167 | --color-status-bar: #f8f7f9; | ||
| 1168 | --color-md-body: #241523; | ||
| 1169 | --color-md-strong: #241523; | ||
| 1170 | --color-md-link: #7253b9; | ||
| 1171 | --color-md-blockquote-border: #d6d1de; | ||
| 1172 | --color-md-blockquote-bg: #f0edf6; | ||
| 1173 | --color-md-code-bg: #f2f1f4; | ||
| 1174 | --color-md-code-text: #241523; | ||
| 1175 | --color-md-user-code-bg: rgba(36, 21, 35, 0.18); | ||
| 1176 | --color-md-user-code-text: #241523; | ||
| 1177 | --color-md-user-fence-bg: rgba(0, 0, 0, 0.16); | ||
| 1178 | --color-md-user-fence-text: #241523; | ||
| 1179 | --color-md-hr: #d6d1de; | ||
| 1180 | --color-user-bubble: #e0d9ee; | ||
| 1181 | --color-user-bubble-foreground: #241523; | ||
| 1182 | --color-user-bubble-foreground-muted: rgba(36, 21, 35, 0.78); | ||
| 1183 | --color-user-bubble-skill-foreground: #a82c87; | ||
| 1184 | --color-backdrop: rgba(0, 0, 0, 0.22); | ||
| 1185 | --color-drawer: rgba(237, 234, 244, 0.99); | ||
| 1186 | --color-drawer-shadow: rgba(0, 0, 0, 0.12); | ||
| 1187 | --color-dot-separator: rgba(118, 108, 118, 0.35); | ||
| 1188 | --color-wordmark: #241523; | ||
| 1189 | --color-chevron: rgba(118, 108, 118, 0.42); | ||
| 1190 | --color-adaptive-amber-50-950-a40: oklch(98.7% 0.022 95.277); | ||
| 1191 | --color-adaptive-amber-200-900-a60: oklch(92.4% 0.12 95.746); | ||
| 1192 | --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 12%); | ||
| 1193 | --color-adaptive-amber-700-300: oklch(55.5% 0.163 48.998); | ||
| 1194 | --color-adaptive-amber-700-400: oklch(55.5% 0.163 48.998); | ||
| 1195 | --color-adaptive-amber-800-200: oklch(47.3% 0.137 46.201); | ||
| 1196 | --color-adaptive-blue-50-blue-400-a14: oklch(97% 0.014 254.604); | ||
| 1197 | --color-adaptive-blue-300-a50-blue-400-a28: oklch(80.9% 0.105 251.813 / 50%); | ||
| 1198 | --color-adaptive-blue-500-a20-blue-400-a15: oklch(62.3% 0.214 259.815 / 20%); | ||
| 1199 | --color-adaptive-blue-500-400: oklch(62.3% 0.214 259.815); | ||
| 1200 | --color-adaptive-blue-600-400: oklch(54.6% 0.245 262.881); | ||
| 1201 | --color-adaptive-black-a10-a25: rgb(0 0 0 / 10%); | ||
| 1202 | --color-adaptive-black-a15-a35: rgb(0 0 0 / 15%); | ||
| 1203 | --color-adaptive-emerald-500-a12-a16: oklch(69.6% 0.17 162.48 / 12%); | ||
| 1204 | --color-adaptive-emerald-600-400: oklch(59.6% 0.145 163.225); | ||
| 1205 | --color-adaptive-emerald-700-300: oklch(50.8% 0.118 165.612); | ||
| 1206 | --color-adaptive-indigo-500-a12-a16: oklch(58.5% 0.233 277.117 / 12%); | ||
| 1207 | --color-adaptive-indigo-600-300: oklch(51.1% 0.262 276.966); | ||
| 1208 | --color-adaptive-indigo-700-300: oklch(45.7% 0.24 277.023); | ||
| 1209 | --color-adaptive-neutral-100-900: oklch(97% 0 0); | ||
| 1210 | --color-adaptive-neutral-200-700-a60: oklch(92.2% 0 0); | ||
| 1211 | --color-adaptive-neutral-200-800: oklch(92.2% 0 0); | ||
| 1212 | --color-adaptive-neutral-200-a70-white-a8: oklch(92.2% 0 0 / 70%); | ||
| 1213 | --color-adaptive-neutral-200-white-a6: oklch(92.2% 0 0); | ||
| 1214 | --color-adaptive-neutral-200-white-a8: oklch(92.2% 0 0); | ||
| 1215 | --color-adaptive-neutral-200-a80-white-a8: oklch(92.2% 0 0 / 80%); | ||
| 1216 | --color-adaptive-neutral-300-a60-white-a12: oklch(87% 0 0 / 60%); | ||
| 1217 | --color-adaptive-neutral-400-500: oklch(70.8% 0 0); | ||
| 1218 | --color-adaptive-neutral-400-a60-500-a60: oklch(70.8% 0 0 / 60%); | ||
| 1219 | --color-adaptive-neutral-400-a80-500-a80: oklch(70.8% 0 0 / 80%); | ||
| 1220 | --color-adaptive-neutral-500-a10-a16: oklch(55.6% 0 0 / 10%); | ||
| 1221 | --color-adaptive-neutral-500-400: oklch(55.6% 0 0); | ||
| 1222 | --color-adaptive-neutral-500-500: oklch(55.6% 0 0); | ||
| 1223 | --color-adaptive-neutral-600-300: oklch(43.9% 0 0); | ||
| 1224 | --color-adaptive-neutral-600-400: oklch(43.9% 0 0); | ||
| 1225 | --color-adaptive-neutral-950-50: oklch(14.5% 0 0); | ||
| 1226 | --color-adaptive-red-50-950-a80: oklch(97.1% 0.013 17.38); | ||
| 1227 | --color-adaptive-red-200-800: oklch(88.5% 0.062 18.334); | ||
| 1228 | --color-adaptive-red-600-a80-400-a80: oklch(57.7% 0.245 27.325 / 80%); | ||
| 1229 | --color-adaptive-red-700-300: oklch(50.5% 0.213 27.518); | ||
| 1230 | --color-adaptive-rose-100-500-a18: oklch(94.1% 0.03 12.58); | ||
| 1231 | --color-adaptive-rose-100-a80-500-a12: oklch(94.1% 0.03 12.58 / 80%); | ||
| 1232 | --color-adaptive-rose-300-a70-400-a28: oklch(81% 0.117 11.638 / 70%); | ||
| 1233 | --color-adaptive-rose-500-a12-a16: oklch(64.5% 0.246 16.439 / 12%); | ||
| 1234 | --color-adaptive-rose-500-400: oklch(64.5% 0.246 16.439); | ||
| 1235 | --color-adaptive-rose-600-400: oklch(58.6% 0.253 17.585); | ||
| 1236 | --color-adaptive-rose-700-300: oklch(51.4% 0.222 16.935); | ||
| 1237 | --color-adaptive-sky-500-a12-a16: oklch(68.5% 0.169 237.323 / 12%); | ||
| 1238 | --color-adaptive-sky-600-400: oklch(58.8% 0.158 241.966); | ||
| 1239 | --color-adaptive-sky-700-300: oklch(50% 0.134 242.749); | ||
| 1240 | --color-adaptive-violet-500-a12-a16: oklch(60.6% 0.25 292.717 / 12%); | ||
| 1241 | --color-adaptive-violet-600-400: oklch(54.1% 0.281 293.009); | ||
| 1242 | --color-adaptive-violet-700-300: oklch(49.1% 0.27 292.581); | ||
| 1243 | --color-adaptive-white-neutral-950-a70: #fff; | ||
| 1244 | --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%); | ||
| 1245 | --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938); | ||
| 1246 | --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786); | ||
| 1247 | } | ||
| 1248 | |||
| 1249 | @variant iris-dark { | ||
| 1250 | --color-screen: #1d1929; | ||
| 1251 | --color-sheet: rgba(29, 25, 41, 0.98); | ||
| 1252 | --color-sheet-solid: #1d1929; | ||
| 1253 | --color-card: #383443; | ||
| 1254 | --color-card-alt: #1d1929; | ||
| 1255 | --color-card-translucent: rgba(56, 52, 67, 0.8); | ||
| 1256 | --color-foreground: #fffaff; | ||
| 1257 | --color-foreground-secondary: #8e8a95; | ||
| 1258 | --color-foreground-muted: #9690a1; | ||
| 1259 | --color-foreground-tertiary: #8e8a95; | ||
| 1260 | --color-border: #4d4366; | ||
| 1261 | --color-border-subtle: rgba(77, 67, 102, 0.7); | ||
| 1262 | --color-separator: rgba(77, 67, 102, 0.55); | ||
| 1263 | --color-subtle: #2d2643; | ||
| 1264 | --color-subtle-strong: #362d51; | ||
| 1265 | --color-inline-skill-background: #433765; | ||
| 1266 | --color-inline-skill-border: rgba(157, 125, 242, 0.42); | ||
| 1267 | --color-inline-skill-foreground: #fffaff; | ||
| 1268 | --color-primary: #9d7df2; | ||
| 1269 | --color-primary-foreground: #241523; | ||
| 1270 | --color-primary-shadow: #000000; | ||
| 1271 | --color-secondary: #362d51; | ||
| 1272 | --color-secondary-foreground: #fffaff; | ||
| 1273 | --color-secondary-border: #4d4366; | ||
| 1274 | --color-switch-active-track: #9d7df2; | ||
| 1275 | --color-switch-active-thumb: #241523; | ||
| 1276 | --color-switch-inactive-track: #362d51; | ||
| 1277 | --color-switch-inactive-thumb: #9690a1; | ||
| 1278 | --color-danger: #40202e; | ||
| 1279 | --color-danger-border: rgba(251, 65, 74, 0.32); | ||
| 1280 | --color-danger-foreground: #ff6467; | ||
| 1281 | --color-input: #383443; | ||
| 1282 | --color-input-border: #5d527b; | ||
| 1283 | --color-sidebar-search: #494459; | ||
| 1284 | --color-placeholder: #a29ea8; | ||
| 1285 | --color-icon: #fffaff; | ||
| 1286 | --color-icon-muted: #8e8a95; | ||
| 1287 | --color-icon-subtle: #8e8a95; | ||
| 1288 | --color-header: rgba(29, 25, 41, 0.97); | ||
| 1289 | --color-header-border: #4a3c70; | ||
| 1290 | --color-glass-surface: rgba(69, 66, 80, 0.74); | ||
| 1291 | --color-glass-tint: rgba(69, 66, 80, 0.22); | ||
| 1292 | --color-status-bar: #1d1929; | ||
| 1293 | --color-md-body: #fffaff; | ||
| 1294 | --color-md-strong: #fffaff; | ||
| 1295 | --color-md-link: #9d7df2; | ||
| 1296 | --color-md-blockquote-border: #4d4366; | ||
| 1297 | --color-md-blockquote-bg: #2d2643; | ||
| 1298 | --color-md-code-bg: #2a2736; | ||
| 1299 | --color-md-code-text: #fffaff; | ||
| 1300 | --color-md-user-code-bg: rgba(255, 250, 255, 0.18); | ||
| 1301 | --color-md-user-code-text: #fffaff; | ||
| 1302 | --color-md-user-fence-bg: rgba(0, 0, 0, 0.28); | ||
| 1303 | --color-md-user-fence-text: #fffaff; | ||
| 1304 | --color-md-hr: #4d4366; | ||
| 1305 | --color-user-bubble: #4b3d72; | ||
| 1306 | --color-user-bubble-foreground: #fffaff; | ||
| 1307 | --color-user-bubble-foreground-muted: rgba(255, 250, 255, 0.78); | ||
| 1308 | --color-user-bubble-skill-foreground: #f099d8; | ||
| 1309 | --color-backdrop: rgba(0, 0, 0, 0.48); | ||
| 1310 | --color-drawer: rgba(39, 33, 57, 0.99); | ||
| 1311 | --color-drawer-shadow: rgba(0, 0, 0, 0.32); | ||
| 1312 | --color-dot-separator: rgba(142, 138, 149, 0.35); | ||
| 1313 | --color-wordmark: #fffaff; | ||
| 1314 | --color-chevron: rgba(142, 138, 149, 0.42); | ||
| 1315 | --color-adaptive-amber-50-950-a40: oklch(27.9% 0.077 45.635 / 40%); | ||
| 1316 | --color-adaptive-amber-200-900-a60: oklch(41.4% 0.112 45.904 / 60%); | ||
| 1317 | --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 16%); | ||
| 1318 | --color-adaptive-amber-700-300: oklch(87.9% 0.169 91.605); | ||
| 1319 | --color-adaptive-amber-700-400: oklch(82.8% 0.189 84.429); | ||
| 1320 | --color-adaptive-amber-800-200: oklch(92.4% 0.12 95.746); | ||
| 1321 | --color-adaptive-blue-50-blue-400-a14: oklch(70.7% 0.165 254.624 / 14%); | ||
| 1322 | --color-adaptive-blue-300-a50-blue-400-a28: oklch(70.7% 0.165 254.624 / 28%); | ||
| 1323 | --color-adaptive-blue-500-a20-blue-400-a15: oklch(70.7% 0.165 254.624 / 15%); | ||
| 1324 | --color-adaptive-blue-500-400: oklch(70.7% 0.165 254.624); | ||
| 1325 | --color-adaptive-blue-600-400: oklch(70.7% 0.165 254.624); | ||
| 1326 | --color-adaptive-black-a10-a25: rgb(0 0 0 / 25%); | ||
| 1327 | --color-adaptive-black-a15-a35: rgb(0 0 0 / 35%); | ||
| 1328 | --color-adaptive-emerald-500-a12-a16: oklch(69.6% 0.17 162.48 / 16%); | ||
| 1329 | --color-adaptive-emerald-600-400: oklch(76.5% 0.177 163.223); | ||
| 1330 | --color-adaptive-emerald-700-300: oklch(84.5% 0.143 164.978); | ||
| 1331 | --color-adaptive-indigo-500-a12-a16: oklch(58.5% 0.233 277.117 / 16%); | ||
| 1332 | --color-adaptive-indigo-600-300: oklch(78.5% 0.115 274.713); | ||
| 1333 | --color-adaptive-indigo-700-300: oklch(78.5% 0.115 274.713); | ||
| 1334 | --color-adaptive-neutral-100-900: oklch(20.5% 0 0); | ||
| 1335 | --color-adaptive-neutral-200-700-a60: oklch(37.1% 0 0 / 60%); | ||
| 1336 | --color-adaptive-neutral-200-800: oklch(26.9% 0 0); | ||
| 1337 | --color-adaptive-neutral-200-a70-white-a8: rgb(255 255 255 / 8%); | ||
| 1338 | --color-adaptive-neutral-200-white-a6: rgb(255 255 255 / 6%); | ||
| 1339 | --color-adaptive-neutral-200-white-a8: rgb(255 255 255 / 8%); | ||
| 1340 | --color-adaptive-neutral-200-a80-white-a8: rgb(255 255 255 / 8%); | ||
| 1341 | --color-adaptive-neutral-300-a60-white-a12: rgb(255 255 255 / 12%); | ||
| 1342 | --color-adaptive-neutral-400-500: oklch(55.6% 0 0); | ||
| 1343 | --color-adaptive-neutral-400-a60-500-a60: oklch(55.6% 0 0 / 60%); | ||
| 1344 | --color-adaptive-neutral-400-a80-500-a80: oklch(55.6% 0 0 / 80%); | ||
| 1345 | --color-adaptive-neutral-500-a10-a16: oklch(55.6% 0 0 / 16%); | ||
| 1346 | --color-adaptive-neutral-500-400: oklch(70.8% 0 0); | ||
| 1347 | --color-adaptive-neutral-500-500: oklch(55.6% 0 0); | ||
| 1348 | --color-adaptive-neutral-600-300: oklch(87% 0 0); | ||
| 1349 | --color-adaptive-neutral-600-400: oklch(70.8% 0 0); | ||
| 1350 | --color-adaptive-neutral-950-50: oklch(98.5% 0 0); | ||
| 1351 | --color-adaptive-red-50-950-a80: oklch(25.8% 0.092 26.042 / 80%); | ||
| 1352 | --color-adaptive-red-200-800: oklch(44.4% 0.177 26.899); | ||
| 1353 | --color-adaptive-red-600-a80-400-a80: oklch(70.4% 0.191 22.216 / 80%); | ||
| 1354 | --color-adaptive-red-700-300: oklch(80.8% 0.114 19.571); | ||
| 1355 | --color-adaptive-rose-100-500-a18: oklch(64.5% 0.246 16.439 / 18%); | ||
| 1356 | --color-adaptive-rose-100-a80-500-a12: oklch(64.5% 0.246 16.439 / 12%); | ||
| 1357 | --color-adaptive-rose-300-a70-400-a28: oklch(71.2% 0.194 13.428 / 28%); | ||
| 1358 | --color-adaptive-rose-500-a12-a16: oklch(64.5% 0.246 16.439 / 16%); | ||
| 1359 | --color-adaptive-rose-500-400: oklch(71.2% 0.194 13.428); | ||
| 1360 | --color-adaptive-rose-600-400: oklch(71.2% 0.194 13.428); | ||
| 1361 | --color-adaptive-rose-700-300: oklch(81% 0.117 11.638); | ||
| 1362 | --color-adaptive-sky-500-a12-a16: oklch(68.5% 0.169 237.323 / 16%); | ||
| 1363 | --color-adaptive-sky-600-400: oklch(74.6% 0.16 232.661); | ||
| 1364 | --color-adaptive-sky-700-300: oklch(82.8% 0.111 230.318); | ||
| 1365 | --color-adaptive-violet-500-a12-a16: oklch(60.6% 0.25 292.717 / 16%); | ||
| 1366 | --color-adaptive-violet-600-400: oklch(70.2% 0.183 293.541); | ||
| 1367 | --color-adaptive-violet-700-300: oklch(81.1% 0.111 293.571); | ||
| 1368 | --color-adaptive-white-neutral-950-a70: oklch(14.5% 0 0 / 70%); | ||
| 1369 | --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%); | ||
| 1370 | --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067); | ||
| 1371 | --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286); | ||
| 1372 | } | ||
| 1373 | } | ||
| 1374 | } | ||
apps/mobile/global.css+1| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | @import "tailwindcss"; | 1 | @import "tailwindcss"; |
| 2 | @import "uniwind"; | 2 | @import "uniwind"; |
| 3 | @import "./generated-uniwind-themes.css"; | ||
| 3 | 4 | ||
| 4 | /* ─── Theme tokens ──────────────────────────────────────────────────── */ | 5 | /* ─── Theme tokens ──────────────────────────────────────────────────── */ |
| 5 | @layer theme { | 6 | @layer theme { |
apps/mobile/metro.config.js+2| ... | @@ -2,6 +2,7 @@ const fs = require("node:fs"); | ... | @@ -2,6 +2,7 @@ const fs = require("node:fs"); |
| 2 | const path = require("node:path"); | 2 | const path = require("node:path"); |
| 3 | const { getDefaultConfig } = require("expo/metro-config"); | 3 | const { getDefaultConfig } = require("expo/metro-config"); |
| 4 | const { withUniwindConfig } = require("uniwind/metro"); | 4 | const { withUniwindConfig } = require("uniwind/metro"); |
| 5 | const extraThemes = require("./generated-uniwind-theme-names.json"); | ||
| 5 | 6 | ||
| 6 | /** @type {import("expo/metro-config").MetroConfig} */ | 7 | /** @type {import("expo/metro-config").MetroConfig} */ |
| 7 | const config = getDefaultConfig(__dirname); | 8 | const config = getDefaultConfig(__dirname); |
| ... | @@ -50,5 +51,6 @@ config.resolver = { | ... | @@ -50,5 +51,6 @@ config.resolver = { |
| 50 | 51 | ||
| 51 | module.exports = withUniwindConfig(config, { | 52 | module.exports = withUniwindConfig(config, { |
| 52 | cssEntryFile: "./global.css", | 53 | cssEntryFile: "./global.css", |
| 54 | extraThemes, | ||
| 53 | polyfills: { rem: 14 }, | 55 | polyfills: { rem: 14 }, |
| 54 | }); | 56 | }); |
apps/mobile/package.json+1| ... | @@ -39,6 +39,7 @@ | ... | @@ -39,6 +39,7 @@ |
| 39 | "config:prod": "APP_VARIANT=production expo config", | 39 | "config:prod": "APP_VARIANT=production expo config", |
| 40 | "profile:android:hermes": "mkdir -p profiles/review && react-native profile-hermes profiles/review", | 40 | "profile:android:hermes": "mkdir -p profiles/review && react-native profile-hermes profiles/review", |
| 41 | "sync:pierre-icons": "node modules/t3-markdown-text/scripts/sync-pierre-file-icons.mjs", | 41 | "sync:pierre-icons": "node modules/t3-markdown-text/scripts/sync-pierre-file-icons.mjs", |
| 42 | "generate": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON scripts/generate-uniwind-themes.mts", | ||
| 42 | "test": "vp test run", | 43 | "test": "vp test run", |
| 43 | "typecheck": "tsc --noEmit" | 44 | "typecheck": "tsc --noEmit" |
| 44 | }, | 45 | }, |
apps/mobile/scripts/generate-uniwind-themes.mts created+262| ... | @@ -0,0 +1,262 @@ | ||
| 1 | #!/usr/bin/env node | ||
| 2 | |||
| 3 | import * as NodeFS from "node:fs"; | ||
| 4 | import * as NodePath from "node:path"; | ||
| 5 | import tailwindColors from "tailwindcss/colors"; | ||
| 6 | import { BUILT_IN_THEME_IDS, type BuiltInThemeId } from "@t3tools/shared/themePalettes"; | ||
| 7 | |||
| 8 | import { | ||
| 9 | getMobileThemeVariables, | ||
| 10 | MOBILE_THEME_VARIABLE_NAMES, | ||
| 11 | type MobileThemeAppearance, | ||
| 12 | type MobileThemeVariables, | ||
| 13 | } from "../src/lib/mobileTheme.ts"; | ||
| 14 | |||
| 15 | const APPEARANCES = ["light", "dark"] as const; | ||
| 16 | const GLOBAL_CSS_PATH = NodePath.resolve(import.meta.dirname, "../global.css"); | ||
| 17 | const GENERATED_CSS_PATH = NodePath.resolve(import.meta.dirname, "../generated-uniwind-themes.css"); | ||
| 18 | const GENERATED_NAMES_PATH = NodePath.resolve( | ||
| 19 | import.meta.dirname, | ||
| 20 | "../generated-uniwind-theme-names.json", | ||
| 21 | ); | ||
| 22 | const GENERATED_DEFAULT_VARIABLES_PATH = NodePath.resolve( | ||
| 23 | import.meta.dirname, | ||
| 24 | "../generated-uniwind-default-theme-variables.json", | ||
| 25 | ); | ||
| 26 | |||
| 27 | type TailwindColorFamily = keyof typeof tailwindColors; | ||
| 28 | type TailwindColorShade = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950; | ||
| 29 | |||
| 30 | const color = (family: TailwindColorFamily, shade?: TailwindColorShade, opacity = 1): string => { | ||
| 31 | const familyColors = tailwindColors[family]; | ||
| 32 | const value = | ||
| 33 | typeof familyColors === "string" | ||
| 34 | ? shade === undefined | ||
| 35 | ? familyColors | ||
| 36 | : undefined | ||
| 37 | : shade === undefined | ||
| 38 | ? undefined | ||
| 39 | : familyColors[String(shade) as keyof typeof familyColors]; | ||
| 40 | if (value === undefined) { | ||
| 41 | throw new Error(`Unknown Tailwind color ${family}${shade === undefined ? "" : `-${shade}`}.`); | ||
| 42 | } | ||
| 43 | if (opacity === 1) return value; | ||
| 44 | |||
| 45 | const percentage = Number((opacity * 100).toFixed(4)); | ||
| 46 | const oklch = /^oklch\((.*)\)$/.exec(value); | ||
| 47 | if (oklch) return `oklch(${oklch[1]} / ${percentage}%)`; | ||
| 48 | if (value === "#fff") return `rgb(255 255 255 / ${percentage}%)`; | ||
| 49 | if (value === "#000") return `rgb(0 0 0 / ${percentage}%)`; | ||
| 50 | return `color-mix(in srgb, ${value} ${percentage}%, transparent)`; | ||
| 51 | }; | ||
| 52 | |||
| 53 | // These replace the remaining dark:* utility pairs. A registered palette theme is | ||
| 54 | // neither literally `light` nor `dark`, so appearance-sensitive values must also be | ||
| 55 | // represented as semantic variables for custom themes. | ||
| 56 | const ADAPTIVE_COLORS = { | ||
| 57 | "--color-adaptive-amber-50-950-a40": [color("amber", 50), color("amber", 950, 0.4)], | ||
| 58 | "--color-adaptive-amber-200-900-a60": [color("amber", 200), color("amber", 900, 0.6)], | ||
| 59 | "--color-adaptive-amber-500-a12-a16": [color("amber", 500, 0.12), color("amber", 500, 0.16)], | ||
| 60 | "--color-adaptive-amber-700-300": [color("amber", 700), color("amber", 300)], | ||
| 61 | "--color-adaptive-amber-700-400": [color("amber", 700), color("amber", 400)], | ||
| 62 | "--color-adaptive-amber-800-200": [color("amber", 800), color("amber", 200)], | ||
| 63 | "--color-adaptive-blue-50-blue-400-a14": [color("blue", 50), color("blue", 400, 0.14)], | ||
| 64 | "--color-adaptive-blue-300-a50-blue-400-a28": [color("blue", 300, 0.5), color("blue", 400, 0.28)], | ||
| 65 | "--color-adaptive-blue-500-a20-blue-400-a15": [color("blue", 500, 0.2), color("blue", 400, 0.15)], | ||
| 66 | "--color-adaptive-blue-500-400": [color("blue", 500), color("blue", 400)], | ||
| 67 | "--color-adaptive-blue-600-400": [color("blue", 600), color("blue", 400)], | ||
| 68 | "--color-adaptive-black-a10-a25": [ | ||
| 69 | color("black", undefined, 0.1), | ||
| 70 | color("black", undefined, 0.25), | ||
| 71 | ], | ||
| 72 | "--color-adaptive-black-a15-a35": [ | ||
| 73 | color("black", undefined, 0.15), | ||
| 74 | color("black", undefined, 0.35), | ||
| 75 | ], | ||
| 76 | "--color-adaptive-emerald-500-a12-a16": [ | ||
| 77 | color("emerald", 500, 0.12), | ||
| 78 | color("emerald", 500, 0.16), | ||
| 79 | ], | ||
| 80 | "--color-adaptive-emerald-600-400": [color("emerald", 600), color("emerald", 400)], | ||
| 81 | "--color-adaptive-emerald-700-300": [color("emerald", 700), color("emerald", 300)], | ||
| 82 | "--color-adaptive-indigo-500-a12-a16": [color("indigo", 500, 0.12), color("indigo", 500, 0.16)], | ||
| 83 | "--color-adaptive-indigo-600-300": [color("indigo", 600), color("indigo", 300)], | ||
| 84 | "--color-adaptive-indigo-700-300": [color("indigo", 700), color("indigo", 300)], | ||
| 85 | "--color-adaptive-neutral-100-900": [color("neutral", 100), color("neutral", 900)], | ||
| 86 | "--color-adaptive-neutral-200-700-a60": [color("neutral", 200), color("neutral", 700, 0.6)], | ||
| 87 | "--color-adaptive-neutral-200-800": [color("neutral", 200), color("neutral", 800)], | ||
| 88 | "--color-adaptive-neutral-200-a70-white-a8": [ | ||
| 89 | color("neutral", 200, 0.7), | ||
| 90 | color("white", undefined, 0.08), | ||
| 91 | ], | ||
| 92 | "--color-adaptive-neutral-200-white-a6": [color("neutral", 200), color("white", undefined, 0.06)], | ||
| 93 | "--color-adaptive-neutral-200-white-a8": [color("neutral", 200), color("white", undefined, 0.08)], | ||
| 94 | "--color-adaptive-neutral-200-a80-white-a8": [ | ||
| 95 | color("neutral", 200, 0.8), | ||
| 96 | color("white", undefined, 0.08), | ||
| 97 | ], | ||
| 98 | "--color-adaptive-neutral-300-a60-white-a12": [ | ||
| 99 | color("neutral", 300, 0.6), | ||
| 100 | color("white", undefined, 0.12), | ||
| 101 | ], | ||
| 102 | "--color-adaptive-neutral-400-500": [color("neutral", 400), color("neutral", 500)], | ||
| 103 | "--color-adaptive-neutral-400-a60-500-a60": [ | ||
| 104 | color("neutral", 400, 0.6), | ||
| 105 | color("neutral", 500, 0.6), | ||
| 106 | ], | ||
| 107 | "--color-adaptive-neutral-400-a80-500-a80": [ | ||
| 108 | color("neutral", 400, 0.8), | ||
| 109 | color("neutral", 500, 0.8), | ||
| 110 | ], | ||
| 111 | "--color-adaptive-neutral-500-a10-a16": [color("neutral", 500, 0.1), color("neutral", 500, 0.16)], | ||
| 112 | "--color-adaptive-neutral-500-400": [color("neutral", 500), color("neutral", 400)], | ||
| 113 | "--color-adaptive-neutral-500-500": [color("neutral", 500), color("neutral", 500)], | ||
| 114 | "--color-adaptive-neutral-600-300": [color("neutral", 600), color("neutral", 300)], | ||
| 115 | "--color-adaptive-neutral-600-400": [color("neutral", 600), color("neutral", 400)], | ||
| 116 | "--color-adaptive-neutral-950-50": [color("neutral", 950), color("neutral", 50)], | ||
| 117 | "--color-adaptive-red-50-950-a80": [color("red", 50), color("red", 950, 0.8)], | ||
| 118 | "--color-adaptive-red-200-800": [color("red", 200), color("red", 800)], | ||
| 119 | "--color-adaptive-red-600-a80-400-a80": [color("red", 600, 0.8), color("red", 400, 0.8)], | ||
| 120 | "--color-adaptive-red-700-300": [color("red", 700), color("red", 300)], | ||
| 121 | "--color-adaptive-rose-100-500-a18": [color("rose", 100), color("rose", 500, 0.18)], | ||
| 122 | "--color-adaptive-rose-100-a80-500-a12": [color("rose", 100, 0.8), color("rose", 500, 0.12)], | ||
| 123 | "--color-adaptive-rose-300-a70-400-a28": [color("rose", 300, 0.7), color("rose", 400, 0.28)], | ||
| 124 | "--color-adaptive-rose-500-a12-a16": [color("rose", 500, 0.12), color("rose", 500, 0.16)], | ||
| 125 | "--color-adaptive-rose-500-400": [color("rose", 500), color("rose", 400)], | ||
| 126 | "--color-adaptive-rose-600-400": [color("rose", 600), color("rose", 400)], | ||
| 127 | "--color-adaptive-rose-700-300": [color("rose", 700), color("rose", 300)], | ||
| 128 | "--color-adaptive-sky-500-a12-a16": [color("sky", 500, 0.12), color("sky", 500, 0.16)], | ||
| 129 | "--color-adaptive-sky-600-400": [color("sky", 600), color("sky", 400)], | ||
| 130 | "--color-adaptive-sky-700-300": [color("sky", 700), color("sky", 300)], | ||
| 131 | "--color-adaptive-violet-500-a12-a16": [color("violet", 500, 0.12), color("violet", 500, 0.16)], | ||
| 132 | "--color-adaptive-violet-600-400": [color("violet", 600), color("violet", 400)], | ||
| 133 | "--color-adaptive-violet-700-300": [color("violet", 700), color("violet", 300)], | ||
| 134 | "--color-adaptive-white-neutral-950-a70": [color("white"), color("neutral", 950, 0.7)], | ||
| 135 | "--color-adaptive-zinc-500-a12-a16": [color("zinc", 500, 0.12), color("zinc", 500, 0.16)], | ||
| 136 | "--color-adaptive-zinc-500-400": [color("zinc", 500), color("zinc", 400)], | ||
| 137 | "--color-adaptive-zinc-600-300": [color("zinc", 600), color("zinc", 300)], | ||
| 138 | }; | ||
| 139 | |||
| 140 | export const customThemeNames = BUILT_IN_THEME_IDS.flatMap((themeId) => | ||
| 141 | APPEARANCES.map((appearance) => `${themeId}-${appearance}`), | ||
| 142 | ); | ||
| 143 | |||
| 144 | const adaptiveVariablesFor = (appearance: MobileThemeAppearance) => | ||
| 145 | Object.fromEntries( | ||
| 146 | Object.entries(ADAPTIVE_COLORS).map(([name, values]) => [ | ||
| 147 | name, | ||
| 148 | values[appearance === "light" ? 0 : 1], | ||
| 149 | ]), | ||
| 150 | ); | ||
| 151 | |||
| 152 | const variablesFor = (themeId: BuiltInThemeId, appearance: MobileThemeAppearance) => ({ | ||
| 153 | ...getMobileThemeVariables(themeId, appearance), | ||
| 154 | ...adaptiveVariablesFor(appearance), | ||
| 155 | }); | ||
| 156 | |||
| 157 | const renderVariant = (name: string, variables: Readonly<Record<string, string>>) => { | ||
| 158 | const declarations = Object.entries(variables) | ||
| 159 | .map(([variable, value]) => ` ${variable}: ${value};`) | ||
| 160 | .join("\n"); | ||
| 161 | return ` @variant ${name} {\n${declarations}\n }`; | ||
| 162 | }; | ||
| 163 | |||
| 164 | export const renderUniwindThemesCSS = () => { | ||
| 165 | const variants = [ | ||
| 166 | renderVariant("light", adaptiveVariablesFor("light")), | ||
| 167 | renderVariant("dark", adaptiveVariablesFor("dark")), | ||
| 168 | ...BUILT_IN_THEME_IDS.flatMap((themeId) => | ||
| 169 | APPEARANCES.map((appearance) => | ||
| 170 | renderVariant(`${themeId}-${appearance}`, variablesFor(themeId, appearance)), | ||
| 171 | ), | ||
| 172 | ), | ||
| 173 | ]; | ||
| 174 | return [ | ||
| 175 | "/* Generated by scripts/generate-uniwind-themes.mts. Do not edit manually. */", | ||
| 176 | "@layer theme {", | ||
| 177 | " :root {", | ||
| 178 | variants.join("\n\n"), | ||
| 179 | " }", | ||
| 180 | "}", | ||
| 181 | "", | ||
| 182 | ].join("\n"); | ||
| 183 | }; | ||
| 184 | |||
| 185 | const readVariantBody = (css: string, appearance: MobileThemeAppearance): string => { | ||
| 186 | const marker = `@variant ${appearance} {`; | ||
| 187 | const markerIndex = css.indexOf(marker); | ||
| 188 | if (markerIndex === -1) throw new Error(`Could not find ${marker} in global.css.`); | ||
| 189 | |||
| 190 | const openingBraceIndex = css.indexOf("{", markerIndex); | ||
| 191 | let depth = 0; | ||
| 192 | for (let index = openingBraceIndex; index < css.length; index += 1) { | ||
| 193 | if (css[index] === "{") depth += 1; | ||
| 194 | if (css[index] !== "}") continue; | ||
| 195 | depth -= 1; | ||
| 196 | if (depth === 0) return css.slice(openingBraceIndex + 1, index); | ||
| 197 | } | ||
| 198 | throw new Error(`Could not find the end of ${marker} in global.css.`); | ||
| 199 | }; | ||
| 200 | |||
| 201 | export const readDefaultThemeVariables = (css: string) => | ||
| 202 | Object.fromEntries( | ||
| 203 | APPEARANCES.map((appearance) => { | ||
| 204 | const body = readVariantBody(css, appearance); | ||
| 205 | const variables = Object.fromEntries( | ||
| 206 | MOBILE_THEME_VARIABLE_NAMES.map((name) => { | ||
| 207 | const match = new RegExp(`^\\s*${name}:\\s*([^;]+);`, "mu").exec(body); | ||
| 208 | if (!match?.[1]) { | ||
| 209 | throw new Error(`Default ${appearance} theme is missing ${name}.`); | ||
| 210 | } | ||
| 211 | return [name, match[1].trim()]; | ||
| 212 | }), | ||
| 213 | ) as MobileThemeVariables; | ||
| 214 | return [appearance, variables]; | ||
| 215 | }), | ||
| 216 | ) as Readonly<Record<MobileThemeAppearance, MobileThemeVariables>>; | ||
| 217 | |||
| 218 | export const renderDefaultThemeVariablesJSON = (css: string) => | ||
| 219 | `${JSON.stringify(readDefaultThemeVariables(css), null, 2)}\n`; | ||
| 220 | |||
| 221 | export const getGeneratedUniwindThemeOutputs = (): ReadonlyArray< | ||
| 222 | readonly [filename: string, contents: string] | ||
| 223 | > => [ | ||
| 224 | [GENERATED_CSS_PATH, renderUniwindThemesCSS()], | ||
| 225 | [GENERATED_NAMES_PATH, `${JSON.stringify(customThemeNames, null, 2)}\n`], | ||
| 226 | [ | ||
| 227 | GENERATED_DEFAULT_VARIABLES_PATH, | ||
| 228 | renderDefaultThemeVariablesJSON(NodeFS.readFileSync(GLOBAL_CSS_PATH, "utf8")), | ||
| 229 | ], | ||
| 230 | ]; | ||
| 231 | |||
| 232 | const writeFileAtomically = (filename: string, contents: string) => { | ||
| 233 | const current = NodeFS.existsSync(filename) ? NodeFS.readFileSync(filename, "utf8") : null; | ||
| 234 | if (current === contents) return; | ||
| 235 | |||
| 236 | const temporaryFilename = `${filename}.${process.pid}.tmp`; | ||
| 237 | try { | ||
| 238 | NodeFS.writeFileSync(temporaryFilename, contents); | ||
| 239 | NodeFS.renameSync(temporaryFilename, filename); | ||
| 240 | } finally { | ||
| 241 | if (NodeFS.existsSync(temporaryFilename)) NodeFS.unlinkSync(temporaryFilename); | ||
| 242 | } | ||
| 243 | }; | ||
| 244 | |||
| 245 | if (import.meta.main) { | ||
| 246 | const checkOnly = process.argv.includes("--check"); | ||
| 247 | for (const [filename, contents] of getGeneratedUniwindThemeOutputs()) { | ||
| 248 | if (checkOnly) { | ||
| 249 | const current = NodeFS.existsSync(filename) ? NodeFS.readFileSync(filename, "utf8") : null; | ||
| 250 | if (current !== contents) { | ||
| 251 | console.error( | ||
| 252 | `${NodePath.relative(process.cwd(), filename)} is stale. Run vp run --filter @t3tools/mobile generate.`, | ||
| 253 | ); | ||
| 254 | process.exitCode = 1; | ||
| 255 | } | ||
| 256 | continue; | ||
| 257 | } | ||
| 258 | // Metro watches the generated CSS. Replacing a complete temporary file keeps | ||
| 259 | // Tailwind from compiling a partially rewritten theme file. | ||
| 260 | writeFileAtomically(filename, contents); | ||
| 261 | } | ||
| 262 | } | ||
apps/mobile/scripts/generate-uniwind-themes.test.ts created+55| ... | @@ -0,0 +1,55 @@ | ||
| 1 | import * as NodeFS from "node:fs"; | ||
| 2 | import * as NodePath from "node:path"; | ||
| 3 | import { describe, expect, it } from "vite-plus/test"; | ||
| 4 | |||
| 5 | import { | ||
| 6 | customThemeNames, | ||
| 7 | getGeneratedUniwindThemeOutputs, | ||
| 8 | readDefaultThemeVariables, | ||
| 9 | renderUniwindThemesCSS, | ||
| 10 | } from "./generate-uniwind-themes.mts"; | ||
| 11 | |||
| 12 | describe("generate mobile Uniwind themes", () => { | ||
| 13 | it("keeps the committed outputs current", () => { | ||
| 14 | const staleOutputs = getGeneratedUniwindThemeOutputs() | ||
| 15 | .filter( | ||
| 16 | ([filename, contents]) => | ||
| 17 | !NodeFS.existsSync(filename) || NodeFS.readFileSync(filename, "utf8") !== contents, | ||
| 18 | ) | ||
| 19 | .map(([filename]) => NodePath.relative(import.meta.dirname, filename)); | ||
| 20 | |||
| 21 | expect( | ||
| 22 | staleOutputs, | ||
| 23 | "Run `vp run --filter @t3tools/mobile generate` and commit the generated outputs.", | ||
| 24 | ).toEqual([]); | ||
| 25 | }); | ||
| 26 | |||
| 27 | it("registers every custom palette for both appearances", () => { | ||
| 28 | expect(customThemeNames).toEqual([ | ||
| 29 | "t3-chat-light", | ||
| 30 | "t3-chat-dark", | ||
| 31 | "grove-light", | ||
| 32 | "grove-dark", | ||
| 33 | "ocean-light", | ||
| 34 | "ocean-dark", | ||
| 35 | "ember-light", | ||
| 36 | "ember-dark", | ||
| 37 | "iris-light", | ||
| 38 | "iris-dark", | ||
| 39 | ]); | ||
| 40 | |||
| 41 | const stylesheet = renderUniwindThemesCSS(); | ||
| 42 | for (const themeName of customThemeNames) { | ||
| 43 | expect(stylesheet.match(new RegExp(`@variant ${themeName} \\{`, "gu"))).toHaveLength(1); | ||
| 44 | } | ||
| 45 | }); | ||
| 46 | |||
| 47 | it("generates the default runtime bridge from the authored CSS", () => { | ||
| 48 | const css = NodeFS.readFileSync(NodePath.resolve(import.meta.dirname, "../global.css"), "utf8"); | ||
| 49 | const variables = readDefaultThemeVariables(css); | ||
| 50 | |||
| 51 | expect(variables.light["--color-screen"]).toBe("#f2f2f7"); | ||
| 52 | expect(variables.dark["--color-screen"]).toBe("#0a0a0a"); | ||
| 53 | expect(Object.keys(variables.light)).toEqual(Object.keys(variables.dark)); | ||
| 54 | }); | ||
| 55 | }); | ||
apps/mobile/src/App.tsx+1-4| ... | @@ -21,7 +21,6 @@ import { RootStack } from "./Stack"; | ... | @@ -21,7 +21,6 @@ import { RootStack } from "./Stack"; |
| 21 | import { appAtomRegistry } from "./state/atom-registry"; | 21 | import { appAtomRegistry } from "./state/atom-registry"; |
| 22 | import { OverlayPortalHost } from "./components/OverlayPortal"; | 22 | import { OverlayPortalHost } from "./components/OverlayPortal"; |
| 23 | import { appBlurTargetRef } from "./lib/appBlurTarget"; | 23 | import { appBlurTargetRef } from "./lib/appBlurTarget"; |
| 24 | import { useThemeColor } from "./lib/useThemeColor"; | ||
| 25 | import { useMobileNavigationTheme } from "./lib/useMobileNavigationTheme"; | 24 | import { useMobileNavigationTheme } from "./lib/useMobileNavigationTheme"; |
| 26 | 25 | ||
| 27 | import "../global.css"; | 26 | import "../global.css"; |
| ... | @@ -72,8 +71,7 @@ export default function App() { | ... | @@ -72,8 +71,7 @@ export default function App() { |
| 72 | 71 | ||
| 73 | function AppContent() { | 72 | function AppContent() { |
| 74 | const { themeAppearance } = useAppearancePreferences(); | 73 | const { themeAppearance } = useAppearancePreferences(); |
| 75 | const statusBarBg = useThemeColor("--color-status-bar"); | 74 | const navigationTheme = useMobileNavigationTheme(); |
| 76 | const navigationTheme = useMobileNavigationTheme(themeAppearance); | ||
| 77 | 75 | ||
| 78 | return ( | 76 | return ( |
| 79 | <> | 77 | <> |
| ... | @@ -83,7 +81,6 @@ function AppContent() { | ... | @@ -83,7 +81,6 @@ function AppContent() { |
| 83 | <SafeAreaProvider> | 81 | <SafeAreaProvider> |
| 84 | <StatusBar | 82 | <StatusBar |
| 85 | barStyle={themeAppearance === "dark" ? "light-content" : "dark-content"} | 83 | barStyle={themeAppearance === "dark" ? "light-content" : "dark-content"} |
| 86 | backgroundColor={statusBarBg} | ||
| 87 | translucent | 84 | translucent |
| 88 | /> | 85 | /> |
| 89 | {/* The navigation theme drives the NATIVE header appearance: native-stack | 86 | {/* The navigation theme drives the NATIVE header appearance: native-stack |
apps/mobile/src/components/AndroidAnchoredMenu.tsx+6-11| ... | @@ -9,7 +9,6 @@ import Animated, { FadeIn } from "react-native-reanimated"; | ... | @@ -9,7 +9,6 @@ import Animated, { FadeIn } from "react-native-reanimated"; |
| 9 | 9 | ||
| 10 | import { appBlurTargetRef } from "../lib/appBlurTarget"; | 10 | import { appBlurTargetRef } from "../lib/appBlurTarget"; |
| 11 | import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; | 11 | import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; |
| 12 | import { useThemeColor } from "../lib/useThemeColor"; | ||
| 13 | import { cn } from "../lib/cn"; | 12 | import { cn } from "../lib/cn"; |
| 14 | import { type AppSymbolName, SymbolView } from "./AppSymbol"; | 13 | import { type AppSymbolName, SymbolView } from "./AppSymbol"; |
| 15 | import { AppText as Text } from "./AppText"; | 14 | import { AppText as Text } from "./AppText"; |
| ... | @@ -84,11 +83,6 @@ export function AndroidAnchoredMenu(props: AndroidAnchoredMenuProps) { | ... | @@ -84,11 +83,6 @@ export function AndroidAnchoredMenu(props: AndroidAnchoredMenuProps) { |
| 84 | const isDarkMode = themeAppearance === "dark"; | 83 | const isDarkMode = themeAppearance === "dark"; |
| 85 | const keyboardVisible = useKeyboardState((state) => state.isVisible); | 84 | const keyboardVisible = useKeyboardState((state) => state.isVisible); |
| 86 | const keyboardHeight = useKeyboardState((state) => state.height); | 85 | const keyboardHeight = useKeyboardState((state) => state.height); |
| 87 | const rippleColor = useThemeColor("--color-subtle"); | ||
| 88 | const iconColor = useThemeColor("--color-icon"); | ||
| 89 | const iconSubtleColor = useThemeColor("--color-icon-subtle"); | ||
| 90 | const dangerColor = useThemeColor("--color-danger-foreground"); | ||
| 91 | |||
| 92 | const close = useCallback(() => { | 86 | const close = useCallback(() => { |
| 93 | setAnchor(null); | 87 | setAnchor(null); |
| 94 | setPath([]); | 88 | setPath([]); |
| ... | @@ -279,10 +273,9 @@ export function AndroidAnchoredMenu(props: AndroidAnchoredMenuProps) { | ... | @@ -279,10 +273,9 @@ export function AndroidAnchoredMenu(props: AndroidAnchoredMenuProps) { |
| 279 | return ( | 273 | return ( |
| 280 | <Pressable | 274 | <Pressable |
| 281 | key={action.id ?? `${index}-${action.title}`} | 275 | key={action.id ?? `${index}-${action.title}`} |
| 282 | android_ripple={{ color: rippleColor }} | ||
| 283 | disabled={disabled} | 276 | disabled={disabled} |
| 284 | className={cn( | 277 | className={cn( |
| 285 | "min-h-11 flex-row items-center gap-2.5 px-3.5 py-2.5", | 278 | "min-h-11 flex-row items-center gap-2.5 px-3.5 py-2.5 active:bg-subtle", |
| 286 | disabled && "opacity-45", | 279 | disabled && "opacity-45", |
| 287 | )} | 280 | )} |
| 288 | onPress={() => onPressItem(action)} | 281 | onPress={() => onPressItem(action)} |
| ... | @@ -307,21 +300,23 @@ export function AndroidAnchoredMenu(props: AndroidAnchoredMenuProps) { | ... | @@ -307,21 +300,23 @@ export function AndroidAnchoredMenu(props: AndroidAnchoredMenuProps) { |
| 307 | <SymbolView | 300 | <SymbolView |
| 308 | name="chevron.right" | 301 | name="chevron.right" |
| 309 | size={13} | 302 | size={13} |
| 310 | tintColor={iconSubtleColor} | 303 | tintColorClassName={"accent-icon-subtle"} |
| 311 | type="monochrome" | 304 | type="monochrome" |
| 312 | /> | 305 | /> |
| 313 | ) : action.state === "on" ? ( | 306 | ) : action.state === "on" ? ( |
| 314 | <SymbolView | 307 | <SymbolView |
| 315 | name="checkmark" | 308 | name="checkmark" |
| 316 | size={15} | 309 | size={15} |
| 317 | tintColor={iconColor} | 310 | tintColorClassName={"accent-icon"} |
| 318 | type="monochrome" | 311 | type="monochrome" |
| 319 | /> | 312 | /> |
| 320 | ) : action.image ? ( | 313 | ) : action.image ? ( |
| 321 | <SymbolView | 314 | <SymbolView |
| 322 | name={action.image as AppSymbolName} | 315 | name={action.image as AppSymbolName} |
| 323 | size={15} | 316 | size={15} |
| 324 | tintColor={destructive ? dangerColor : iconColor} | 317 | tintColorClassName={ |
| 318 | destructive ? "accent-danger-foreground" : "accent-icon" | ||
| 319 | } | ||
| 325 | type="monochrome" | 320 | type="monochrome" |
| 326 | /> | 321 | /> |
| 327 | ) : null} | 322 | ) : null} |
apps/mobile/src/components/AndroidScreenHeader.tsx+2-7| ... | @@ -5,7 +5,6 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"; | ... | @@ -5,7 +5,6 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 5 | import { SymbolView, type AppSymbolName } from "./AppSymbol"; | 5 | import { SymbolView, type AppSymbolName } from "./AppSymbol"; |
| 6 | import { AppText as Text } from "./AppText"; | 6 | import { AppText as Text } from "./AppText"; |
| 7 | import { cn } from "../lib/cn"; | 7 | import { cn } from "../lib/cn"; |
| 8 | import { useThemeColor } from "../lib/useThemeColor"; | ||
| 9 | 8 | ||
| 10 | export interface AndroidHeaderAction { | 9 | export interface AndroidHeaderAction { |
| 11 | readonly accessibilityLabel: string; | 10 | readonly accessibilityLabel: string; |
| ... | @@ -20,9 +19,6 @@ export function AndroidHeaderIconButton(props: { | ... | @@ -20,9 +19,6 @@ export function AndroidHeaderIconButton(props: { |
| 20 | readonly onPress?: () => void; | 19 | readonly onPress?: () => void; |
| 21 | readonly disabled?: boolean; | 20 | readonly disabled?: boolean; |
| 22 | }) { | 21 | }) { |
| 23 | const foregroundColor = useThemeColor("--color-foreground"); | ||
| 24 | const disabledColor = useThemeColor("--color-icon-subtle"); | ||
| 25 | |||
| 26 | return ( | 22 | return ( |
| 27 | <Pressable | 23 | <Pressable |
| 28 | accessibilityLabel={props.accessibilityLabel} | 24 | accessibilityLabel={props.accessibilityLabel} |
| ... | @@ -38,7 +34,7 @@ export function AndroidHeaderIconButton(props: { | ... | @@ -38,7 +34,7 @@ export function AndroidHeaderIconButton(props: { |
| 38 | <SymbolView | 34 | <SymbolView |
| 39 | name={props.icon} | 35 | name={props.icon} |
| 40 | size={20} | 36 | size={20} |
| 41 | tintColor={props.disabled ? disabledColor : foregroundColor} | 37 | tintColorClassName={props.disabled ? "accent-icon-subtle" : "accent-foreground"} |
| 42 | type="monochrome" | 38 | type="monochrome" |
| 43 | /> | 39 | /> |
| 44 | </Pressable> | 40 | </Pressable> |
| ... | @@ -54,7 +50,6 @@ export function AndroidScreenHeader(props: { | ... | @@ -54,7 +50,6 @@ export function AndroidScreenHeader(props: { |
| 54 | readonly embedded?: boolean; | 50 | readonly embedded?: boolean; |
| 55 | }) { | 51 | }) { |
| 56 | const insets = useSafeAreaInsets(); | 52 | const insets = useSafeAreaInsets(); |
| 57 | const foregroundColor = useThemeColor("--color-foreground"); | ||
| 58 | 53 | ||
| 59 | return ( | 54 | return ( |
| 60 | <View | 55 | <View |
| ... | @@ -75,7 +70,7 @@ export function AndroidScreenHeader(props: { | ... | @@ -75,7 +70,7 @@ export function AndroidScreenHeader(props: { |
| 75 | <SymbolView | 70 | <SymbolView |
| 76 | name="chevron.left" | 71 | name="chevron.left" |
| 77 | size={24} | 72 | size={24} |
| 78 | tintColor={foregroundColor} | 73 | tintColorClassName={"accent-foreground"} |
| 79 | type="monochrome" | 74 | type="monochrome" |
| 80 | /> | 75 | /> |
| 81 | </Pressable> | 76 | </Pressable> |
apps/mobile/src/components/AppSymbol.tsx+10-1| ... | @@ -81,6 +81,7 @@ import { | ... | @@ -81,6 +81,7 @@ import { |
| 81 | } from "@tabler/icons-react-native"; | 81 | } from "@tabler/icons-react-native"; |
| 82 | import { Platform } from "react-native"; | 82 | import { Platform } from "react-native"; |
| 83 | import { SymbolView as ExpoSymbolView, type SFSymbol, type SymbolViewProps } from "expo-symbols"; | 83 | import { SymbolView as ExpoSymbolView, type SFSymbol, type SymbolViewProps } from "expo-symbols"; |
| 84 | import { withUniwind } from "uniwind"; | ||
| 84 | 85 | ||
| 85 | const ANDROID_ICON_BY_SF_SYMBOL: Partial<Record<SFSymbol, Icon>> = { | 86 | const ANDROID_ICON_BY_SF_SYMBOL: Partial<Record<SFSymbol, Icon>> = { |
| 86 | "arrow.branch": IconGitBranch, | 87 | "arrow.branch": IconGitBranch, |
| ... | @@ -190,7 +191,7 @@ const ANDROID_ICON_BY_MATERIAL_NAME: Record<string, Icon> = { | ... | @@ -190,7 +191,7 @@ const ANDROID_ICON_BY_MATERIAL_NAME: Record<string, Icon> = { |
| 190 | export type { SFSymbol } from "expo-symbols"; | 191 | export type { SFSymbol } from "expo-symbols"; |
| 191 | export type AppSymbolName = SymbolViewProps["name"]; | 192 | export type AppSymbolName = SymbolViewProps["name"]; |
| 192 | 193 | ||
| 193 | export function SymbolView(props: SymbolViewProps) { | 194 | function AppSymbolView(props: SymbolViewProps) { |
| 194 | if (Platform.OS !== "android") { | 195 | if (Platform.OS !== "android") { |
| 195 | return <ExpoSymbolView {...props} />; | 196 | return <ExpoSymbolView {...props} />; |
| 196 | } | 197 | } |
| ... | @@ -216,3 +217,11 @@ export function SymbolView(props: SymbolViewProps) { | ... | @@ -216,3 +217,11 @@ export function SymbolView(props: SymbolViewProps) { |
| 216 | /> | 217 | /> |
| 217 | ); | 218 | ); |
| 218 | } | 219 | } |
| 220 | |||
| 221 | /** | ||
| 222 | * expo-symbols and the Android Tabler fallback both expose tint as a native | ||
| 223 | * prop rather than a React Native style. Keep that third-party boundary here | ||
| 224 | * so callers can use Uniwind's `tintColorClassName` instead of subscribing to | ||
| 225 | * theme variables in every parent component. | ||
| 226 | */ | ||
| 227 | export const SymbolView = withUniwind(AppSymbolView); |
apps/mobile/src/components/CompactBrandTitle.tsx+6-32| ... | @@ -9,7 +9,6 @@ import { AppText as Text } from "./AppText"; | ... | @@ -9,7 +9,6 @@ import { AppText as Text } from "./AppText"; |
| 9 | import { T3Wordmark } from "./T3Wordmark"; | 9 | import { T3Wordmark } from "./T3Wordmark"; |
| 10 | import { IPAD_HOME_TITLE_OFFSET } from "../lib/layoutMetrics"; | 10 | import { IPAD_HOME_TITLE_OFFSET } from "../lib/layoutMetrics"; |
| 11 | import { resolveMobileStageLabel } from "../lib/mobileBranding"; | 11 | import { resolveMobileStageLabel } from "../lib/mobileBranding"; |
| 12 | import { useThemeColor } from "../lib/useThemeColor"; | ||
| 13 | import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../native/native-glass"; | 12 | import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../native/native-glass"; |
| 14 | 13 | ||
| 15 | // Native leading items inherit different UIKit margins than title views. | 14 | // Native leading items inherit different UIKit margins than title views. |
| ... | @@ -37,9 +36,6 @@ export function CompactBrandTitle( | ... | @@ -37,9 +36,6 @@ export function CompactBrandTitle( |
| 37 | readonly nativeLeadingItem?: boolean; | 36 | readonly nativeLeadingItem?: boolean; |
| 38 | } = {}, | 37 | } = {}, |
| 39 | ) { | 38 | ) { |
| 40 | const iconColor = useThemeColor("--color-icon"); | ||
| 41 | const mutedColor = useThemeColor("--color-foreground-muted"); | ||
| 42 | const subtleColor = useThemeColor("--color-subtle"); | ||
| 43 | const stageLabel = resolveMobileStageLabel(Constants.expoConfig?.extra?.appVariant); | 39 | const stageLabel = resolveMobileStageLabel(Constants.expoConfig?.extra?.appVariant); |
| 44 | const titleOffset = brandTitleOffset(props.nativeLeadingItem === true); | 40 | const titleOffset = brandTitleOffset(props.nativeLeadingItem === true); |
| 45 | 41 | ||
| ... | @@ -49,42 +45,20 @@ export function CompactBrandTitle( | ... | @@ -49,42 +45,20 @@ export function CompactBrandTitle( |
| 49 | accessibilityLabel="T3 Code, Threads" | 45 | accessibilityLabel="T3 Code, Threads" |
| 50 | accessible | 46 | accessible |
| 51 | role="heading" | 47 | role="heading" |
| 52 | style={{ | 48 | className="flex-row items-center gap-1.5" |
| 53 | alignItems: "center", | 49 | style={{ marginLeft: titleOffset }} |
| 54 | flexDirection: "row", | ||
| 55 | gap: 6, | ||
| 56 | marginLeft: titleOffset, | ||
| 57 | }} | ||
| 58 | > | 50 | > |
| 59 | <T3Wordmark color={iconColor} height={15} /> | 51 | <T3Wordmark colorClassName="accent-icon" height={15} /> |
| 60 | <Text | 52 | <Text |
| 61 | allowFontScaling={props.allowFontScaling} | 53 | allowFontScaling={props.allowFontScaling} |
| 62 | style={{ | 54 | className="font-t3-medium text-[21px] tracking-[-0.5px] text-foreground-muted" |
| 63 | color: mutedColor, | ||
| 64 | fontFamily: "DMSans-Medium", | ||
| 65 | fontSize: 21, | ||
| 66 | letterSpacing: -0.5, | ||
| 67 | }} | ||
| 68 | > | 55 | > |
| 69 | Code | 56 | Code |
| 70 | </Text> | 57 | </Text> |
| 71 | <View | 58 | <View className="rounded-full bg-subtle px-1.5 py-0.5"> |
| 72 | style={{ | ||
| 73 | backgroundColor: subtleColor, | ||
| 74 | borderRadius: 999, | ||
| 75 | paddingHorizontal: 6, | ||
| 76 | paddingVertical: 2, | ||
| 77 | }} | ||
| 78 | > | ||
| 79 | <Text | 59 | <Text |
| 80 | allowFontScaling={props.allowFontScaling} | 60 | allowFontScaling={props.allowFontScaling} |
| 81 | style={{ | 61 | className="font-t3-bold text-[9px] tracking-[0.9px] text-foreground-muted uppercase" |
| 82 | color: mutedColor, | ||
| 83 | fontFamily: "DMSans-Bold", | ||
| 84 | fontSize: 9, | ||
| 85 | letterSpacing: 0.9, | ||
| 86 | textTransform: "uppercase", | ||
| 87 | }} | ||
| 88 | > | 62 | > |
| 89 | {stageLabel} | 63 | {stageLabel} |
| 90 | </Text> | 64 | </Text> |
apps/mobile/src/components/ComposerAttachmentStrip.tsx+1-3| ... | @@ -1,6 +1,5 @@ | ... | @@ -1,6 +1,5 @@ |
| 1 | import { SymbolView } from "../components/AppSymbol"; | 1 | import { SymbolView } from "../components/AppSymbol"; |
| 2 | import { Image, Pressable, ScrollView, View } from "react-native"; | 2 | import { Image, Pressable, ScrollView, View } from "react-native"; |
| 3 | import { useThemeColor } from "../lib/useThemeColor"; | ||
| 4 | 3 | ||
| 5 | import type { DraftComposerImageAttachment } from "../lib/composerImages"; | 4 | import type { DraftComposerImageAttachment } from "../lib/composerImages"; |
| 6 | 5 | ||
| ... | @@ -24,7 +23,6 @@ export interface ComposerAttachmentStripProps { | ... | @@ -24,7 +23,6 @@ export interface ComposerAttachmentStripProps { |
| 24 | * buttons. Used by both the thread composer and the new-task draft screen. | 23 | * buttons. Used by both the thread composer and the new-task draft screen. |
| 25 | */ | 24 | */ |
| 26 | export function ComposerAttachmentStrip(props: ComposerAttachmentStripProps) { | 25 | export function ComposerAttachmentStrip(props: ComposerAttachmentStripProps) { |
| 27 | const subtleBg = useThemeColor("--color-subtle"); | ||
| 28 | const size = props.imageSize ?? 72; | 26 | const size = props.imageSize ?? 72; |
| 29 | const radius = props.imageBorderRadius ?? 16; | 27 | const radius = props.imageBorderRadius ?? 16; |
| 30 | const removeButtonPlacement = props.removeButtonPlacement ?? "overlay"; | 28 | const removeButtonPlacement = props.removeButtonPlacement ?? "overlay"; |
| ... | @@ -56,11 +54,11 @@ export function ComposerAttachmentStrip(props: ComposerAttachmentStripProps) { | ... | @@ -56,11 +54,11 @@ export function ComposerAttachmentStrip(props: ComposerAttachmentStripProps) { |
| 56 | > | 54 | > |
| 57 | <Image | 55 | <Image |
| 58 | source={{ uri: image.previewUri }} | 56 | source={{ uri: image.previewUri }} |
| 57 | className="bg-subtle" | ||
| 59 | style={{ | 58 | style={{ |
| 60 | width: size, | 59 | width: size, |
| 61 | height: size, | 60 | height: size, |
| 62 | borderRadius: radius, | 61 | borderRadius: radius, |
| 63 | backgroundColor: subtleBg, | ||
| 64 | }} | 62 | }} |
| 65 | resizeMode="cover" | 63 | resizeMode="cover" |
| 66 | /> | 64 | /> |
apps/mobile/src/components/ComposerToolbar.tsx+65-54| ... | @@ -11,9 +11,6 @@ import { | ... | @@ -11,9 +11,6 @@ import { |
| 11 | type ViewStyle, | 11 | type ViewStyle, |
| 12 | } from "react-native"; | 12 | } from "react-native"; |
| 13 | 13 | ||
| 14 | import { useThemeColor } from "../lib/useThemeColor"; | ||
| 15 | import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; | ||
| 16 | import { themeColorWithAlpha } from "../lib/mobileTheme"; | ||
| 17 | import { cn } from "../lib/cn"; | 14 | import { cn } from "../lib/cn"; |
| 18 | import { AppText as Text } from "./AppText"; | 15 | import { AppText as Text } from "./AppText"; |
| 19 | import { SymbolView } from "./AppSymbol"; | 16 | import { SymbolView } from "./AppSymbol"; |
| ... | @@ -42,10 +39,6 @@ export function ComposerInlineControl(props: { | ... | @@ -42,10 +39,6 @@ export function ComposerInlineControl(props: { |
| 42 | readonly chevronDirection?: "down" | "right"; | 39 | readonly chevronDirection?: "down" | "right"; |
| 43 | readonly showChevron?: boolean; | 40 | readonly showChevron?: boolean; |
| 44 | }) { | 41 | }) { |
| 45 | const iconColor = useThemeColor( | ||
| 46 | props.emphasized || props.selected ? "--color-icon" : "--color-icon-muted", | ||
| 47 | ); | ||
| 48 | |||
| 49 | return ( | 42 | return ( |
| 50 | <Pressable | 43 | <Pressable |
| 51 | accessibilityLabel={props.accessibilityLabel ?? props.label} | 44 | accessibilityLabel={props.accessibilityLabel ?? props.label} |
| ... | @@ -62,7 +55,14 @@ export function ComposerInlineControl(props: { | ... | @@ -62,7 +55,14 @@ export function ComposerInlineControl(props: { |
| 62 | {props.iconNode ? ( | 55 | {props.iconNode ? ( |
| 63 | <View className="size-4 shrink-0 items-center justify-center">{props.iconNode}</View> | 56 | <View className="size-4 shrink-0 items-center justify-center">{props.iconNode}</View> |
| 64 | ) : props.icon ? ( | 57 | ) : props.icon ? ( |
| 65 | <SymbolView name={props.icon} size={16} tintColor={iconColor} type="monochrome" /> | 58 | <SymbolView |
| 59 | name={props.icon} | ||
| 60 | size={16} | ||
| 61 | tintColorClassName={ | ||
| 62 | props.emphasized || props.selected ? "accent-icon" : "accent-icon-muted" | ||
| 63 | } | ||
| 64 | type="monochrome" | ||
| 65 | /> | ||
| 66 | ) : null} | 66 | ) : null} |
| 67 | <Text | 67 | <Text |
| 68 | className={cn( | 68 | className={cn( |
| ... | @@ -77,7 +77,9 @@ export function ComposerInlineControl(props: { | ... | @@ -77,7 +77,9 @@ export function ComposerInlineControl(props: { |
| 77 | <SymbolView | 77 | <SymbolView |
| 78 | name={props.chevronDirection === "right" ? "chevron.right" : "chevron.down"} | 78 | name={props.chevronDirection === "right" ? "chevron.right" : "chevron.down"} |
| 79 | size={10} | 79 | size={10} |
| 80 | tintColor={iconColor} | 80 | tintColorClassName={ |
| 81 | props.emphasized || props.selected ? "accent-icon" : "accent-icon-muted" | ||
| 82 | } | ||
| 81 | type="monochrome" | 83 | type="monochrome" |
| 82 | /> | 84 | /> |
| 83 | )} | 85 | )} |
| ... | @@ -111,8 +113,12 @@ export function ComposerToolbarRow(props: { | ... | @@ -111,8 +113,12 @@ export function ComposerToolbarRow(props: { |
| 111 | 113 | ||
| 112 | export function ComposerToolbarScroller(props: { | 114 | export function ComposerToolbarScroller(props: { |
| 113 | readonly children: ReactNode; | 115 | readonly children: ReactNode; |
| 114 | readonly fadeOpaque: string; | 116 | /** Only for non-Uniwind surfaces such as the native terminal palette. */ |
| 115 | readonly fadeTransparent: string; | 117 | readonly fadeOpaque?: string; |
| 118 | /** Only for non-Uniwind surfaces such as the native terminal palette. */ | ||
| 119 | readonly fadeTransparent?: string; | ||
| 120 | /** Semantic Uniwind surface behind the toolbar. Defaults to card. */ | ||
| 121 | readonly fadeSurface?: "card" | "sheet"; | ||
| 116 | readonly contentPaddingRight?: number; | 122 | readonly contentPaddingRight?: number; |
| 117 | }) { | 123 | }) { |
| 118 | const [metrics, setMetrics] = useState({ | 124 | const [metrics, setMetrics] = useState({ |
| ... | @@ -170,27 +176,37 @@ export function ComposerToolbarScroller(props: { | ... | @@ -170,27 +176,37 @@ export function ComposerToolbarScroller(props: { |
| 170 | </ScrollView> | 176 | </ScrollView> |
| 171 | {scrollEdges.showLeftFade ? ( | 177 | {scrollEdges.showLeftFade ? ( |
| 172 | <View | 178 | <View |
| 179 | className={cn( | ||
| 180 | "absolute inset-y-0 left-0", | ||
| 181 | props.fadeOpaque === undefined && "bg-linear-to-r to-transparent", | ||
| 182 | props.fadeOpaque === undefined && | ||
| 183 | (props.fadeSurface === "sheet" ? "from-sheet" : "from-card"), | ||
| 184 | )} | ||
| 173 | pointerEvents="none" | 185 | pointerEvents="none" |
| 174 | style={{ | 186 | style={{ |
| 175 | bottom: 0, | ||
| 176 | experimental_backgroundImage: `linear-gradient(to right, ${props.fadeOpaque} 0%, ${props.fadeTransparent} 100%)`, | ||
| 177 | left: 0, | ||
| 178 | position: "absolute", | ||
| 179 | top: 0, | ||
| 180 | width: COMPOSER_TOOLBAR_FADE_WIDTH, | 187 | width: COMPOSER_TOOLBAR_FADE_WIDTH, |
| 188 | experimental_backgroundImage: | ||
| 189 | props.fadeOpaque === undefined | ||
| 190 | ? undefined | ||
| 191 | : `linear-gradient(to right, ${props.fadeOpaque} 0%, ${props.fadeTransparent ?? "transparent"} 100%)`, | ||
| 181 | }} | 192 | }} |
| 182 | /> | 193 | /> |
| 183 | ) : null} | 194 | ) : null} |
| 184 | {scrollEdges.showRightFade ? ( | 195 | {scrollEdges.showRightFade ? ( |
| 185 | <View | 196 | <View |
| 197 | className={cn( | ||
| 198 | "absolute inset-y-0 right-0", | ||
| 199 | props.fadeOpaque === undefined && "bg-linear-to-r from-transparent", | ||
| 200 | props.fadeOpaque === undefined && | ||
| 201 | (props.fadeSurface === "sheet" ? "to-sheet" : "to-card"), | ||
| 202 | )} | ||
| 186 | pointerEvents="none" | 203 | pointerEvents="none" |
| 187 | style={{ | 204 | style={{ |
| 188 | bottom: 0, | ||
| 189 | experimental_backgroundImage: `linear-gradient(to right, ${props.fadeTransparent} 0%, ${props.fadeOpaque} 100%)`, | ||
| 190 | position: "absolute", | ||
| 191 | right: 0, | ||
| 192 | top: 0, | ||
| 193 | width: COMPOSER_TOOLBAR_FADE_WIDTH, | 205 | width: COMPOSER_TOOLBAR_FADE_WIDTH, |
| 206 | experimental_backgroundImage: | ||
| 207 | props.fadeOpaque === undefined | ||
| 208 | ? undefined | ||
| 209 | : `linear-gradient(to right, ${props.fadeTransparent ?? "transparent"} 0%, ${props.fadeOpaque} 100%)`, | ||
| 194 | }} | 210 | }} |
| 195 | /> | 211 | /> |
| 196 | ) : null} | 212 | ) : null} |
| ... | @@ -214,30 +230,16 @@ export function ComposerToolbarButton(props: { | ... | @@ -214,30 +230,16 @@ export function ComposerToolbarButton(props: { |
| 214 | readonly className?: string; | 230 | readonly className?: string; |
| 215 | readonly style?: StyleProp<ViewStyle>; | 231 | readonly style?: StyleProp<ViewStyle>; |
| 216 | }) { | 232 | }) { |
| 217 | const { themeAppearance } = useAppearancePreferences(); | ||
| 218 | const isDarkMode = themeAppearance === "dark"; | ||
| 219 | const iconColor = useThemeColor("--color-icon"); | ||
| 220 | const iconSubtle = useThemeColor("--color-icon-subtle"); | ||
| 221 | const primaryFg = useThemeColor("--color-primary-foreground"); | ||
| 222 | const dangerFg = useThemeColor("--color-danger-foreground"); | ||
| 223 | const variant = props.variant ?? "default"; | 233 | const variant = props.variant ?? "default"; |
| 224 | const isCircle = !props.label && props.showChevron === false; | 234 | const isCircle = !props.label && props.showChevron === false; |
| 225 | const defaultBorderColor = useThemeColor("--color-border-subtle"); | 235 | const iconTintClassName = |
| 226 | const activeBorderColor = useThemeColor("--color-border"); | ||
| 227 | const filledBorderColor = | ||
| 228 | variant === "danger" | ||
| 229 | ? themeColorWithAlpha(String(dangerFg), 0.14) | ||
| 230 | : props.disabled | ||
| 231 | ? defaultBorderColor | ||
| 232 | : themeColorWithAlpha(String(primaryFg), 0.18); | ||
| 233 | const iconTintColor = | ||
| 234 | variant === "primary" | 236 | variant === "primary" |
| 235 | ? props.disabled | 237 | ? props.disabled |
| 236 | ? iconSubtle | 238 | ? "accent-icon-subtle" |
| 237 | : primaryFg | 239 | : "accent-primary-foreground" |
| 238 | : variant === "danger" | 240 | : variant === "danger" |
| 239 | ? dangerFg | 241 | ? "accent-danger-foreground" |
| 240 | : iconColor; | 242 | : "accent-icon"; |
| 241 | 243 | ||
| 242 | return ( | 244 | return ( |
| 243 | <Pressable | 245 | <Pressable |
| ... | @@ -250,7 +252,7 @@ export function ComposerToolbarButton(props: { | ... | @@ -250,7 +252,7 @@ export function ComposerToolbarButton(props: { |
| 250 | // so callers can lift it with max-w-full — flex-filling pills in the | 252 | // so callers can lift it with max-w-full — flex-filling pills in the |
| 251 | // thread composer stretch to the row's edge. The numeric maxWidth | 253 | // thread composer stretch to the row's edge. The numeric maxWidth |
| 252 | // prop still wins via the inline style below. | 254 | // prop still wins via the inline style below. |
| 253 | "h-11 max-w-[172px] flex-row items-center justify-center rounded-full active:opacity-70", | 255 | "h-11 max-w-[172px] flex-row items-center justify-center rounded-full border shadow-lg shadow-adaptive-black-a10-a25 active:opacity-70", |
| 254 | isCircle ? "w-11" : "gap-2 px-3.5", | 256 | isCircle ? "w-11" : "gap-2 px-3.5", |
| 255 | variant === "primary" | 257 | variant === "primary" |
| 256 | ? props.disabled | 258 | ? props.disabled |
| ... | @@ -261,24 +263,23 @@ export function ComposerToolbarButton(props: { | ... | @@ -261,24 +263,23 @@ export function ComposerToolbarButton(props: { |
| 261 | : props.active | 263 | : props.active |
| 262 | ? "bg-subtle-strong" | 264 | ? "bg-subtle-strong" |
| 263 | : "bg-subtle", | 265 | : "bg-subtle", |
| 266 | variant === "default" | ||
| 267 | ? props.active | ||
| 268 | ? "border-border" | ||
| 269 | : "border-border-subtle" | ||
| 270 | : variant === "danger" | ||
| 271 | ? "border-danger-border" | ||
| 272 | : props.disabled | ||
| 273 | ? "border-border-subtle" | ||
| 274 | : "border-primary-foreground/20", | ||
| 264 | props.className, | 275 | props.className, |
| 265 | )} | 276 | )} |
| 266 | style={({ pressed }) => [ | 277 | style={({ pressed }) => [ |
| 267 | { | 278 | { |
| 268 | borderColor: | ||
| 269 | variant === "default" | ||
| 270 | ? props.active | ||
| 271 | ? activeBorderColor | ||
| 272 | : defaultBorderColor | ||
| 273 | : filledBorderColor, | ||
| 274 | borderWidth: 1, | ||
| 275 | maxWidth: props.maxWidth, | 279 | maxWidth: props.maxWidth, |
| 276 | minWidth: props.minWidth, | 280 | minWidth: props.minWidth, |
| 277 | opacity: props.disabled ? 0.55 : pressed ? 0.72 : 1, | 281 | opacity: props.disabled ? 0.55 : pressed ? 0.72 : 1, |
| 278 | shadowColor: "#000", | 282 | shadowOpacity: props.disabled ? 0 : 1, |
| 279 | shadowOffset: { width: 0, height: isDarkMode ? 3 : 2 }, | ||
| 280 | shadowOpacity: props.disabled ? 0 : isDarkMode ? 0.24 : 0.08, | ||
| 281 | shadowRadius: isDarkMode ? 10 : 8, | ||
| 282 | }, | 283 | }, |
| 283 | props.style, | 284 | props.style, |
| 284 | ]} | 285 | ]} |
| ... | @@ -286,7 +287,12 @@ export function ComposerToolbarButton(props: { | ... | @@ -286,7 +287,12 @@ export function ComposerToolbarButton(props: { |
| 286 | {props.iconNode ? ( | 287 | {props.iconNode ? ( |
| 287 | <View className="h-4 w-4 items-center justify-center">{props.iconNode}</View> | 288 | <View className="h-4 w-4 items-center justify-center">{props.iconNode}</View> |
| 288 | ) : props.icon ? ( | 289 | ) : props.icon ? ( |
| 289 | <SymbolView name={props.icon} size={16} tintColor={iconTintColor} type="monochrome" /> | 290 | <SymbolView |
| 291 | name={props.icon} | ||
| 292 | size={16} | ||
| 293 | tintColorClassName={iconTintClassName} | ||
| 294 | type="monochrome" | ||
| 295 | /> | ||
| 290 | ) : null} | 296 | ) : null} |
| 291 | {props.label ? ( | 297 | {props.label ? ( |
| 292 | <Text | 298 | <Text |
| ... | @@ -306,7 +312,12 @@ export function ComposerToolbarButton(props: { | ... | @@ -306,7 +312,12 @@ export function ComposerToolbarButton(props: { |
| 306 | </Text> | 312 | </Text> |
| 307 | ) : null} | 313 | ) : null} |
| 308 | {props.showChevron === false ? null : ( | 314 | {props.showChevron === false ? null : ( |
| 309 | <SymbolView name="chevron.down" size={11} tintColor={iconTintColor} type="monochrome" /> | 315 | <SymbolView |
| 316 | name="chevron.down" | ||
| 317 | size={11} | ||
| 318 | tintColorClassName={iconTintClassName} | ||
| 319 | type="monochrome" | ||
| 320 | /> | ||
| 310 | )} | 321 | )} |
| 311 | </Pressable> | 322 | </Pressable> |
| 312 | ); | 323 | ); |
apps/mobile/src/components/ConfirmDialogHost.tsx+2-7| ... | @@ -1,7 +1,6 @@ | ... | @@ -1,7 +1,6 @@ |
| 1 | import { useCallback, useEffect, useState } from "react"; | 1 | import { useCallback, useEffect, useState } from "react"; |
| 2 | import { Modal, Pressable, View } from "react-native"; | 2 | import { Modal, Pressable, View } from "react-native"; |
| 3 | 3 | ||
| 4 | import { useThemeColor } from "../lib/useThemeColor"; | ||
| 5 | import { cn } from "../lib/cn"; | 4 | import { cn } from "../lib/cn"; |
| 6 | import { AppText } from "./AppText"; | 5 | import { AppText } from "./AppText"; |
| 7 | 6 | ||
| ... | @@ -35,8 +34,6 @@ export function showConfirmDialog(request: ConfirmDialogRequest): void { | ... | @@ -35,8 +34,6 @@ export function showConfirmDialog(request: ConfirmDialogRequest): void { |
| 35 | */ | 34 | */ |
| 36 | export function ConfirmDialogHost() { | 35 | export function ConfirmDialogHost() { |
| 37 | const [request, setRequest] = useState<ConfirmDialogRequest | null>(null); | 36 | const [request, setRequest] = useState<ConfirmDialogRequest | null>(null); |
| 38 | const pressedOverlay = useThemeColor("--color-subtle"); | ||
| 39 | |||
| 40 | useEffect(() => { | 37 | useEffect(() => { |
| 41 | presentRequest = setRequest; | 38 | presentRequest = setRequest; |
| 42 | return () => { | 39 | return () => { |
| ... | @@ -76,8 +73,7 @@ export function ConfirmDialogHost() { | ... | @@ -76,8 +73,7 @@ export function ConfirmDialogHost() { |
| 76 | <View className="overflow-hidden rounded-full"> | 73 | <View className="overflow-hidden rounded-full"> |
| 77 | <Pressable | 74 | <Pressable |
| 78 | accessibilityRole="button" | 75 | accessibilityRole="button" |
| 79 | className="min-h-10 items-center justify-center px-4" | 76 | className="min-h-10 items-center justify-center px-4 active:bg-subtle" |
| 80 | android_ripple={{ color: pressedOverlay }} | ||
| 81 | onPress={handleCancel} | 77 | onPress={handleCancel} |
| 82 | > | 78 | > |
| 83 | <AppText className="text-base font-t3-medium"> | 79 | <AppText className="text-base font-t3-medium"> |
| ... | @@ -88,8 +84,7 @@ export function ConfirmDialogHost() { | ... | @@ -88,8 +84,7 @@ export function ConfirmDialogHost() { |
| 88 | <View className="overflow-hidden rounded-full"> | 84 | <View className="overflow-hidden rounded-full"> |
| 89 | <Pressable | 85 | <Pressable |
| 90 | accessibilityRole="button" | 86 | accessibilityRole="button" |
| 91 | className="min-h-10 items-center justify-center px-4" | 87 | className="min-h-10 items-center justify-center px-4 active:bg-subtle" |
| 92 | android_ripple={{ color: pressedOverlay }} | ||
| 93 | onPress={handleConfirm} | 88 | onPress={handleConfirm} |
| 94 | > | 89 | > |
| 95 | <AppText | 90 | <AppText |
apps/mobile/src/components/ControlPill.tsx+11-11| ... | @@ -9,7 +9,6 @@ import { | ... | @@ -9,7 +9,6 @@ import { |
| 9 | useRef, | 9 | useRef, |
| 10 | } from "react"; | 10 | } from "react"; |
| 11 | import { Platform, Pressable, View } from "react-native"; | 11 | import { Platform, Pressable, View } from "react-native"; |
| 12 | import { useThemeColor } from "../lib/useThemeColor"; | ||
| 13 | import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; | 12 | import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; |
| 14 | 13 | ||
| 15 | import { cn } from "../lib/cn"; | 14 | import { cn } from "../lib/cn"; |
| ... | @@ -49,18 +48,14 @@ export function ControlPill(props: { | ... | @@ -49,18 +48,14 @@ export function ControlPill(props: { |
| 49 | props.onPress?.(); | 48 | props.onPress?.(); |
| 50 | }; | 49 | }; |
| 51 | 50 | ||
| 52 | const iconColor = useThemeColor("--color-icon"); | 51 | const iconTintClassName = |
| 53 | const iconSubtle = useThemeColor("--color-icon-subtle"); | ||
| 54 | const primaryFg = useThemeColor("--color-primary-foreground"); | ||
| 55 | const dangerFg = useThemeColor("--color-danger-foreground"); | ||
| 56 | const iconTintColor = | ||
| 57 | variant === "primary" | 52 | variant === "primary" |
| 58 | ? props.disabled | 53 | ? props.disabled |
| 59 | ? iconSubtle | 54 | ? "accent-icon-subtle" |
| 60 | : primaryFg | 55 | : "accent-primary-foreground" |
| 61 | : variant === "danger" | 56 | : variant === "danger" |
| 62 | ? dangerFg | 57 | ? "accent-danger-foreground" |
| 63 | : iconColor; | 58 | : "accent-icon"; |
| 64 | 59 | ||
| 65 | const isCircle = | 60 | const isCircle = |
| 66 | variant === "circle" || variant === "danger" || (variant === "primary" && !props.label); | 61 | variant === "circle" || variant === "danger" || (variant === "primary" && !props.label); |
| ... | @@ -101,7 +96,12 @@ export function ControlPill(props: { | ... | @@ -101,7 +96,12 @@ export function ControlPill(props: { |
| 101 | {props.iconNode ? ( | 96 | {props.iconNode ? ( |
| 102 | <View className="h-4 w-4 items-center justify-center">{props.iconNode}</View> | 97 | <View className="h-4 w-4 items-center justify-center">{props.iconNode}</View> |
| 103 | ) : props.icon ? ( | 98 | ) : props.icon ? ( |
| 104 | <SymbolView name={props.icon} size={16} tintColor={iconTintColor} type="monochrome" /> | 99 | <SymbolView |
| 100 | name={props.icon} | ||
| 101 | size={16} | ||
| 102 | tintColorClassName={iconTintClassName} | ||
| 103 | type="monochrome" | ||
| 104 | /> | ||
| 105 | ) : null} | 105 | ) : null} |
| 106 | {props.label ? <Text className={labelClassName}>{props.label}</Text> : null} | 106 | {props.label ? <Text className={labelClassName}>{props.label}</Text> : null} |
| 107 | </Pressable> | 107 | </Pressable> |
apps/mobile/src/components/ErrorBanner.tsx+2-4| ... | @@ -3,10 +3,8 @@ import { View } from "react-native"; | ... | @@ -3,10 +3,8 @@ import { View } from "react-native"; |
| 3 | import { AppText as Text } from "./AppText"; | 3 | import { AppText as Text } from "./AppText"; |
| 4 | export function ErrorBanner(props: { readonly message: string }) { | 4 | export function ErrorBanner(props: { readonly message: string }) { |
| 5 | return ( | 5 | return ( |
| 6 | <View className="rounded-2xl border border-rose-300/70 bg-rose-100/80 px-3.5 py-3 dark:border-rose-400/28 dark:bg-rose-500/12"> | 6 | <View className="rounded-2xl border border-adaptive-rose-300-a70-400-a28 bg-adaptive-rose-100-a80-500-a12 px-3.5 py-3"> |
| 7 | <Text className="font-t3-medium text-sm text-rose-700 dark:text-rose-300"> | 7 | <Text className="font-t3-medium text-sm text-adaptive-rose-700-300">{props.message}</Text> |
| 8 | {props.message} | ||
| 9 | </Text> | ||
| 10 | </View> | 8 | </View> |
| 11 | ); | 9 | ); |
| 12 | } | 10 | } |
apps/mobile/src/components/GlassSafeAreaView.tsx+1-12| ... | @@ -1,7 +1,6 @@ | ... | @@ -1,7 +1,6 @@ |
| 1 | import type { ReactNode } from "react"; | 1 | import type { ReactNode } from "react"; |
| 2 | import { View, type StyleProp, type ViewStyle } from "react-native"; | 2 | import { View, type StyleProp, type ViewStyle } from "react-native"; |
| 3 | import { useSafeAreaInsets } from "react-native-safe-area-context"; | 3 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 4 | import { useThemeColor } from "../lib/useThemeColor"; | ||
| 5 | 4 | ||
| 6 | import { GlassSurface } from "./GlassSurface"; | 5 | import { GlassSurface } from "./GlassSurface"; |
| 7 | 6 | ||
| ... | @@ -19,23 +18,13 @@ export function GlassSafeAreaView({ | ... | @@ -19,23 +18,13 @@ export function GlassSafeAreaView({ |
| 19 | style, | 18 | style, |
| 20 | }: GlassSafeAreaViewProps) { | 19 | }: GlassSafeAreaViewProps) { |
| 21 | const insets = useSafeAreaInsets(); | 20 | const insets = useSafeAreaInsets(); |
| 22 | const headerColor = useThemeColor("--color-header"); | ||
| 23 | const headerBorderColor = useThemeColor("--color-header-border"); | ||
| 24 | const glassTint = useThemeColor("--color-glass-tint"); | ||
| 25 | const headerPaddingTop = insets.top + 16; | 21 | const headerPaddingTop = insets.top + 16; |
| 26 | const surfaceStyle = { | ||
| 27 | borderRadius: 0, | ||
| 28 | backgroundColor: headerColor, | ||
| 29 | borderBottomWidth: 1, | ||
| 30 | borderBottomColor: headerBorderColor, | ||
| 31 | } as const; | ||
| 32 | 22 | ||
| 33 | return ( | 23 | return ( |
| 34 | <View style={[surfaceStyle, style]}> | 24 | <View className="border-b border-header-border bg-header" style={style}> |
| 35 | <GlassSurface | 25 | <GlassSurface |
| 36 | chrome="none" | 26 | chrome="none" |
| 37 | glassEffectStyle="regular" | 27 | glassEffectStyle="regular" |
| 38 | tintColor={glassTint} | ||
| 39 | style={{ borderRadius: 0, backgroundColor: "transparent" }} | 28 | style={{ borderRadius: 0, backgroundColor: "transparent" }} |
| 40 | > | 29 | > |
| 41 | <View | 30 | <View |
apps/mobile/src/components/GlassSurface.tsx+37-15| ... | @@ -2,22 +2,29 @@ import { GlassView, isGlassEffectAPIAvailable } from "expo-glass-effect"; | ... | @@ -2,22 +2,29 @@ import { GlassView, isGlassEffectAPIAvailable } from "expo-glass-effect"; |
| 2 | import type { ReactNode } from "react"; | 2 | import type { ReactNode } from "react"; |
| 3 | import { | 3 | import { |
| 4 | Platform, | 4 | Platform, |
| 5 | useColorScheme, | ||
| 5 | View, | 6 | View, |
| 6 | type ColorValue, | 7 | type ColorValue, |
| 7 | type StyleProp, | 8 | type StyleProp, |
| 8 | type ViewProps, | 9 | type ViewProps, |
| 9 | type ViewStyle, | 10 | type ViewStyle, |
| 10 | } from "react-native"; | 11 | } from "react-native"; |
| 11 | import { useThemeColor } from "../lib/useThemeColor"; | 12 | import { withUniwind } from "uniwind"; |
| 12 | import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; | ||
| 13 | 13 | ||
| 14 | interface GlassSurfaceProps extends Omit<ViewProps, "className"> { | 14 | import { cn } from "../lib/cn"; |
| 15 | |||
| 16 | const ThemedGlassView = withUniwind(GlassView); | ||
| 17 | |||
| 18 | interface GlassSurfaceProps extends ViewProps { | ||
| 15 | readonly children: ReactNode; | 19 | readonly children: ReactNode; |
| 16 | readonly glassEffectStyle?: "clear" | "regular" | "none"; | 20 | readonly glassEffectStyle?: "clear" | "regular" | "none"; |
| 17 | readonly tintColor?: ColorValue; | 21 | readonly tintColor?: ColorValue; |
| 22 | readonly tintColorClassName?: string; | ||
| 18 | readonly chrome?: "default" | "none"; | 23 | readonly chrome?: "default" | "none"; |
| 19 | /** Styling used only when native Liquid Glass is unavailable. */ | 24 | /** Styling used only when native Liquid Glass is unavailable. */ |
| 20 | readonly fallbackStyle?: StyleProp<ViewStyle>; | 25 | readonly fallbackStyle?: StyleProp<ViewStyle>; |
| 26 | /** Uniwind styling used only when native Liquid Glass is unavailable. */ | ||
| 27 | readonly fallbackClassName?: string; | ||
| 21 | } | 28 | } |
| 22 | 29 | ||
| 23 | export function GlassSurface({ | 30 | export function GlassSurface({ |
| ... | @@ -25,22 +32,18 @@ export function GlassSurface({ | ... | @@ -25,22 +32,18 @@ export function GlassSurface({ |
| 25 | glassEffectStyle = "regular", | 32 | glassEffectStyle = "regular", |
| 26 | chrome = "default", | 33 | chrome = "default", |
| 27 | tintColor, | 34 | tintColor, |
| 35 | tintColorClassName, | ||
| 28 | fallbackStyle, | 36 | fallbackStyle, |
| 37 | fallbackClassName, | ||
| 38 | className, | ||
| 29 | style, | 39 | style, |
| 30 | ...props | 40 | ...props |
| 31 | }: GlassSurfaceProps) { | 41 | }: GlassSurfaceProps) { |
| 32 | const { themeAppearance } = useAppearancePreferences(); | 42 | const isDarkMode = useColorScheme() === "dark"; |
| 33 | const isDarkMode = themeAppearance === "dark"; | ||
| 34 | const borderColor = useThemeColor("--color-border"); | ||
| 35 | const glassSurface = useThemeColor("--color-glass-surface"); | ||
| 36 | const glassTint = useThemeColor("--color-glass-tint"); | ||
| 37 | const supportsGlass = Platform.OS === "ios" && isGlassEffectAPIAvailable(); | 43 | const supportsGlass = Platform.OS === "ios" && isGlassEffectAPIAvailable(); |
| 38 | const surfaceStyle: ViewStyle = { | 44 | const surfaceStyle: ViewStyle = { |
| 39 | borderRadius: 32, | 45 | borderRadius: 32, |
| 40 | overflow: "hidden", | 46 | overflow: "hidden", |
| 41 | borderWidth: chrome === "none" ? 0 : 1, | ||
| 42 | borderColor: chrome === "none" ? "transparent" : borderColor, | ||
| 43 | backgroundColor: chrome === "none" ? "transparent" : glassSurface, | ||
| 44 | shadowColor: chrome === "none" ? "transparent" : "#000000", | 47 | shadowColor: chrome === "none" ? "transparent" : "#000000", |
| 45 | shadowOpacity: chrome === "none" ? 0 : isDarkMode ? 0.22 : 0.08, | 48 | shadowOpacity: chrome === "none" ? 0 : isDarkMode ? 0.22 : 0.08, |
| 46 | shadowRadius: chrome === "none" ? 0 : 28, | 49 | shadowRadius: chrome === "none" ? 0 : 28, |
| ... | @@ -59,20 +62,39 @@ export function GlassSurface({ | ... | @@ -59,20 +62,39 @@ export function GlassSurface({ |
| 59 | 62 | ||
| 60 | if (supportsGlass) { | 63 | if (supportsGlass) { |
| 61 | return ( | 64 | return ( |
| 62 | <GlassView | 65 | <ThemedGlassView |
| 63 | {...props} | 66 | {...props} |
| 67 | className={cn( | ||
| 68 | chrome === "none" | ||
| 69 | ? "border-0 border-transparent bg-transparent" | ||
| 70 | : "border border-border bg-glass-surface", | ||
| 71 | className, | ||
| 72 | )} | ||
| 64 | glassEffectStyle={glassEffectStyle} | 73 | glassEffectStyle={glassEffectStyle} |
| 65 | tintColor={String(tintColor ?? glassTint)} | 74 | tintColor={tintColor === undefined ? undefined : String(tintColor)} |
| 75 | tintColorClassName={ | ||
| 76 | tintColorClassName ?? (tintColor === undefined ? "accent-glass-tint" : undefined) | ||
| 77 | } | ||
| 66 | colorScheme={isDarkMode ? "dark" : "light"} | 78 | colorScheme={isDarkMode ? "dark" : "light"} |
| 67 | style={[surfaceStyle, style]} | 79 | style={[surfaceStyle, style]} |
| 68 | > | 80 | > |
| 69 | {children} | 81 | {children} |
| 70 | </GlassView> | 82 | </ThemedGlassView> |
| 71 | ); | 83 | ); |
| 72 | } | 84 | } |
| 73 | 85 | ||
| 74 | return ( | 86 | return ( |
| 75 | <View {...props} style={[surfaceStyle, fallbackStyle, style]}> | 87 | <View |
| 88 | {...props} | ||
| 89 | className={cn( | ||
| 90 | chrome === "none" | ||
| 91 | ? "border-0 border-transparent bg-transparent" | ||
| 92 | : "border border-border bg-glass-surface", | ||
| 93 | fallbackClassName, | ||
| 94 | className, | ||
| 95 | )} | ||
| 96 | style={[surfaceStyle, fallbackStyle, style]} | ||
| 97 | > | ||
| 76 | {children} | 98 | {children} |
| 77 | </View> | 99 | </View> |
| 78 | ); | 100 | ); |
apps/mobile/src/components/LoadingScreen.tsx+1-7| ... | @@ -1,6 +1,5 @@ | ... | @@ -1,6 +1,5 @@ |
| 1 | import { ActivityIndicator, StatusBar, View } from "react-native"; | 1 | import { ActivityIndicator, StatusBar, View } from "react-native"; |
| 2 | import { useSafeAreaInsets } from "react-native-safe-area-context"; | 2 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 3 | import { useThemeColor } from "../lib/useThemeColor"; | ||
| 4 | import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; | 3 | import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; |
| 5 | 4 | ||
| 6 | import { AppText as Text } from "./AppText"; | 5 | import { AppText as Text } from "./AppText"; |
| ... | @@ -11,17 +10,12 @@ export function LoadingScreen(props: { | ... | @@ -11,17 +10,12 @@ export function LoadingScreen(props: { |
| 11 | readonly messagePlacement?: "above-spinner" | "below-spinner"; | 10 | readonly messagePlacement?: "above-spinner" | "below-spinner"; |
| 12 | }) { | 11 | }) { |
| 13 | const { themeAppearance: colorScheme } = useAppearancePreferences(); | 12 | const { themeAppearance: colorScheme } = useAppearancePreferences(); |
| 14 | const screenBg = useThemeColor("--color-screen"); | ||
| 15 | const insets = useSafeAreaInsets(); | 13 | const insets = useSafeAreaInsets(); |
| 16 | const messagePlacement = props.messagePlacement ?? "below-spinner"; | 14 | const messagePlacement = props.messagePlacement ?? "below-spinner"; |
| 17 | 15 | ||
| 18 | return ( | 16 | return ( |
| 19 | <View className="flex-1 bg-screen" style={{ paddingTop: insets.top }}> | 17 | <View className="flex-1 bg-screen" style={{ paddingTop: insets.top }}> |
| 20 | <StatusBar | 18 | <StatusBar barStyle={colorScheme === "dark" ? "light-content" : "dark-content"} translucent /> |
| 21 | barStyle={colorScheme === "dark" ? "light-content" : "dark-content"} | ||
| 22 | backgroundColor={screenBg as string} | ||
| 23 | translucent | ||
| 24 | /> | ||
| 25 | <View className="flex-1 items-center justify-center gap-5 px-6"> | 19 | <View className="flex-1 items-center justify-center gap-5 px-6"> |
| 26 | <BrandMark compact /> | 20 | <BrandMark compact /> |
| 27 | {messagePlacement === "above-spinner" ? ( | 21 | {messagePlacement === "above-spinner" ? ( |
apps/mobile/src/components/PierreEntryIcon.tsx+8-3| ... | @@ -3,7 +3,6 @@ import { Image, type ImageStyle, type StyleProp } from "react-native"; | ... | @@ -3,7 +3,6 @@ import { Image, type ImageStyle, type StyleProp } from "react-native"; |
| 3 | 3 | ||
| 4 | import { markdownFileIconSource } from "@t3tools/mobile-markdown-text/file-icons"; | 4 | import { markdownFileIconSource } from "@t3tools/mobile-markdown-text/file-icons"; |
| 5 | import { resolveMarkdownFileIcon } from "@t3tools/mobile-markdown-text/links"; | 5 | import { resolveMarkdownFileIcon } from "@t3tools/mobile-markdown-text/links"; |
| 6 | import { useThemeColor } from "../lib/useThemeColor"; | ||
| 7 | 6 | ||
| 8 | export function PierreEntryIcon(props: { | 7 | export function PierreEntryIcon(props: { |
| 9 | readonly path: string; | 8 | readonly path: string; |
| ... | @@ -12,9 +11,15 @@ export function PierreEntryIcon(props: { | ... | @@ -12,9 +11,15 @@ export function PierreEntryIcon(props: { |
| 12 | readonly style?: StyleProp<ImageStyle>; | 11 | readonly style?: StyleProp<ImageStyle>; |
| 13 | }) { | 12 | }) { |
| 14 | const size = props.size ?? 16; | 13 | const size = props.size ?? 16; |
| 15 | const folderColor = useThemeColor("--color-icon-subtle"); | ||
| 16 | if (props.kind === "directory") { | 14 | if (props.kind === "directory") { |
| 17 | return <SymbolView name="folder" size={size} tintColor={folderColor} type="monochrome" />; | 15 | return ( |
| 16 | <SymbolView | ||
| 17 | name="folder" | ||
| 18 | size={size} | ||
| 19 | tintColorClassName={"accent-icon-subtle"} | ||
| 20 | type="monochrome" | ||
| 21 | /> | ||
| 22 | ); | ||
| 18 | } | 23 | } |
| 19 | 24 | ||
| 20 | return ( | 25 | return ( |
apps/mobile/src/components/ProjectFavicon.tsx+1-3| ... | @@ -7,7 +7,6 @@ import { | ... | @@ -7,7 +7,6 @@ import { |
| 7 | getProjectFaviconCacheKey, | 7 | getProjectFaviconCacheKey, |
| 8 | isProjectFaviconFallbackUrl, | 8 | isProjectFaviconFallbackUrl, |
| 9 | } from "@t3tools/shared/projectFavicon"; | 9 | } from "@t3tools/shared/projectFavicon"; |
| 10 | import { useThemeColor } from "../lib/useThemeColor"; | ||
| 11 | import { useAssetUrl } from "../state/assets"; | 10 | import { useAssetUrl } from "../state/assets"; |
| 12 | import { | 11 | import { |
| 13 | beginProjectFaviconRequest, | 12 | beginProjectFaviconRequest, |
| ... | @@ -62,7 +61,6 @@ function ProjectFaviconImage(props: { | ... | @@ -62,7 +61,6 @@ function ProjectFaviconImage(props: { |
| 62 | readonly projectTitle: string; | 61 | readonly projectTitle: string; |
| 63 | readonly size: number; | 62 | readonly size: number; |
| 64 | }) { | 63 | }) { |
| 65 | const iconMuted = useThemeColor("--color-icon-subtle"); | ||
| 66 | const faviconRequest = useMemo( | 64 | const faviconRequest = useMemo( |
| 67 | () => createProjectFaviconRequest(props.cacheKey, props.faviconUrl), | 65 | () => createProjectFaviconRequest(props.cacheKey, props.faviconUrl), |
| 68 | [props.cacheKey, props.faviconUrl], | 66 | [props.cacheKey, props.faviconUrl], |
| ... | @@ -97,7 +95,7 @@ function ProjectFaviconImage(props: { | ... | @@ -97,7 +95,7 @@ function ProjectFaviconImage(props: { |
| 97 | <SymbolView | 95 | <SymbolView |
| 98 | name={{ ios: "folder.fill", android: props.open ? "folder_open" : "folder" }} | 96 | name={{ ios: "folder.fill", android: props.open ? "folder_open" : "folder" }} |
| 99 | size={props.size * 0.78} | 97 | size={props.size * 0.78} |
| 100 | tintColor={iconMuted} | 98 | tintColorClassName={"accent-icon-subtle"} |
| 101 | type="monochrome" | 99 | type="monochrome" |
| 102 | /> | 100 | /> |
| 103 | ) : null} | 101 | ) : null} |
apps/mobile/src/components/SourceControlIcon.tsx+14-3| ... | @@ -1,4 +1,7 @@ | ... | @@ -1,4 +1,7 @@ |
| 1 | import Svg, { Defs, LinearGradient, Path, Stop } from "react-native-svg"; | 1 | import Svg, { Defs, LinearGradient, Path, Stop } from "react-native-svg"; |
| 2 | import { withUniwind } from "uniwind"; | ||
| 3 | |||
| 4 | const ThemedSvg = withUniwind(Svg); | ||
| 2 | 5 | ||
| 3 | export type SourceControlIconKind = "github" | "gitlab" | "bitbucket" | "azure-devops"; | 6 | export type SourceControlIconKind = "github" | "gitlab" | "bitbucket" | "azure-devops"; |
| 4 | 7 | ||
| ... | @@ -6,20 +9,28 @@ export function SourceControlIcon(props: { | ... | @@ -6,20 +9,28 @@ export function SourceControlIcon(props: { |
| 6 | readonly kind: SourceControlIconKind; | 9 | readonly kind: SourceControlIconKind; |
| 7 | readonly size?: number; | 10 | readonly size?: number; |
| 8 | readonly color?: string; | 11 | readonly color?: string; |
| 12 | readonly colorClassName?: string; | ||
| 9 | }) { | 13 | }) { |
| 10 | const size = props.size ?? 18; | 14 | const size = props.size ?? 18; |
| 11 | 15 | ||
| 12 | switch (props.kind) { | 16 | switch (props.kind) { |
| 13 | case "github": | 17 | case "github": |
| 14 | return ( | 18 | return ( |
| 15 | <Svg width={size} height={size} viewBox="0 0 16 16" fill="none"> | 19 | <ThemedSvg |
| 20 | width={size} | ||
| 21 | height={size} | ||
| 22 | viewBox="0 0 16 16" | ||
| 23 | color={props.color} | ||
| 24 | colorClassName={props.colorClassName} | ||
| 25 | fill="none" | ||
| 26 | > | ||
| 16 | <Path | 27 | <Path |
| 17 | fillRule="evenodd" | 28 | fillRule="evenodd" |
| 18 | clipRule="evenodd" | 29 | clipRule="evenodd" |
| 19 | d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82A7.68 7.68 0 0 1 8.02 3.86c.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z" | 30 | d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82A7.68 7.68 0 0 1 8.02 3.86c.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z" |
| 20 | fill={props.color ?? "#24292F"} | 31 | fill="currentColor" |
| 21 | /> | 32 | /> |
| 22 | </Svg> | 33 | </ThemedSvg> |
| 23 | ); | 34 | ); |
| 24 | case "gitlab": | 35 | case "gitlab": |
| 25 | return ( | 36 | return ( |
apps/mobile/src/components/T3Wordmark.tsx+12-3| ... | @@ -1,11 +1,18 @@ | ... | @@ -1,11 +1,18 @@ |
| 1 | import type { ColorValue } from "react-native"; | 1 | import type { ColorValue } from "react-native"; |
| 2 | import Svg, { Path } from "react-native-svg"; | 2 | import Svg, { Path } from "react-native-svg"; |
| 3 | import { withUniwind } from "uniwind"; | ||
| 4 | |||
| 5 | const ThemedPath = withUniwind(Path); | ||
| 3 | 6 | ||
| 4 | /** | 7 | /** |
| 5 | * The "T3" brand mark, matching the desktop sidebar's T3Wordmark SVG | 8 | * The "T3" brand mark, matching the desktop sidebar's T3Wordmark SVG |
| 6 | * (apps/web Sidebar.tsx). Width derives from the viewBox aspect ratio. | 9 | * (apps/web Sidebar.tsx). Width derives from the viewBox aspect ratio. |
| 7 | */ | 10 | */ |
| 8 | export function T3Wordmark(props: { readonly height: number; readonly color: ColorValue }) { | 11 | export function T3Wordmark(props: { |
| 12 | readonly height: number; | ||
| 13 | readonly color?: ColorValue; | ||
| 14 | readonly colorClassName?: string; | ||
| 15 | }) { | ||
| 9 | const aspectRatio = 94.3941 / 56.96; | 16 | const aspectRatio = 94.3941 / 56.96; |
| 10 | return ( | 17 | return ( |
| 11 | <Svg | 18 | <Svg |
| ... | @@ -14,9 +21,11 @@ export function T3Wordmark(props: { readonly height: number; readonly color: Col | ... | @@ -14,9 +21,11 @@ export function T3Wordmark(props: { readonly height: number; readonly color: Col |
| 14 | width={props.height * aspectRatio} | 21 | width={props.height * aspectRatio} |
| 15 | viewBox="15.5309 37 94.3941 56.96" | 22 | viewBox="15.5309 37 94.3941 56.96" |
| 16 | > | 23 | > |
| 17 | <Path | 24 | <ThemedPath |
| 18 | d="M33.4509 93V47.56H15.5309V37H64.3309V47.56H46.4109V93H33.4509ZM86.7253 93.96C82.832 93.96 78.9653 93.4533 75.1253 92.44C71.2853 91.3733 68.032 89.88 65.3653 87.96L70.4053 78.04C72.5386 79.5867 75.0186 80.8133 77.8453 81.72C80.672 82.6267 83.5253 83.08 86.4053 83.08C89.6586 83.08 92.2186 82.44 94.0853 81.16C95.952 79.88 96.8853 78.12 96.8853 75.88C96.8853 73.7467 96.0586 72.0667 94.4053 70.84C92.752 69.6133 90.0853 69 86.4053 69H80.4853V60.44L96.0853 42.76L97.5253 47.4H68.1653V37H107.365V45.4L91.8453 63.08L85.2853 59.32H89.0453C95.9253 59.32 101.125 60.8667 104.645 63.96C108.165 67.0533 109.925 71.0267 109.925 75.88C109.925 79.0267 109.099 81.9867 107.445 84.76C105.792 87.48 103.259 89.6933 99.8453 91.4C96.432 93.1067 92.0586 93.96 86.7253 93.96Z" | 25 | d="M33.4509 93V47.56H15.5309V37H64.3309V47.56H46.4109V93H33.4509ZM86.7253 93.96C82.832 93.96 78.9653 93.4533 75.1253 92.44C71.2853 91.3733 68.032 89.88 65.3653 87.96L70.4053 78.04C72.5386 79.5867 75.0186 80.8133 77.8453 81.72C80.672 82.6267 83.5253 83.08 86.4053 83.08C89.6586 83.08 92.2186 82.44 94.0853 81.16C95.952 79.88 96.8853 78.12 96.8853 75.88C96.8853 73.7467 96.0586 72.0667 94.4053 70.84C92.752 69.6133 90.0853 69 86.4053 69H80.4853V60.44L96.0853 42.76L97.5253 47.4H68.1653V37H107.365V45.4L91.8453 63.08L85.2853 59.32H89.0453C95.9253 59.32 101.125 60.8667 104.645 63.96C108.165 67.0533 109.925 71.0267 109.925 75.88C109.925 79.0267 109.099 81.9867 107.445 84.76C105.792 87.48 103.259 89.6933 99.8453 91.4C96.432 93.1067 92.0586 93.96 86.7253 93.96Z" |
| 19 | fill={props.color} | 26 | color={props.color} |
| 27 | colorClassName={props.colorClassName} | ||
| 28 | fill="currentColor" | ||
| 20 | /> | 29 | /> |
| 21 | </Svg> | 30 | </Svg> |
| 22 | ); | 31 | ); |
apps/mobile/src/components/ThemedSwitch.tsx+9-11| ... | @@ -1,21 +1,19 @@ | ... | @@ -1,21 +1,19 @@ |
| 1 | import { Platform, Switch, type SwitchProps } from "react-native"; | 1 | import { Platform, Switch, type SwitchProps } from "react-native"; |
| 2 | 2 | ||
| 3 | import { useThemeColor } from "../lib/useThemeColor"; | ||
| 4 | |||
| 5 | export function ThemedSwitch(props: SwitchProps) { | 3 | export function ThemedSwitch(props: SwitchProps) { |
| 6 | const activeTrack = String(useThemeColor("--color-switch-active-track")); | ||
| 7 | const inactiveTrack = String(useThemeColor("--color-switch-inactive-track")); | ||
| 8 | const activeThumb = String(useThemeColor("--color-switch-active-thumb")); | ||
| 9 | const inactiveThumb = String(useThemeColor("--color-switch-inactive-thumb")); | ||
| 10 | |||
| 11 | return ( | 4 | return ( |
| 12 | <Switch | 5 | <Switch |
| 13 | {...props} | 6 | {...props} |
| 14 | ios_backgroundColor={inactiveTrack} | 7 | ios_backgroundColorClassName="accent-switch-inactive-track" |
| 15 | thumbColor={ | 8 | thumbColorClassName={ |
| 16 | Platform.OS === "android" ? (props.value ? activeThumb : inactiveThumb) : undefined | 9 | Platform.OS === "android" |
| 10 | ? props.value | ||
| 11 | ? "accent-switch-active-thumb" | ||
| 12 | : "accent-switch-inactive-thumb" | ||
| 13 | : undefined | ||
| 17 | } | 14 | } |
| 18 | trackColor={{ false: inactiveTrack, true: activeTrack }} | 15 | trackColorOffClassName="accent-switch-inactive-track" |
| 16 | trackColorOnClassName="accent-switch-active-track" | ||
| 19 | /> | 17 | /> |
| 20 | ); | 18 | ); |
| 21 | } | 19 | } |
apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx+16-20| ... | @@ -27,7 +27,7 @@ import { EmptyState } from "../../components/EmptyState"; | ... | @@ -27,7 +27,7 @@ import { EmptyState } from "../../components/EmptyState"; |
| 27 | import { ProjectFavicon } from "../../components/ProjectFavicon"; | 27 | import { ProjectFavicon } from "../../components/ProjectFavicon"; |
| 28 | import { useSafeAreaInsets } from "react-native-safe-area-context"; | 28 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 29 | import { relativeTime } from "../../lib/time"; | 29 | import { relativeTime } from "../../lib/time"; |
| 30 | import { useThemeColor } from "../../lib/useThemeColor"; | 30 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 31 | import { ThreadSwipeable } from "../home/thread-swipe-actions"; | 31 | import { ThreadSwipeable } from "../home/thread-swipe-actions"; |
| 32 | import { | 32 | import { |
| 33 | createNativeMailSearchToolbarItem, | 33 | createNativeMailSearchToolbarItem, |
| ... | @@ -70,8 +70,6 @@ function ArchivedThreadsHeader(props: { | ... | @@ -70,8 +70,6 @@ function ArchivedThreadsHeader(props: { |
| 70 | const navigation = useNavigation(); | 70 | const navigation = useNavigation(); |
| 71 | const insets = useSafeAreaInsets(); | 71 | const insets = useSafeAreaInsets(); |
| 72 | const hasCustomFilter = props.selectedEnvironmentId !== null || props.sortOrder !== "newest"; | 72 | const hasCustomFilter = props.selectedEnvironmentId !== null || props.sortOrder !== "newest"; |
| 73 | const searchIconColor = useThemeColor("--color-icon"); | ||
| 74 | const searchTextColor = useThemeColor("--color-foreground"); | ||
| 75 | const usesNativeChrome = Platform.OS === "ios"; | 73 | const usesNativeChrome = Platform.OS === "ios"; |
| 76 | const usesCompactMailToolbar = | 74 | const usesCompactMailToolbar = |
| 77 | Platform.OS === "ios" && width < 700 && NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED; | 75 | Platform.OS === "ios" && width < 700 && NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED; |
| ... | @@ -154,7 +152,7 @@ function ArchivedThreadsHeader(props: { | ... | @@ -154,7 +152,7 @@ function ArchivedThreadsHeader(props: { |
| 154 | <SymbolView | 152 | <SymbolView |
| 155 | name="chevron.left" | 153 | name="chevron.left" |
| 156 | size={24} | 154 | size={24} |
| 157 | tintColor={searchTextColor} | 155 | tintColorClassName={"accent-foreground"} |
| 158 | type="monochrome" | 156 | type="monochrome" |
| 159 | /> | 157 | /> |
| 160 | </Pressable> | 158 | </Pressable> |
| ... | @@ -162,7 +160,7 @@ function ArchivedThreadsHeader(props: { | ... | @@ -162,7 +160,7 @@ function ArchivedThreadsHeader(props: { |
| 162 | <SymbolView | 160 | <SymbolView |
| 163 | name="magnifyingglass" | 161 | name="magnifyingglass" |
| 164 | size={17} | 162 | size={17} |
| 165 | tintColor={searchIconColor} | 163 | tintColorClassName={"accent-icon"} |
| 166 | type="monochrome" | 164 | type="monochrome" |
| 167 | /> | 165 | /> |
| 168 | <TextInput | 166 | <TextInput |
| ... | @@ -192,7 +190,7 @@ function ArchivedThreadsHeader(props: { | ... | @@ -192,7 +190,7 @@ function ArchivedThreadsHeader(props: { |
| 192 | : "line.3.horizontal.decrease.circle" | 190 | : "line.3.horizontal.decrease.circle" |
| 193 | } | 191 | } |
| 194 | size={16} | 192 | size={16} |
| 195 | tintColor={searchIconColor} | 193 | tintColorClassName={"accent-icon"} |
| 196 | type="monochrome" | 194 | type="monochrome" |
| 197 | /> | 195 | /> |
| 198 | </Pressable> | 196 | </Pressable> |
| ... | @@ -402,9 +400,7 @@ function ArchivedThreadRow(props: { | ... | @@ -402,9 +400,7 @@ function ArchivedThreadRow(props: { |
| 402 | readonly thread: EnvironmentThreadShell; | 400 | readonly thread: EnvironmentThreadShell; |
| 403 | }) { | 401 | }) { |
| 404 | const { width: windowWidth } = useWindowDimensions(); | 402 | const { width: windowWidth } = useWindowDimensions(); |
| 405 | const cardColor = useThemeColor("--color-card"); | 403 | const cardColor = useUniwindTheme()["--color-card"]; |
| 406 | const iconColor = useThemeColor("--color-icon-subtle"); | ||
| 407 | const separatorColor = useThemeColor("--color-separator"); | ||
| 408 | const timestamp = relativeTime(props.thread.archivedAt ?? props.thread.updatedAt); | 404 | const timestamp = relativeTime(props.thread.archivedAt ?? props.thread.updatedAt); |
| 409 | const subtitle = [props.environmentLabel, props.thread.branch].filter((part): part is string => | 405 | const subtitle = [props.environmentLabel, props.thread.branch].filter((part): part is string => |
| 410 | Boolean(part), | 406 | Boolean(part), |
| ... | @@ -436,14 +432,15 @@ function ArchivedThreadRow(props: { | ... | @@ -436,14 +432,15 @@ function ArchivedThreadRow(props: { |
| 436 | > | 432 | > |
| 437 | {() => ( | 433 | {() => ( |
| 438 | <View | 434 | <View |
| 439 | className="flex-row items-center gap-3 bg-card px-4 py-3" | 435 | className={`flex-row items-center gap-3 bg-card px-4 py-3 ${props.isLast ? "" : "border-b border-separator"}`} |
| 440 | style={{ | ||
| 441 | borderBottomColor: separatorColor, | ||
| 442 | borderBottomWidth: props.isLast ? 0 : 1, | ||
| 443 | }} | ||
| 444 | > | 436 | > |
| 445 | <View className="h-[34px] w-[34px] items-center justify-center rounded-[11px] bg-subtle"> | 437 | <View className="h-[34px] w-[34px] items-center justify-center rounded-[11px] bg-subtle"> |
| 446 | <SymbolView name="archivebox.fill" size={15} tintColor={iconColor} type="monochrome" /> | 438 | <SymbolView |
| 439 | name="archivebox.fill" | ||
| 440 | size={15} | ||
| 441 | tintColorClassName={"accent-icon-subtle"} | ||
| 442 | type="monochrome" | ||
| 443 | /> | ||
| 447 | </View> | 444 | </View> |
| 448 | 445 | ||
| 449 | <View className="min-w-0 flex-1 gap-1"> | 446 | <View className="min-w-0 flex-1 gap-1"> |
| ... | @@ -463,7 +460,7 @@ function ArchivedThreadRow(props: { | ... | @@ -463,7 +460,7 @@ function ArchivedThreadRow(props: { |
| 463 | <SymbolView | 460 | <SymbolView |
| 464 | name="arrow.triangle.branch" | 461 | name="arrow.triangle.branch" |
| 465 | size={10} | 462 | size={10} |
| 466 | tintColor={iconColor} | 463 | tintColorClassName={"accent-icon-subtle"} |
| 467 | type="monochrome" | 464 | type="monochrome" |
| 468 | /> | 465 | /> |
| 469 | <Text | 466 | <Text |
| ... | @@ -513,7 +510,6 @@ export function ArchivedThreadsScreen(props: { | ... | @@ -513,7 +510,6 @@ export function ArchivedThreadsScreen(props: { |
| 513 | const { onDeleteThread, onUnarchiveThread } = props; | 510 | const { onDeleteThread, onUnarchiveThread } = props; |
| 514 | const openSwipeableRef = useRef<SwipeableMethods | null>(null); | 511 | const openSwipeableRef = useRef<SwipeableMethods | null>(null); |
| 515 | const archiveScrollGesture = useMemo(() => Gesture.Native(), []); | 512 | const archiveScrollGesture = useMemo(() => Gesture.Native(), []); |
| 516 | const refreshTint = useThemeColor("--color-icon"); | ||
| 517 | const environmentLabelsById = useMemo( | 513 | const environmentLabelsById = useMemo( |
| 518 | () => | 514 | () => |
| 519 | new Map( | 515 | new Map( |
| ... | @@ -594,7 +590,7 @@ export function ArchivedThreadsScreen(props: { | ... | @@ -594,7 +590,7 @@ export function ArchivedThreadsScreen(props: { |
| 594 | if (isInitialLoad) { | 590 | if (isInitialLoad) { |
| 595 | return ( | 591 | return ( |
| 596 | <View className="items-center py-16"> | 592 | <View className="items-center py-16"> |
| 597 | <ActivityIndicator color={refreshTint} /> | 593 | <ActivityIndicator colorClassName={"accent-icon"} /> |
| 598 | <Text className="mt-3 text-sm text-foreground-muted">Loading archive...</Text> | 594 | <Text className="mt-3 text-sm text-foreground-muted">Loading archive...</Text> |
| 599 | </View> | 595 | </View> |
| 600 | ); | 596 | ); |
| ... | @@ -610,7 +606,7 @@ export function ArchivedThreadsScreen(props: { | ... | @@ -610,7 +606,7 @@ export function ArchivedThreadsScreen(props: { |
| 610 | title={isFiltered ? "No matching threads" : "No archived threads"} | 606 | title={isFiltered ? "No matching threads" : "No archived threads"} |
| 611 | /> | 607 | /> |
| 612 | ); | 608 | ); |
| 613 | }, [isFiltered, isInitialLoad, refreshTint]); | 609 | }, [isFiltered, isInitialLoad]); |
| 614 | 610 | ||
| 615 | return ( | 611 | return ( |
| 616 | <View className="flex-1 bg-sheet"> | 612 | <View className="flex-1 bg-sheet"> |
| ... | @@ -649,7 +645,7 @@ export function ArchivedThreadsScreen(props: { | ... | @@ -649,7 +645,7 @@ export function ArchivedThreadsScreen(props: { |
| 649 | <RefreshControl | 645 | <RefreshControl |
| 650 | onRefresh={props.onRefresh} | 646 | onRefresh={props.onRefresh} |
| 651 | refreshing={props.isLoading && !isInitialLoad} | 647 | refreshing={props.isLoading && !isInitialLoad} |
| 652 | tintColor={String(refreshTint)} | 648 | tintColorClassName={String("accent-icon")} |
| 653 | /> | 649 | /> |
| 654 | } | 650 | } |
| 655 | renderItem={renderListItem} | 651 | renderItem={renderListItem} |
apps/mobile/src/features/connection/CloudEnvironmentRows.tsx+11-11| ... | @@ -18,7 +18,6 @@ import { AppText as Text } from "../../components/AppText"; | ... | @@ -18,7 +18,6 @@ import { AppText as Text } from "../../components/AppText"; |
| 18 | import { ThemedSwitch } from "../../components/ThemedSwitch"; | 18 | import { ThemedSwitch } from "../../components/ThemedSwitch"; |
| 19 | import { cn } from "../../lib/cn"; | 19 | import { cn } from "../../lib/cn"; |
| 20 | import { copyTextWithHaptic } from "../../lib/copyTextWithHaptic"; | 20 | import { copyTextWithHaptic } from "../../lib/copyTextWithHaptic"; |
| 21 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 22 | import type { ConnectedEnvironmentSummary } from "../../state/remote-runtime-types"; | 21 | import type { ConnectedEnvironmentSummary } from "../../state/remote-runtime-types"; |
| 23 | import { availableCloudEnvironmentPresentation } from "../cloud/cloudEnvironmentPresentation"; | 22 | import { availableCloudEnvironmentPresentation } from "../cloud/cloudEnvironmentPresentation"; |
| 24 | import { hasCloudPublicConfig } from "../cloud/publicConfig"; | 23 | import { hasCloudPublicConfig } from "../cloud/publicConfig"; |
| ... | @@ -78,7 +77,6 @@ function CloudEnvironmentRowsContent( | ... | @@ -78,7 +77,6 @@ function CloudEnvironmentRowsContent( |
| 78 | props: CloudEnvironmentRowsProps & { readonly discoveryAvailable?: boolean }, | 77 | props: CloudEnvironmentRowsProps & { readonly discoveryAvailable?: boolean }, |
| 79 | ) { | 78 | ) { |
| 80 | const controller = useConnectionController(); | 79 | const controller = useConnectionController(); |
| 81 | const iconColor = useThemeColor("--color-icon"); | ||
| 82 | const discoveryAvailable = props.discoveryAvailable ?? true; | 80 | const discoveryAvailable = props.discoveryAvailable ?? true; |
| 83 | const availableCloudEnvironments = discoveryAvailable | 81 | const availableCloudEnvironments = discoveryAvailable |
| 84 | ? (props.showcaseAvailableEnvironments ?? controller.availableRelayEnvironments) | 82 | ? (props.showcaseAvailableEnvironments ?? controller.availableRelayEnvironments) |
| ... | @@ -118,12 +116,12 @@ function CloudEnvironmentRowsContent( | ... | @@ -118,12 +116,12 @@ function CloudEnvironmentRowsContent( |
| 118 | className="h-9 w-9 items-center justify-center rounded-full bg-subtle active:opacity-70 disabled:opacity-50" | 116 | className="h-9 w-9 items-center justify-center rounded-full bg-subtle active:opacity-70 disabled:opacity-50" |
| 119 | > | 117 | > |
| 120 | {controller.relayDiscovery.isRefreshing ? ( | 118 | {controller.relayDiscovery.isRefreshing ? ( |
| 121 | <ActivityIndicator color={iconColor} size="small" /> | 119 | <ActivityIndicator colorClassName={"accent-icon"} size="small" /> |
| 122 | ) : ( | 120 | ) : ( |
| 123 | <SymbolView | 121 | <SymbolView |
| 124 | name="arrow.clockwise" | 122 | name="arrow.clockwise" |
| 125 | size={14} | 123 | size={14} |
| 126 | tintColor={iconColor} | 124 | tintColorClassName={"accent-icon"} |
| 127 | type="monochrome" | 125 | type="monochrome" |
| 128 | /> | 126 | /> |
| 129 | )} | 127 | )} |
| ... | @@ -158,7 +156,7 @@ function CloudEnvironmentRowsContent( | ... | @@ -158,7 +156,7 @@ function CloudEnvironmentRowsContent( |
| 158 | </View> | 156 | </View> |
| 159 | ) : controller.relayDiscovery.isRefreshing ? ( | 157 | ) : controller.relayDiscovery.isRefreshing ? ( |
| 160 | <View collapsable={false} className="items-center gap-3 rounded-[24px] bg-card p-6"> | 158 | <View collapsable={false} className="items-center gap-3 rounded-[24px] bg-card p-6"> |
| 161 | <ActivityIndicator color={iconColor} /> | 159 | <ActivityIndicator colorClassName={"accent-icon"} /> |
| 162 | <Text className="text-center text-sm leading-normal text-foreground-muted"> | 160 | <Text className="text-center text-sm leading-normal text-foreground-muted"> |
| 163 | Loading linked cloud environments. | 161 | Loading linked cloud environments. |
| 164 | </Text> | 162 | </Text> |
| ... | @@ -275,7 +273,6 @@ function CloudEnvironmentRowShell(props: { | ... | @@ -275,7 +273,6 @@ function CloudEnvironmentRowShell(props: { |
| 275 | readonly statusText?: string; | 273 | readonly statusText?: string; |
| 276 | readonly value: boolean; | 274 | readonly value: boolean; |
| 277 | }) { | 275 | }) { |
| 278 | const chevron = useThemeColor("--color-chevron"); | ||
| 279 | const isRetrying = | 276 | const isRetrying = |
| 280 | props.connectionState === "connecting" || props.connectionState === "reconnecting"; | 277 | props.connectionState === "connecting" || props.connectionState === "reconnecting"; |
| 281 | const shouldPulse = isRetrying; | 278 | const shouldPulse = isRetrying; |
| ... | @@ -287,7 +284,7 @@ function CloudEnvironmentRowShell(props: { | ... | @@ -287,7 +284,7 @@ function CloudEnvironmentRowShell(props: { |
| 287 | traceId: props.connectionErrorTraceId, | 284 | traceId: props.connectionErrorTraceId, |
| 288 | }); | 285 | }); |
| 289 | const statusClassName = props.connectionError | 286 | const statusClassName = props.connectionError |
| 290 | ? "text-rose-500 dark:text-rose-400" | 287 | ? "text-adaptive-rose-500-400" |
| 291 | : "text-foreground-muted"; | 288 | : "text-foreground-muted"; |
| 292 | const [errorMeasurement, setErrorMeasurement] = useState<{ | 289 | const [errorMeasurement, setErrorMeasurement] = useState<{ |
| 293 | readonly text: string; | 290 | readonly text: string; |
| ... | @@ -377,7 +374,7 @@ function CloudEnvironmentRowShell(props: { | ... | @@ -377,7 +374,7 @@ function CloudEnvironmentRowShell(props: { |
| 377 | <SymbolView | 374 | <SymbolView |
| 378 | name="chevron.down" | 375 | name="chevron.down" |
| 379 | size={10} | 376 | size={10} |
| 380 | tintColor={chevron} | 377 | tintColorClassName={"accent-chevron"} |
| 381 | type="monochrome" | 378 | type="monochrome" |
| 382 | style={{ | 379 | style={{ |
| 383 | marginTop: 3, | 380 | marginTop: 3, |
| ... | @@ -397,8 +394,6 @@ function CloudEnvironmentRowShell(props: { | ... | @@ -397,8 +394,6 @@ function CloudEnvironmentRowShell(props: { |
| 397 | } | 394 | } |
| 398 | 395 | ||
| 399 | function CopyTraceIdButton(props: { readonly traceId: string }) { | 396 | function CopyTraceIdButton(props: { readonly traceId: string }) { |
| 400 | const iconColor = useThemeColor("--color-icon"); | ||
| 401 | |||
| 402 | return ( | 397 | return ( |
| 403 | <Pressable | 398 | <Pressable |
| 404 | accessibilityRole="button" | 399 | accessibilityRole="button" |
| ... | @@ -407,7 +402,12 @@ function CopyTraceIdButton(props: { readonly traceId: string }) { | ... | @@ -407,7 +402,12 @@ function CopyTraceIdButton(props: { readonly traceId: string }) { |
| 407 | }} | 402 | }} |
| 408 | className="self-start flex-row items-center gap-1.5 rounded-full bg-subtle px-3 py-2 active:opacity-70" | 403 | className="self-start flex-row items-center gap-1.5 rounded-full bg-subtle px-3 py-2 active:opacity-70" |
| 409 | > | 404 | > |
| 410 | <SymbolView name="doc.on.doc" size={12} tintColor={iconColor} type="monochrome" /> | 405 | <SymbolView |
| 406 | name="doc.on.doc" | ||
| 407 | size={12} | ||
| 408 | tintColorClassName={"accent-icon"} | ||
| 409 | type="monochrome" | ||
| 410 | /> | ||
| 411 | <Text className="text-xs font-t3-bold text-foreground">Copy trace ID</Text> | 411 | <Text className="text-xs font-t3-bold text-foreground">Copy trace ID</Text> |
| 412 | </Pressable> | 412 | </Pressable> |
| 413 | ); | 413 | ); |
apps/mobile/src/features/connection/ConnectionEnvironmentRow.tsx+15-10| ... | @@ -7,7 +7,6 @@ import { AsyncResult } from "effect/unstable/reactivity"; | ... | @@ -7,7 +7,6 @@ import { AsyncResult } from "effect/unstable/reactivity"; |
| 7 | import { useCallback, useState } from "react"; | 7 | import { useCallback, useState } from "react"; |
| 8 | import { Alert, Pressable, View } from "react-native"; | 8 | import { Alert, Pressable, View } from "react-native"; |
| 9 | import Animated, { FadeIn, FadeOut, LinearTransition } from "react-native-reanimated"; | 9 | import Animated, { FadeIn, FadeOut, LinearTransition } from "react-native-reanimated"; |
| 10 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 11 | 10 | ||
| 12 | import { AppText as Text, AppTextInput as TextInput } from "../../components/AppText"; | 11 | import { AppText as Text, AppTextInput as TextInput } from "../../components/AppText"; |
| 13 | import { cn } from "../../lib/cn"; | 12 | import { cn } from "../../lib/cn"; |
| ... | @@ -36,10 +35,6 @@ export function ConnectionEnvironmentRow(props: { | ... | @@ -36,10 +35,6 @@ export function ConnectionEnvironmentRow(props: { |
| 36 | }) { | 35 | }) { |
| 37 | const [label, setLabel] = useState(props.environment.environmentLabel); | 36 | const [label, setLabel] = useState(props.environment.environmentLabel); |
| 38 | const [url, setUrl] = useState(props.environment.displayUrl); | 37 | const [url, setUrl] = useState(props.environment.displayUrl); |
| 39 | |||
| 40 | const mutedColor = useThemeColor("--color-icon-subtle"); | ||
| 41 | const primaryFg = useThemeColor("--color-primary-foreground"); | ||
| 42 | const dangerFg = useThemeColor("--color-danger-foreground"); | ||
| 43 | const statusLabel = connectionStatusLabel(props.environment); | 38 | const statusLabel = connectionStatusLabel(props.environment); |
| 44 | const statusTraceId = props.environment.connectionErrorTraceId; | 39 | const statusTraceId = props.environment.connectionErrorTraceId; |
| 45 | const hasConnectionFailure = props.environment.connectionError !== null; | 40 | const hasConnectionFailure = props.environment.connectionError !== null; |
| ... | @@ -85,7 +80,7 @@ export function ConnectionEnvironmentRow(props: { | ... | @@ -85,7 +80,7 @@ export function ConnectionEnvironmentRow(props: { |
| 85 | <Text | 80 | <Text |
| 86 | className={cn( | 81 | className={cn( |
| 87 | "text-xs", | 82 | "text-xs", |
| 88 | hasConnectionFailure ? "text-rose-500 dark:text-rose-400" : "text-foreground-muted", | 83 | hasConnectionFailure ? "text-adaptive-rose-500-400" : "text-foreground-muted", |
| 89 | )} | 84 | )} |
| 90 | numberOfLines={props.expanded ? undefined : 1} | 85 | numberOfLines={props.expanded ? undefined : 1} |
| 91 | selectable={props.expanded} | 86 | selectable={props.expanded} |
| ... | @@ -117,7 +112,7 @@ export function ConnectionEnvironmentRow(props: { | ... | @@ -117,7 +112,7 @@ export function ConnectionEnvironmentRow(props: { |
| 117 | <SymbolView | 112 | <SymbolView |
| 118 | name="chevron.down" | 113 | name="chevron.down" |
| 119 | size={12} | 114 | size={12} |
| 120 | tintColor={mutedColor} | 115 | tintColorClassName={"accent-icon-subtle"} |
| 121 | type="monochrome" | 116 | type="monochrome" |
| 122 | style={{ | 117 | style={{ |
| 123 | transform: [{ rotate: props.expanded ? "180deg" : "0deg" }], | 118 | transform: [{ rotate: props.expanded ? "180deg" : "0deg" }], |
| ... | @@ -174,7 +169,12 @@ export function ConnectionEnvironmentRow(props: { | ... | @@ -174,7 +169,12 @@ export function ConnectionEnvironmentRow(props: { |
| 174 | className="min-h-[42px] flex-1 flex-row items-center justify-center gap-1.5 rounded-[14px] bg-primary px-3.5 py-2.5 active:opacity-70" | 169 | className="min-h-[42px] flex-1 flex-row items-center justify-center gap-1.5 rounded-[14px] bg-primary px-3.5 py-2.5 active:opacity-70" |
| 175 | onPress={handleSave} | 170 | onPress={handleSave} |
| 176 | > | 171 | > |
| 177 | <SymbolView name="checkmark" size={13} tintColor={primaryFg} type="monochrome" /> | 172 | <SymbolView |
| 173 | name="checkmark" | ||
| 174 | size={13} | ||
| 175 | tintColorClassName={"accent-primary-foreground"} | ||
| 176 | type="monochrome" | ||
| 177 | /> | ||
| 178 | <Text className="text-xs font-t3-bold tracking-[0.8px] uppercase text-primary-foreground"> | 178 | <Text className="text-xs font-t3-bold tracking-[0.8px] uppercase text-primary-foreground"> |
| 179 | Save | 179 | Save |
| 180 | </Text> | 180 | </Text> |
| ... | @@ -188,7 +188,7 @@ export function ConnectionEnvironmentRow(props: { | ... | @@ -188,7 +188,7 @@ export function ConnectionEnvironmentRow(props: { |
| 188 | <SymbolView | 188 | <SymbolView |
| 189 | name="arrow.clockwise" | 189 | name="arrow.clockwise" |
| 190 | size={14} | 190 | size={14} |
| 191 | tintColor={mutedColor} | 191 | tintColorClassName={"accent-icon-subtle"} |
| 192 | type="monochrome" | 192 | type="monochrome" |
| 193 | /> | 193 | /> |
| 194 | </Pressable> | 194 | </Pressable> |
| ... | @@ -197,7 +197,12 @@ export function ConnectionEnvironmentRow(props: { | ... | @@ -197,7 +197,12 @@ export function ConnectionEnvironmentRow(props: { |
| 197 | className="h-[42px] w-[42px] items-center justify-center rounded-[14px] border border-danger-border bg-danger active:opacity-70" | 197 | className="h-[42px] w-[42px] items-center justify-center rounded-[14px] border border-danger-border bg-danger active:opacity-70" |
| 198 | onPress={() => props.onRemove(props.environment.environmentId)} | 198 | onPress={() => props.onRemove(props.environment.environmentId)} |
| 199 | > | 199 | > |
| 200 | <SymbolView name="trash" size={14} tintColor={dangerFg} type="monochrome" /> | 200 | <SymbolView |
| 201 | name="trash" | ||
| 202 | size={14} | ||
| 203 | tintColorClassName={"accent-danger-foreground"} | ||
| 204 | type="monochrome" | ||
| 205 | /> | ||
| 201 | </Pressable> | 206 | </Pressable> |
| 202 | </View> | 207 | </View> |
| 203 | </Animated.View> | 208 | </Animated.View> |
apps/mobile/src/features/connection/ConnectionSheetButton.tsx+7-7| ... | @@ -1,6 +1,5 @@ | ... | @@ -1,6 +1,5 @@ |
| 1 | import { SymbolView } from "../../components/AppSymbol"; | 1 | import { SymbolView } from "../../components/AppSymbol"; |
| 2 | import { Platform, Pressable } from "react-native"; | 2 | import { Platform, Pressable } from "react-native"; |
| 3 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 4 | 3 | ||
| 5 | import { AppText as Text } from "../../components/AppText"; | 4 | import { AppText as Text } from "../../components/AppText"; |
| 6 | import { cn } from "../../lib/cn"; | 5 | import { cn } from "../../lib/cn"; |
| ... | @@ -37,11 +36,12 @@ export function ConnectionSheetButton(props: { | ... | @@ -37,11 +36,12 @@ export function ConnectionSheetButton(props: { |
| 37 | }) { | 36 | }) { |
| 38 | const tone = props.tone ?? "secondary"; | 37 | const tone = props.tone ?? "secondary"; |
| 39 | 38 | ||
| 40 | const primaryFg = useThemeColor("--color-primary-foreground"); | 39 | const textColorClassName = |
| 41 | const dangerFg = useThemeColor("--color-danger-foreground"); | 40 | tone === "primary" |
| 42 | const secondaryFg = useThemeColor("--color-secondary-foreground"); | 41 | ? "accent-primary-foreground" |
| 43 | 42 | : tone === "danger" | |
| 44 | const textColor = tone === "primary" ? primaryFg : tone === "danger" ? dangerFg : secondaryFg; | 43 | ? "accent-danger-foreground" |
| 44 | : "accent-secondary-foreground"; | ||
| 45 | 45 | ||
| 46 | const primaryShadow = | 46 | const primaryShadow = |
| 47 | tone === "primary" | 47 | tone === "primary" |
| ... | @@ -79,7 +79,7 @@ export function ConnectionSheetButton(props: { | ... | @@ -79,7 +79,7 @@ export function ConnectionSheetButton(props: { |
| 79 | <SymbolView | 79 | <SymbolView |
| 80 | name={props.icon} | 80 | name={props.icon} |
| 81 | size={props.compact ? 13 : 14} | 81 | size={props.compact ? 13 : 14} |
| 82 | tintColor={textColor} | 82 | tintColorClassName={textColorClassName} |
| 83 | type="monochrome" | 83 | type="monochrome" |
| 84 | /> | 84 | /> |
| 85 | <Text | 85 | <Text |
apps/mobile/src/features/connection/ConnectionsNewRouteScreen.tsx+2-2| ... | @@ -5,7 +5,7 @@ import { AsyncResult } from "effect/unstable/reactivity"; | ... | @@ -5,7 +5,7 @@ import { AsyncResult } from "effect/unstable/reactivity"; |
| 5 | import { useCallback, useEffect, useRef, useState } from "react"; | 5 | import { useCallback, useEffect, useRef, useState } from "react"; |
| 6 | import { Alert, Linking, Platform, ScrollView, View } from "react-native"; | 6 | import { Alert, Linking, Platform, ScrollView, View } from "react-native"; |
| 7 | import { useSafeAreaInsets } from "react-native-safe-area-context"; | 7 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 8 | import { useThemeColor } from "../../lib/useThemeColor"; | 8 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 9 | 9 | ||
| 10 | import { AndroidScreenHeader } from "../../components/AndroidScreenHeader"; | 10 | import { AndroidScreenHeader } from "../../components/AndroidScreenHeader"; |
| 11 | import { AppText as Text, AppTextInput as TextInput } from "../../components/AppText"; | 11 | import { AppText as Text, AppTextInput as TextInput } from "../../components/AppText"; |
| ... | @@ -47,7 +47,7 @@ export function ConnectionsNewRouteScreen({ | ... | @@ -47,7 +47,7 @@ export function ConnectionsNewRouteScreen({ |
| 47 | const [scannerLocked, setScannerLocked] = useState(false); | 47 | const [scannerLocked, setScannerLocked] = useState(false); |
| 48 | const attemptedAutoConnectRef = useRef<string | null>(null); | 48 | const attemptedAutoConnectRef = useRef<string | null>(null); |
| 49 | 49 | ||
| 50 | const headerIconColor = useThemeColor("--color-icon"); | 50 | const headerIconColor = useUniwindTheme()["--color-icon"]; |
| 51 | 51 | ||
| 52 | const connectDisabled = isSubmitting || hostInput.trim().length === 0; | 52 | const connectDisabled = isSubmitting || hostInput.trim().length === 0; |
| 53 | 53 |
apps/mobile/src/features/connection/ConnectionsRouteScreen.tsx+1-5| ... | @@ -5,7 +5,6 @@ import type { EnvironmentId } from "@t3tools/contracts"; | ... | @@ -5,7 +5,6 @@ import type { EnvironmentId } from "@t3tools/contracts"; |
| 5 | import { useCallback, useState } from "react"; | 5 | import { useCallback, useState } from "react"; |
| 6 | import { Platform, ScrollView, View } from "react-native"; | 6 | import { Platform, ScrollView, View } from "react-native"; |
| 7 | import { useSafeAreaInsets } from "react-native-safe-area-context"; | 7 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 8 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 9 | 8 | ||
| 10 | import { AndroidScreenHeader } from "../../components/AndroidScreenHeader"; | 9 | import { AndroidScreenHeader } from "../../components/AndroidScreenHeader"; |
| 11 | import { AppText as Text } from "../../components/AppText"; | 10 | import { AppText as Text } from "../../components/AppText"; |
| ... | @@ -24,9 +23,6 @@ export function ConnectionsRouteScreen() { | ... | @@ -24,9 +23,6 @@ export function ConnectionsRouteScreen() { |
| 24 | const insets = useSafeAreaInsets(); | 23 | const insets = useSafeAreaInsets(); |
| 25 | const hasEnvironments = connectedEnvironments.length > 0; | 24 | const hasEnvironments = connectedEnvironments.length > 0; |
| 26 | const [expandedId, setExpandedId] = useState<EnvironmentId | null>(null); | 25 | const [expandedId, setExpandedId] = useState<EnvironmentId | null>(null); |
| 27 | |||
| 28 | const accentColor = useThemeColor("--color-icon-muted"); | ||
| 29 | |||
| 30 | const handleToggle = useCallback((environmentId: EnvironmentId) => { | 26 | const handleToggle = useCallback((environmentId: EnvironmentId) => { |
| 31 | setExpandedId((prev) => (prev === environmentId ? null : environmentId)); | 27 | setExpandedId((prev) => (prev === environmentId ? null : environmentId)); |
| 32 | }, []); | 28 | }, []); |
| ... | @@ -89,7 +85,7 @@ export function ConnectionsRouteScreen() { | ... | @@ -89,7 +85,7 @@ export function ConnectionsRouteScreen() { |
| 89 | <SymbolView | 85 | <SymbolView |
| 90 | name="point.3.connected.trianglepath.dotted" | 86 | name="point.3.connected.trianglepath.dotted" |
| 91 | size={20} | 87 | size={20} |
| 92 | tintColor={accentColor} | 88 | tintColorClassName={"accent-icon-muted"} |
| 93 | type="monochrome" | 89 | type="monochrome" |
| 94 | /> | 90 | /> |
| 95 | </View> | 91 | </View> |
apps/mobile/src/features/connection/EnvironmentConnectionNotice.tsx+2-4| ... | @@ -7,7 +7,6 @@ import { ActivityIndicator, Pressable, View } from "react-native"; | ... | @@ -7,7 +7,6 @@ import { ActivityIndicator, Pressable, View } from "react-native"; |
| 7 | 7 | ||
| 8 | import { AppText as Text } from "../../components/AppText"; | 8 | import { AppText as Text } from "../../components/AppText"; |
| 9 | import { copyTextWithHaptic } from "../../lib/copyTextWithHaptic"; | 9 | import { copyTextWithHaptic } from "../../lib/copyTextWithHaptic"; |
| 10 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 11 | 10 | ||
| 12 | function noticeTitle(phase: EnvironmentConnectionPhase, environmentLabel: string): string { | 11 | function noticeTitle(phase: EnvironmentConnectionPhase, environmentLabel: string): string { |
| 13 | switch (phase) { | 12 | switch (phase) { |
| ... | @@ -55,7 +54,6 @@ export function EnvironmentConnectionNotice(props: { | ... | @@ -55,7 +54,6 @@ export function EnvironmentConnectionNotice(props: { |
| 55 | readonly resourceName: string; | 54 | readonly resourceName: string; |
| 56 | readonly onRetry: () => void; | 55 | readonly onRetry: () => void; |
| 57 | }) { | 56 | }) { |
| 58 | const iconColor = String(useThemeColor("--color-icon-muted")); | ||
| 59 | const isRetrying = | 57 | const isRetrying = |
| 60 | props.connection.phase === "connecting" || props.connection.phase === "reconnecting"; | 58 | props.connection.phase === "connecting" || props.connection.phase === "reconnecting"; |
| 61 | 59 | ||
| ... | @@ -63,12 +61,12 @@ export function EnvironmentConnectionNotice(props: { | ... | @@ -63,12 +61,12 @@ export function EnvironmentConnectionNotice(props: { |
| 63 | <View className="flex-1 items-center justify-center px-8"> | 61 | <View className="flex-1 items-center justify-center px-8"> |
| 64 | <View className="max-w-[320px] items-center gap-3"> | 62 | <View className="max-w-[320px] items-center gap-3"> |
| 65 | {isRetrying ? ( | 63 | {isRetrying ? ( |
| 66 | <ActivityIndicator size="small" color={iconColor} /> | 64 | <ActivityIndicator size="small" colorClassName={"accent-icon-muted"} /> |
| 67 | ) : ( | 65 | ) : ( |
| 68 | <SymbolView | 66 | <SymbolView |
| 69 | name={props.connection.phase === "offline" ? "wifi.slash" : "bolt.horizontal.circle"} | 67 | name={props.connection.phase === "offline" ? "wifi.slash" : "bolt.horizontal.circle"} |
| 70 | size={24} | 68 | size={24} |
| 71 | tintColor={iconColor} | 69 | tintColorClassName={"accent-icon-muted"} |
| 72 | type="monochrome" | 70 | type="monochrome" |
| 73 | /> | 71 | /> |
| 74 | )} | 72 | )} |
apps/mobile/src/features/connection/connectionTone.ts+12-12| ... | @@ -6,38 +6,38 @@ export function connectionTone(state: RemoteClientConnectionState): StatusTone { | ... | @@ -6,38 +6,38 @@ export function connectionTone(state: RemoteClientConnectionState): StatusTone { |
| 6 | case "connected": | 6 | case "connected": |
| 7 | return { | 7 | return { |
| 8 | label: "Connected", | 8 | label: "Connected", |
| 9 | pillClassName: "bg-emerald-500/12 dark:bg-emerald-500/16", | 9 | pillClassName: "bg-adaptive-emerald-500-a12-a16", |
| 10 | textClassName: "text-emerald-700 dark:text-emerald-300", | 10 | textClassName: "text-adaptive-emerald-700-300", |
| 11 | }; | 11 | }; |
| 12 | case "reconnecting": | 12 | case "reconnecting": |
| 13 | return { | 13 | return { |
| 14 | label: "Reconnecting", | 14 | label: "Reconnecting", |
| 15 | pillClassName: "bg-amber-500/12 dark:bg-amber-500/16", | 15 | pillClassName: "bg-adaptive-amber-500-a12-a16", |
| 16 | textClassName: "text-amber-700 dark:text-amber-300", | 16 | textClassName: "text-adaptive-amber-700-300", |
| 17 | }; | 17 | }; |
| 18 | case "connecting": | 18 | case "connecting": |
| 19 | return { | 19 | return { |
| 20 | label: "Connecting", | 20 | label: "Connecting", |
| 21 | pillClassName: "bg-sky-500/12 dark:bg-sky-500/16", | 21 | pillClassName: "bg-adaptive-sky-500-a12-a16", |
| 22 | textClassName: "text-sky-700 dark:text-sky-300", | 22 | textClassName: "text-adaptive-sky-700-300", |
| 23 | }; | 23 | }; |
| 24 | case "error": | 24 | case "error": |
| 25 | return { | 25 | return { |
| 26 | label: "Connection failed", | 26 | label: "Connection failed", |
| 27 | pillClassName: "bg-rose-500/12 dark:bg-rose-500/16", | 27 | pillClassName: "bg-adaptive-rose-500-a12-a16", |
| 28 | textClassName: "text-rose-700 dark:text-rose-300", | 28 | textClassName: "text-adaptive-rose-700-300", |
| 29 | }; | 29 | }; |
| 30 | case "offline": | 30 | case "offline": |
| 31 | return { | 31 | return { |
| 32 | label: "Offline", | 32 | label: "Offline", |
| 33 | pillClassName: "bg-rose-500/12 dark:bg-rose-500/16", | 33 | pillClassName: "bg-adaptive-rose-500-a12-a16", |
| 34 | textClassName: "text-rose-700 dark:text-rose-300", | 34 | textClassName: "text-adaptive-rose-700-300", |
| 35 | }; | 35 | }; |
| 36 | case "available": | 36 | case "available": |
| 37 | return { | 37 | return { |
| 38 | label: "Available", | 38 | label: "Available", |
| 39 | pillClassName: "bg-neutral-500/10 dark:bg-neutral-500/16", | 39 | pillClassName: "bg-adaptive-neutral-500-a10-a16", |
| 40 | textClassName: "text-neutral-600 dark:text-neutral-300", | 40 | textClassName: "text-adaptive-neutral-600-300", |
| 41 | }; | 41 | }; |
| 42 | } | 42 | } |
| 43 | } | 43 | } |
apps/mobile/src/features/files/FileMarkdownPreview.tsx+10-9| ... | @@ -13,7 +13,7 @@ import { | ... | @@ -13,7 +13,7 @@ import { |
| 13 | resolveMarkdownFontSizes, | 13 | resolveMarkdownFontSizes, |
| 14 | resolveNativeMarkdownTypography, | 14 | resolveNativeMarkdownTypography, |
| 15 | } from "../../lib/appearancePreferences"; | 15 | } from "../../lib/appearancePreferences"; |
| 16 | import { useThemeColor } from "../../lib/useThemeColor"; | 16 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 17 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | 17 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; |
| 18 | import { | 18 | import { |
| 19 | hasNativeSelectableMarkdownText, | 19 | hasNativeSelectableMarkdownText, |
| ... | @@ -38,14 +38,15 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles { | ... | @@ -38,14 +38,15 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles { |
| 38 | () => resolveNativeMarkdownTypography(appearance.baseFontSize), | 38 | () => resolveNativeMarkdownTypography(appearance.baseFontSize), |
| 39 | [appearance.baseFontSize], | 39 | [appearance.baseFontSize], |
| 40 | ); | 40 | ); |
| 41 | const body = String(useThemeColor("--color-md-body")); | 41 | const theme = useUniwindTheme(); |
| 42 | const strong = String(useThemeColor("--color-md-strong")); | 42 | const body = theme["--color-md-body"]; |
| 43 | const link = String(useThemeColor("--color-md-link")); | 43 | const strong = theme["--color-md-strong"]; |
| 44 | const blockquoteBorder = String(useThemeColor("--color-md-blockquote-border")); | 44 | const link = theme["--color-md-link"]; |
| 45 | const blockquoteBackground = String(useThemeColor("--color-md-blockquote-bg")); | 45 | const blockquoteBorder = theme["--color-md-blockquote-border"]; |
| 46 | const codeBackground = String(useThemeColor("--color-md-code-bg")); | 46 | const blockquoteBackground = theme["--color-md-blockquote-bg"]; |
| 47 | const codeText = String(useThemeColor("--color-md-code-text")); | 47 | const codeBackground = theme["--color-md-code-bg"]; |
| 48 | const horizontalRule = String(useThemeColor("--color-md-hr")); | 48 | const codeText = theme["--color-md-code-text"]; |
| 49 | const horizontalRule = theme["--color-md-hr"]; | ||
| 49 | const regularFontFamily = useFontFamily("regular"); | 50 | const regularFontFamily = useFontFamily("regular"); |
| 50 | const mediumFontFamily = useFontFamily("medium"); | 51 | const mediumFontFamily = useFontFamily("medium"); |
| 51 | const boldFontFamily = useFontFamily("bold"); | 52 | const boldFontFamily = useFontFamily("bold"); |
apps/mobile/src/features/files/FileTreeBrowser.tsx+2-6| ... | @@ -7,7 +7,6 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"; | ... | @@ -7,7 +7,6 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 7 | import { AppText as Text } from "../../components/AppText"; | 7 | import { AppText as Text } from "../../components/AppText"; |
| 8 | import { PierreEntryIcon } from "../../components/PierreEntryIcon"; | 8 | import { PierreEntryIcon } from "../../components/PierreEntryIcon"; |
| 9 | import { cn } from "../../lib/cn"; | 9 | import { cn } from "../../lib/cn"; |
| 10 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 11 | import { IOS_NAV_BAR_HEIGHT } from "../../lib/layoutMetrics"; | 10 | import { IOS_NAV_BAR_HEIGHT } from "../../lib/layoutMetrics"; |
| 12 | import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass"; | 11 | import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass"; |
| 13 | import { | 12 | import { |
| ... | @@ -46,7 +45,6 @@ const FileTreeRow = memo(function FileTreeRow(props: { | ... | @@ -46,7 +45,6 @@ const FileTreeRow = memo(function FileTreeRow(props: { |
| 46 | readonly item: VisibleFileTreeNode; | 45 | readonly item: VisibleFileTreeNode; |
| 47 | readonly selected: boolean; | 46 | readonly selected: boolean; |
| 48 | readonly expanded: boolean; | 47 | readonly expanded: boolean; |
| 49 | readonly iconColor: string; | ||
| 50 | readonly onPressDirectory: (path: string) => void; | 48 | readonly onPressDirectory: (path: string) => void; |
| 51 | readonly onPreviewFile?: (path: string) => void; | 49 | readonly onPreviewFile?: (path: string) => void; |
| 52 | readonly onPressFile: (path: string) => void; | 50 | readonly onPressFile: (path: string) => void; |
| ... | @@ -79,7 +77,7 @@ const FileTreeRow = memo(function FileTreeRow(props: { | ... | @@ -79,7 +77,7 @@ const FileTreeRow = memo(function FileTreeRow(props: { |
| 79 | <SymbolView | 77 | <SymbolView |
| 80 | name={props.expanded ? "chevron.down" : "chevron.right"} | 78 | name={props.expanded ? "chevron.down" : "chevron.right"} |
| 81 | size={12} | 79 | size={12} |
| 82 | tintColor={props.iconColor} | 80 | tintColorClassName="accent-icon-muted" |
| 83 | type="monochrome" | 81 | type="monochrome" |
| 84 | /> | 82 | /> |
| 85 | ) : ( | 83 | ) : ( |
| ... | @@ -125,7 +123,6 @@ export function FileTreeBrowser(props: { | ... | @@ -125,7 +123,6 @@ export function FileTreeBrowser(props: { |
| 125 | // Native transparent-header height ≈ safe-area top + nav bar (~44). Matches the | 123 | // Native transparent-header height ≈ safe-area top + nav bar (~44). Matches the |
| 126 | // observed adjustedContentInset bottom (~102) seen in the native trace. | 124 | // observed adjustedContentInset bottom (~102) seen in the native trace. |
| 127 | const headerInset = NATIVE_LIQUID_GLASS_SUPPORTED ? insets.top + IOS_NAV_BAR_HEIGHT : 0; | 125 | const headerInset = NATIVE_LIQUID_GLASS_SUPPORTED ? insets.top + IOS_NAV_BAR_HEIGHT : 0; |
| 128 | const iconColor = String(useThemeColor("--color-icon-muted")); | ||
| 129 | const { onPreviewFile, onSelectFile, selectedPath: controlledSelectedPath } = props; | 126 | const { onPreviewFile, onSelectFile, selectedPath: controlledSelectedPath } = props; |
| 130 | const controlledSelectedPathRef = useRef(controlledSelectedPath); | 127 | const controlledSelectedPathRef = useRef(controlledSelectedPath); |
| 131 | const pendingSelectionTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); | 128 | const pendingSelectionTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); |
| ... | @@ -216,13 +213,12 @@ export function FileTreeBrowser(props: { | ... | @@ -216,13 +213,12 @@ export function FileTreeBrowser(props: { |
| 216 | item={item} | 213 | item={item} |
| 217 | selected={item.node.kind === "file" && item.node.path === selectedPath} | 214 | selected={item.node.kind === "file" && item.node.path === selectedPath} |
| 218 | expanded={expandedPaths.has(item.node.path)} | 215 | expanded={expandedPaths.has(item.node.path)} |
| 219 | iconColor={iconColor} | ||
| 220 | onPressDirectory={toggleDirectory} | 216 | onPressDirectory={toggleDirectory} |
| 221 | onPreviewFile={onPreviewFile} | 217 | onPreviewFile={onPreviewFile} |
| 222 | onPressFile={handleSelectFile} | 218 | onPressFile={handleSelectFile} |
| 223 | /> | 219 | /> |
| 224 | ), | 220 | ), |
| 225 | [expandedPaths, handleSelectFile, iconColor, onPreviewFile, selectedPath, toggleDirectory], | 221 | [expandedPaths, handleSelectFile, onPreviewFile, selectedPath, toggleDirectory], |
| 226 | ); | 222 | ); |
| 227 | 223 | ||
| 228 | if (props.error && props.entries.length === 0) { | 224 | if (props.error && props.entries.length === 0) { |
apps/mobile/src/features/files/SourceFileSurface.tsx+4-2| ... | @@ -17,6 +17,7 @@ import { cn } from "../../lib/cn"; | ... | @@ -17,6 +17,7 @@ import { cn } from "../../lib/cn"; |
| 17 | import type { ResolvedMobileCodeSurface } from "../../lib/appearancePreferences"; | 17 | import type { ResolvedMobileCodeSurface } from "../../lib/appearancePreferences"; |
| 18 | import { useAppearanceCodeSurface } from "../settings/appearance/useAppearanceCodeSurface"; | 18 | import { useAppearanceCodeSurface } from "../settings/appearance/useAppearanceCodeSurface"; |
| 19 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | 19 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; |
| 20 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; | ||
| 20 | import { | 21 | import { |
| 21 | buildNativeSourceTokens, | 22 | buildNativeSourceTokens, |
| 22 | NATIVE_SOURCE_CONTENT_WIDTH, | 23 | NATIVE_SOURCE_CONTENT_WIDTH, |
| ... | @@ -153,6 +154,7 @@ function NativeSourceFileSurface( | ... | @@ -153,6 +154,7 @@ function NativeSourceFileSurface( |
| 153 | const { NativeView, onRefresh } = props; | 154 | const { NativeView, onRefresh } = props; |
| 154 | const { codeSurface, codeWordBreak, nativeSourceStyle } = useAppearanceCodeSurface(); | 155 | const { codeSurface, codeWordBreak, nativeSourceStyle } = useAppearanceCodeSurface(); |
| 155 | const { themeAppearance, themeId } = useAppearancePreferences(); | 156 | const { themeAppearance, themeId } = useAppearancePreferences(); |
| 157 | const appTheme = useUniwindTheme(); | ||
| 156 | const { width: viewportWidth } = useWindowDimensions(); | 158 | const { width: viewportWidth } = useWindowDimensions(); |
| 157 | const { rowsJson, status, targetIndex, tokens } = useSourceFileModel(props); | 159 | const { rowsJson, status, targetIndex, tokens } = useSourceFileModel(props); |
| 158 | const [isPullRefreshing, setIsPullRefreshing] = useState(false); | 160 | const [isPullRefreshing, setIsPullRefreshing] = useState(false); |
| ... | @@ -173,8 +175,8 @@ function NativeSourceFileSurface( | ... | @@ -173,8 +175,8 @@ function NativeSourceFileSurface( |
| 173 | [targetIndex], | 175 | [targetIndex], |
| 174 | ); | 176 | ); |
| 175 | const themeJson = useMemo( | 177 | const themeJson = useMemo( |
| 176 | () => JSON.stringify(createNativeReviewDiffTheme(themeAppearance, themeId)), | 178 | () => JSON.stringify(createNativeReviewDiffTheme(themeAppearance, themeId, appTheme)), |
| 177 | [themeAppearance, themeId], | 179 | [appTheme, themeAppearance, themeId], |
| 178 | ); | 180 | ); |
| 179 | const styleJson = useMemo(() => JSON.stringify(nativeSourceStyle), [nativeSourceStyle]); | 181 | const styleJson = useMemo(() => JSON.stringify(nativeSourceStyle), [nativeSourceStyle]); |
| 180 | const contentWidth = codeWordBreak | 182 | const contentWidth = codeWordBreak |
apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx+14-9| ... | @@ -19,7 +19,7 @@ import { LoadingScreen } from "../../components/LoadingScreen"; | ... | @@ -19,7 +19,7 @@ import { LoadingScreen } from "../../components/LoadingScreen"; |
| 19 | import { resolveFileSelectionNavigationAction } from "../../lib/adaptive-navigation"; | 19 | import { resolveFileSelectionNavigationAction } from "../../lib/adaptive-navigation"; |
| 20 | import { copyTextWithHaptic } from "../../lib/copyTextWithHaptic"; | 20 | import { copyTextWithHaptic } from "../../lib/copyTextWithHaptic"; |
| 21 | import { tryOpenExternalUrl } from "../../lib/openExternalUrl"; | 21 | import { tryOpenExternalUrl } from "../../lib/openExternalUrl"; |
| 22 | import { useThemeColor } from "../../lib/useThemeColor"; | 22 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 23 | import { useThreadSelection } from "../../state/use-thread-selection"; | 23 | import { useThreadSelection } from "../../state/use-thread-selection"; |
| 24 | import { useSelectedThreadWorktree } from "../../state/use-selected-thread-worktree"; | 24 | import { useSelectedThreadWorktree } from "../../state/use-selected-thread-worktree"; |
| 25 | import { useEnvironmentQuery } from "../../state/query"; | 25 | import { useEnvironmentQuery } from "../../state/query"; |
| ... | @@ -135,11 +135,11 @@ function FileContent(props: { | ... | @@ -135,11 +135,11 @@ function FileContent(props: { |
| 135 | return ( | 135 | return ( |
| 136 | <View className="flex-1 bg-sheet"> | 136 | <View className="flex-1 bg-sheet"> |
| 137 | {props.truncated ? ( | 137 | {props.truncated ? ( |
| 138 | <View className="border-b border-amber-200 bg-amber-50 px-4 py-2 dark:border-amber-900/60 dark:bg-amber-950/40"> | 138 | <View className="border-b border-adaptive-amber-200-900-a60 bg-adaptive-amber-50-950-a40 px-4 py-2"> |
| 139 | <Text className="text-2xs font-t3-bold uppercase text-amber-700 dark:text-amber-300"> | 139 | <Text className="text-2xs font-t3-bold uppercase text-adaptive-amber-700-300"> |
| 140 | Partial file | 140 | Partial file |
| 141 | </Text> | 141 | </Text> |
| 142 | <Text className="text-xs leading-snug text-amber-800 dark:text-amber-200"> | 142 | <Text className="text-xs leading-snug text-adaptive-amber-800-200"> |
| 143 | Preview limited to the first 1 MB of a truncated file. | 143 | Preview limited to the first 1 MB of a truncated file. |
| 144 | </Text> | 144 | </Text> |
| 145 | </View> | 145 | </View> |
| ... | @@ -210,7 +210,7 @@ function FilesUnavailable() { | ... | @@ -210,7 +210,7 @@ function FilesUnavailable() { |
| 210 | } | 210 | } |
| 211 | 211 | ||
| 212 | function FilesToolbarBottomFade() { | 212 | function FilesToolbarBottomFade() { |
| 213 | const sheetColor = String(useThemeColor("--color-sheet")); | 213 | const sheetColor = String(useUniwindTheme()["--color-sheet"]); |
| 214 | 214 | ||
| 215 | if (process.env.EXPO_OS !== "ios") { | 215 | if (process.env.EXPO_OS !== "ios") { |
| 216 | return null; | 216 | return null; |
| ... | @@ -245,8 +245,8 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { | ... | @@ -245,8 +245,8 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { |
| 245 | const [searchQuery, setSearchQuery] = useState(""); | 245 | const [searchQuery, setSearchQuery] = useState(""); |
| 246 | const isAndroid = Platform.OS === "android"; | 246 | const isAndroid = Platform.OS === "android"; |
| 247 | const { themeAppearance: highlightTheme } = useAppearancePreferences(); | 247 | const { themeAppearance: highlightTheme } = useAppearancePreferences(); |
| 248 | const iconColor = String(useThemeColor("--color-icon-muted")); | 248 | const theme = useUniwindTheme(); |
| 249 | const sheetSurfaceColor = String(useThemeColor("--color-sheet-solid")); | 249 | const sheetSurfaceColor = theme["--color-sheet-solid"]; |
| 250 | const { cwd, environmentId, projectName, selectedThread, threadId } = useThreadFilesWorkspace( | 250 | const { cwd, environmentId, projectName, selectedThread, threadId } = useThreadFilesWorkspace( |
| 251 | props.route.params, | 251 | props.route.params, |
| 252 | ); | 252 | ); |
| ... | @@ -413,7 +413,12 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { | ... | @@ -413,7 +413,12 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) { |
| 413 | ]} | 413 | ]} |
| 414 | /> | 414 | /> |
| 415 | <View className="flex-row items-center gap-2 border-b border-border px-3 py-2"> | 415 | <View className="flex-row items-center gap-2 border-b border-border px-3 py-2"> |
| 416 | <SymbolView name="magnifyingglass" size={17} tintColor={iconColor} type="monochrome" /> | 416 | <SymbolView |
| 417 | name="magnifyingglass" | ||
| 418 | size={17} | ||
| 419 | tintColorClassName={"accent-icon-muted"} | ||
| 420 | type="monochrome" | ||
| 421 | /> | ||
| 417 | <TextInput | 422 | <TextInput |
| 418 | accessibilityLabel="Search files" | 423 | accessibilityLabel="Search files" |
| 419 | autoCapitalize="none" | 424 | autoCapitalize="none" |
| ... | @@ -467,7 +472,7 @@ export function ThreadFileScreen(props: ThreadFileRouteScreenProps) { | ... | @@ -467,7 +472,7 @@ export function ThreadFileScreen(props: ThreadFileRouteScreenProps) { |
| 467 | useAdaptiveWorkspacePaneRole("inspector"); | 472 | useAdaptiveWorkspacePaneRole("inspector"); |
| 468 | const navigation = useNavigation(); | 473 | const navigation = useNavigation(); |
| 469 | const { fileInspector, panes, toggleAuxiliaryPane } = useAdaptiveWorkspaceLayout(); | 474 | const { fileInspector, panes, toggleAuxiliaryPane } = useAdaptiveWorkspaceLayout(); |
| 470 | const iconColor = useThemeColor("--color-icon"); | 475 | const iconColor = useUniwindTheme()["--color-icon"]; |
| 471 | const params = props.route.params; | 476 | const params = props.route.params; |
| 472 | const relativePath = normalizeRoutePath(params.path); | 477 | const relativePath = normalizeRoutePath(params.path); |
| 473 | const targetLine = normalizeRouteLine(firstRouteParam(params.line)); | 478 | const targetLine = normalizeRouteLine(firstRouteParam(params.line)); |
apps/mobile/src/features/files/thread-file-navigator-pane.tsx+16-6| ... | @@ -12,7 +12,7 @@ import { | ... | @@ -12,7 +12,7 @@ import { |
| 12 | 12 | ||
| 13 | import { AppText as Text, AppTextInput as TextInput } from "../../components/AppText"; | 13 | import { AppText as Text, AppTextInput as TextInput } from "../../components/AppText"; |
| 14 | import { nativeHeaderScrollEdgeEffects } from "../../native/StackHeader"; | 14 | import { nativeHeaderScrollEdgeEffects } from "../../native/StackHeader"; |
| 15 | import { useThemeColor } from "../../lib/useThemeColor"; | 15 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 16 | import { projectEnvironment } from "../../state/projects"; | 16 | import { projectEnvironment } from "../../state/projects"; |
| 17 | import { useEnvironmentQuery } from "../../state/query"; | 17 | import { useEnvironmentQuery } from "../../state/query"; |
| 18 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | 18 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; |
| ... | @@ -29,9 +29,9 @@ export function ThreadFileNavigatorPane(props: { | ... | @@ -29,9 +29,9 @@ export function ThreadFileNavigatorPane(props: { |
| 29 | }) { | 29 | }) { |
| 30 | const [searchQuery, setSearchQuery] = useState(""); | 30 | const [searchQuery, setSearchQuery] = useState(""); |
| 31 | const { themeAppearance: highlightTheme } = useAppearancePreferences(); | 31 | const { themeAppearance: highlightTheme } = useAppearancePreferences(); |
| 32 | const iconColor = String(useThemeColor("--color-icon-muted")); | 32 | const theme = useUniwindTheme(); |
| 33 | const foregroundColor = String(useThemeColor("--color-foreground")); | 33 | const foregroundColor = theme["--color-foreground"]; |
| 34 | const sheetColor = String(useThemeColor("--color-sheet")); | 34 | const sheetColor = theme["--color-sheet"]; |
| 35 | const headerScrollEdgeEffects = nativeHeaderScrollEdgeEffects(Platform.OS, Platform.Version); | 35 | const headerScrollEdgeEffects = nativeHeaderScrollEdgeEffects(Platform.OS, Platform.Version); |
| 36 | const entriesQuery = useEnvironmentQuery( | 36 | const entriesQuery = useEnvironmentQuery( |
| 37 | projectEnvironment.listEntries({ | 37 | projectEnvironment.listEntries({ |
| ... | @@ -152,11 +152,21 @@ export function ThreadFileNavigatorPane(props: { | ... | @@ -152,11 +152,21 @@ export function ThreadFileNavigatorPane(props: { |
| 152 | className="h-8 w-8 items-center justify-center rounded-full active:bg-subtle" | 152 | className="h-8 w-8 items-center justify-center rounded-full active:bg-subtle" |
| 153 | onPress={entriesQuery.refresh} | 153 | onPress={entriesQuery.refresh} |
| 154 | > | 154 | > |
| 155 | <SymbolView name="arrow.clockwise" size={14} tintColor={iconColor} type="monochrome" /> | 155 | <SymbolView |
| 156 | name="arrow.clockwise" | ||
| 157 | size={14} | ||
| 158 | tintColorClassName={"accent-icon-muted"} | ||
| 159 | type="monochrome" | ||
| 160 | /> | ||
| 156 | </Pressable> | 161 | </Pressable> |
| 157 | </View> | 162 | </View> |
| 158 | <View className="flex-row items-center gap-2 border-t border-border px-3 py-2"> | 163 | <View className="flex-row items-center gap-2 border-t border-border px-3 py-2"> |
| 159 | <SymbolView name="magnifyingglass" size={15} tintColor={iconColor} type="monochrome" /> | 164 | <SymbolView |
| 165 | name="magnifyingglass" | ||
| 166 | size={15} | ||
| 167 | tintColorClassName={"accent-icon-muted"} | ||
| 168 | type="monochrome" | ||
| 169 | /> | ||
| 160 | <TextInput | 170 | <TextInput |
| 161 | accessibilityLabel="Search files" | 171 | accessibilityLabel="Search files" |
| 162 | autoCapitalize="none" | 172 | autoCapitalize="none" |
apps/mobile/src/features/home/AndroidHomeFab.tsx+1-4| ... | @@ -3,7 +3,6 @@ import { Platform, Pressable, View } from "react-native"; | ... | @@ -3,7 +3,6 @@ import { Platform, Pressable, View } from "react-native"; |
| 3 | import { useSafeAreaInsets } from "react-native-safe-area-context"; | 3 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 4 | 4 | ||
| 5 | import { SymbolView } from "../../components/AppSymbol"; | 5 | import { SymbolView } from "../../components/AppSymbol"; |
| 6 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 7 | 6 | ||
| 8 | /** | 7 | /** |
| 9 | * Android-only wrapper that overlays a bottom-right new-task FAB on a thread | 8 | * Android-only wrapper that overlays a bottom-right new-task FAB on a thread |
| ... | @@ -25,8 +24,6 @@ function AndroidHomeFab(props: { | ... | @@ -25,8 +24,6 @@ function AndroidHomeFab(props: { |
| 25 | readonly children: ReactNode; | 24 | readonly children: ReactNode; |
| 26 | }) { | 25 | }) { |
| 27 | const insets = useSafeAreaInsets(); | 26 | const insets = useSafeAreaInsets(); |
| 28 | const primaryForegroundColor = useThemeColor("--color-primary-foreground"); | ||
| 29 | |||
| 30 | return ( | 27 | return ( |
| 31 | <View className="flex-1"> | 28 | <View className="flex-1"> |
| 32 | {props.children} | 29 | {props.children} |
| ... | @@ -42,7 +39,7 @@ function AndroidHomeFab(props: { | ... | @@ -42,7 +39,7 @@ function AndroidHomeFab(props: { |
| 42 | <SymbolView | 39 | <SymbolView |
| 43 | name="square.and.pencil" | 40 | name="square.and.pencil" |
| 44 | size={22} | 41 | size={22} |
| 45 | tintColor={primaryForegroundColor} | 42 | tintColorClassName={"accent-primary-foreground"} |
| 46 | type="monochrome" | 43 | type="monochrome" |
| 47 | /> | 44 | /> |
| 48 | </Pressable> | 45 | </Pressable> |
apps/mobile/src/features/home/HomeHeader.tsx+17-9| ... | @@ -12,7 +12,7 @@ import { SymbolView } from "../../components/AppSymbol"; | ... | @@ -12,7 +12,7 @@ import { SymbolView } from "../../components/AppSymbol"; |
| 12 | import { T3Wordmark } from "../../components/T3Wordmark"; | 12 | import { T3Wordmark } from "../../components/T3Wordmark"; |
| 13 | import { HOME_HORIZONTAL_INSET } from "../../lib/layoutMetrics"; | 13 | import { HOME_HORIZONTAL_INSET } from "../../lib/layoutMetrics"; |
| 14 | import { resolveMobileStageLabel } from "../../lib/mobileBranding"; | 14 | import { resolveMobileStageLabel } from "../../lib/mobileBranding"; |
| 15 | import { useThemeColor } from "../../lib/useThemeColor"; | 15 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 16 | import { useThreadListV2Enabled } from "../threads/use-thread-list-v2-enabled"; | 16 | import { useThreadListV2Enabled } from "../threads/use-thread-list-v2-enabled"; |
| 17 | import { useHardwareKeyboardCommand } from "../keyboard/hardwareKeyboardCommands"; | 17 | import { useHardwareKeyboardCommand } from "../keyboard/hardwareKeyboardCommands"; |
| 18 | import { withNativeGlassHeaderItem } from "../layout/native-glass-header-items"; | 18 | import { withNativeGlassHeaderItem } from "../layout/native-glass-header-items"; |
| ... | @@ -67,8 +67,6 @@ function checkedMenuState(checked: boolean) { | ... | @@ -67,8 +67,6 @@ function checkedMenuState(checked: boolean) { |
| 67 | 67 | ||
| 68 | function AndroidHomeHeader(props: HomeHeaderProps) { | 68 | function AndroidHomeHeader(props: HomeHeaderProps) { |
| 69 | const insets = useSafeAreaInsets(); | 69 | const insets = useSafeAreaInsets(); |
| 70 | const iconColor = useThemeColor("--color-icon"); | ||
| 71 | const mutedColor = useThemeColor("--color-foreground-muted"); | ||
| 72 | const stageLabel = resolveMobileStageLabel(Constants.expoConfig?.extra?.appVariant); | 70 | const stageLabel = resolveMobileStageLabel(Constants.expoConfig?.extra?.appVariant); |
| 73 | // Thread List v2 lays the list out in fixed creation order, so the | 71 | // Thread List v2 lays the list out in fixed creation order, so the |
| 74 | // sort/group filter controls would be silently ignored — hide them and | 72 | // sort/group filter controls would be silently ignored — hide them and |
| ... | @@ -218,7 +216,7 @@ function AndroidHomeHeader(props: HomeHeaderProps) { | ... | @@ -218,7 +216,7 @@ function AndroidHomeHeader(props: HomeHeaderProps) { |
| 218 | brand={ | 216 | brand={ |
| 219 | <View className="flex-row items-center gap-2"> | 217 | <View className="flex-row items-center gap-2"> |
| 220 | {/* Mirrors the desktop SidebarBrand: T3 mark + muted "Code". */} | 218 | {/* Mirrors the desktop SidebarBrand: T3 mark + muted "Code". */} |
| 221 | <T3Wordmark color={iconColor} height={15} /> | 219 | <T3Wordmark colorClassName="accent-icon" height={15} /> |
| 222 | <RNText className="-ml-0.5 text-[21px] font-t3-medium tracking-[-0.5px] text-foreground-muted"> | 220 | <RNText className="-ml-0.5 text-[21px] font-t3-medium tracking-[-0.5px] text-foreground-muted"> |
| 223 | Code | 221 | Code |
| 224 | </RNText> | 222 | </RNText> |
| ... | @@ -248,7 +246,7 @@ function AndroidHomeHeader(props: HomeHeaderProps) { | ... | @@ -248,7 +246,7 @@ function AndroidHomeHeader(props: HomeHeaderProps) { |
| 248 | : "line.3.horizontal.decrease.circle" | 246 | : "line.3.horizontal.decrease.circle" |
| 249 | } | 247 | } |
| 250 | size={16} | 248 | size={16} |
| 251 | tintColor={iconColor} | 249 | tintColorClassName={"accent-icon"} |
| 252 | type="monochrome" | 250 | type="monochrome" |
| 253 | /> | 251 | /> |
| 254 | </Pressable> | 252 | </Pressable> |
| ... | @@ -262,12 +260,22 @@ function AndroidHomeHeader(props: HomeHeaderProps) { | ... | @@ -262,12 +260,22 @@ function AndroidHomeHeader(props: HomeHeaderProps) { |
| 262 | onPress={props.onOpenSettings} | 260 | onPress={props.onOpenSettings} |
| 263 | className="size-11 items-center justify-center rounded-full bg-subtle" | 261 | className="size-11 items-center justify-center rounded-full bg-subtle" |
| 264 | > | 262 | > |
| 265 | <SymbolView name="gearshape" size={18} tintColor={iconColor} type="monochrome" /> | 263 | <SymbolView |
| 264 | name="gearshape" | ||
| 265 | size={18} | ||
| 266 | tintColorClassName={"accent-icon"} | ||
| 267 | type="monochrome" | ||
| 268 | /> | ||
| 266 | </Pressable> | 269 | </Pressable> |
| 267 | </View> | 270 | </View> |
| 268 | 271 | ||
| 269 | <View className="min-h-12 flex-row items-center gap-2.5 rounded-2xl border border-input-border bg-input px-3.5"> | 272 | <View className="min-h-12 flex-row items-center gap-2.5 rounded-2xl border border-input-border bg-input px-3.5"> |
| 270 | <SymbolView name="magnifyingglass" size={17} tintColor={mutedColor} type="monochrome" /> | 273 | <SymbolView |
| 274 | name="magnifyingglass" | ||
| 275 | size={17} | ||
| 276 | tintColorClassName={"accent-foreground-muted"} | ||
| 277 | type="monochrome" | ||
| 278 | /> | ||
| 271 | <TextInput | 279 | <TextInput |
| 272 | accessibilityLabel="Search threads" | 280 | accessibilityLabel="Search threads" |
| 273 | autoCapitalize="none" | 281 | autoCapitalize="none" |
| ... | @@ -286,7 +294,7 @@ function AndroidHomeHeader(props: HomeHeaderProps) { | ... | @@ -286,7 +294,7 @@ function AndroidHomeHeader(props: HomeHeaderProps) { |
| 286 | <SymbolView | 294 | <SymbolView |
| 287 | name="xmark.circle.fill" | 295 | name="xmark.circle.fill" |
| 288 | size={17} | 296 | size={17} |
| 289 | tintColor={mutedColor} | 297 | tintColorClassName={"accent-foreground-muted"} |
| 290 | type="monochrome" | 298 | type="monochrome" |
| 291 | /> | 299 | /> |
| 292 | </Pressable> | 300 | </Pressable> |
| ... | @@ -300,7 +308,7 @@ function AndroidHomeHeader(props: HomeHeaderProps) { | ... | @@ -300,7 +308,7 @@ function AndroidHomeHeader(props: HomeHeaderProps) { |
| 300 | 308 | ||
| 301 | function IosHomeHeader(props: HomeHeaderProps) { | 309 | function IosHomeHeader(props: HomeHeaderProps) { |
| 302 | const searchBarRef = useRef<SearchBarCommands>(null); | 310 | const searchBarRef = useRef<SearchBarCommands>(null); |
| 303 | const iconColor = useThemeColor("--color-icon"); | 311 | const iconColor = useUniwindTheme()["--color-icon"]; |
| 304 | // Thread List v2 lays the list out in fixed creation order, so the | 312 | // Thread List v2 lays the list out in fixed creation order, so the |
| 305 | // sort/group filter controls would be silently ignored — hide them and | 313 | // sort/group filter controls would be silently ignored — hide them and |
| 306 | // key the "customized" icon state off the environment filter alone. | 314 | // key the "customized" icon state off the environment filter alone. |
apps/mobile/src/features/home/HomeScreen.tsx+1-3| ... | @@ -23,7 +23,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | ... | @@ -23,7 +23,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; |
| 23 | import { ActivityIndicator, FlatList, Platform, Pressable, View } from "react-native"; | 23 | import { ActivityIndicator, FlatList, Platform, Pressable, View } from "react-native"; |
| 24 | import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable"; | 24 | import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable"; |
| 25 | import { useSafeAreaInsets } from "react-native-safe-area-context"; | 25 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 26 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 27 | 26 | ||
| 28 | import { AppText as Text } from "../../components/AppText"; | 27 | import { AppText as Text } from "../../components/AppText"; |
| 29 | import { EmptyState } from "../../components/EmptyState"; | 28 | import { EmptyState } from "../../components/EmptyState"; |
| ... | @@ -216,7 +215,6 @@ export function HomeScreen(props: HomeScreenProps) { | ... | @@ -216,7 +215,6 @@ export function HomeScreen(props: HomeScreenProps) { |
| 216 | const openSwipeableRef = useRef<SwipeableMethods | null>(null); | 215 | const openSwipeableRef = useRef<SwipeableMethods | null>(null); |
| 217 | const listRef = useRef<LegendListRef | null>(null); | 216 | const listRef = useRef<LegendListRef | null>(null); |
| 218 | const insets = useSafeAreaInsets(); | 217 | const insets = useSafeAreaInsets(); |
| 219 | const accentColor = useThemeColor("--color-icon-muted"); | ||
| 220 | const iosBottomToolbarClearance = | 218 | const iosBottomToolbarClearance = |
| 221 | Platform.OS === "ios" && !NATIVE_LIQUID_GLASS_SUPPORTED | 219 | Platform.OS === "ios" && !NATIVE_LIQUID_GLASS_SUPPORTED |
| 222 | ? PRE_LIQUID_GLASS_BOTTOM_TOOLBAR_HEIGHT | 220 | ? PRE_LIQUID_GLASS_BOTTOM_TOOLBAR_HEIGHT |
| ... | @@ -1088,7 +1086,7 @@ export function HomeScreen(props: HomeScreenProps) { | ... | @@ -1088,7 +1086,7 @@ export function HomeScreen(props: HomeScreenProps) { |
| 1088 | /> | 1086 | /> |
| 1089 | {emptyState.loading ? ( | 1087 | {emptyState.loading ? ( |
| 1090 | <View className="mt-4 items-center"> | 1088 | <View className="mt-4 items-center"> |
| 1091 | <ActivityIndicator color={accentColor} /> | 1089 | <ActivityIndicator colorClassName={"accent-icon-muted"} /> |
| 1092 | </View> | 1090 | </View> |
| 1093 | ) : null} | 1091 | ) : null} |
| 1094 | </View> | 1092 | </View> |
apps/mobile/src/features/home/WorkspaceConnectionTitle.tsx+2-4| ... | @@ -8,7 +8,6 @@ import { ActivityIndicator, Animated, Platform, Pressable, View } from "react-na | ... | @@ -8,7 +8,6 @@ import { ActivityIndicator, Animated, Platform, Pressable, View } from "react-na |
| 8 | import { SymbolView } from "../../components/AppSymbol"; | 8 | import { SymbolView } from "../../components/AppSymbol"; |
| 9 | import { AppText as Text } from "../../components/AppText"; | 9 | import { AppText as Text } from "../../components/AppText"; |
| 10 | import { brandTitleOffset, CompactBrandTitle } from "../../components/CompactBrandTitle"; | 10 | import { brandTitleOffset, CompactBrandTitle } from "../../components/CompactBrandTitle"; |
| 11 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 12 | import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass"; | 11 | import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass"; |
| 13 | import { useWorkspaceState } from "../../state/workspace"; | 12 | import { useWorkspaceState } from "../../state/workspace"; |
| 14 | import { | 13 | import { |
| ... | @@ -98,7 +97,6 @@ export function WorkspaceConnectionTitle(props: { | ... | @@ -98,7 +97,6 @@ export function WorkspaceConnectionTitle(props: { |
| 98 | /** Horizontal correction so the status aligns with the brand in native title slots. */ | 97 | /** Horizontal correction so the status aligns with the brand in native title slots. */ |
| 99 | readonly statusOffset?: number; | 98 | readonly statusOffset?: number; |
| 100 | }) { | 99 | }) { |
| 101 | const iconColor = String(useThemeColor("--color-icon-muted")); | ||
| 102 | const status = useDelayedConnectionStatus(); | 100 | const status = useDelayedConnectionStatus(); |
| 103 | const size = props.size ?? "navbar"; | 101 | const size = props.size ?? "navbar"; |
| 104 | 102 | ||
| ... | @@ -125,12 +123,12 @@ export function WorkspaceConnectionTitle(props: { | ... | @@ -125,12 +123,12 @@ export function WorkspaceConnectionTitle(props: { |
| 125 | style={{ marginLeft: props.statusOffset ?? 0 }} | 123 | style={{ marginLeft: props.statusOffset ?? 0 }} |
| 126 | > | 124 | > |
| 127 | {status.showsProgress ? ( | 125 | {status.showsProgress ? ( |
| 128 | <ActivityIndicator color={iconColor} size="small" /> | 126 | <ActivityIndicator colorClassName={"accent-icon-muted"} size="small" /> |
| 129 | ) : ( | 127 | ) : ( |
| 130 | <SymbolView | 128 | <SymbolView |
| 131 | name="wifi.slash" | 129 | name="wifi.slash" |
| 132 | size={size === "pageTitle" ? 17 : 15} | 130 | size={size === "pageTitle" ? 17 : 15} |
| 133 | tintColor={iconColor} | 131 | tintColorClassName={"accent-icon-muted"} |
| 134 | type="monochrome" | 132 | type="monochrome" |
| 135 | /> | 133 | /> |
| 136 | )} | 134 | )} |
apps/mobile/src/features/layout/WorkspaceEmptyDetail.tsx+6-4| ... | @@ -2,15 +2,17 @@ import { SymbolView } from "../../components/AppSymbol"; | ... | @@ -2,15 +2,17 @@ import { SymbolView } from "../../components/AppSymbol"; |
| 2 | import { Pressable, View } from "react-native"; | 2 | import { Pressable, View } from "react-native"; |
| 3 | 3 | ||
| 4 | import { AppText as Text } from "../../components/AppText"; | 4 | import { AppText as Text } from "../../components/AppText"; |
| 5 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 6 | 5 | ||
| 7 | export function WorkspaceEmptyDetail(props: { readonly onStartNewTask?: () => void }) { | 6 | export function WorkspaceEmptyDetail(props: { readonly onStartNewTask?: () => void }) { |
| 8 | const iconColor = useThemeColor("--color-icon-subtle"); | ||
| 9 | |||
| 10 | return ( | 7 | return ( |
| 11 | <View className="flex-1 items-center justify-center bg-screen px-10"> | 8 | <View className="flex-1 items-center justify-center bg-screen px-10"> |
| 12 | <View className="max-w-[360px] items-center gap-3"> | 9 | <View className="max-w-[360px] items-center gap-3"> |
| 13 | <SymbolView name="sidebar.left" size={34} tintColor={iconColor} type="hierarchical" /> | 10 | <SymbolView |
| 11 | name="sidebar.left" | ||
| 12 | size={34} | ||
| 13 | tintColorClassName={"accent-icon-subtle"} | ||
| 14 | type="hierarchical" | ||
| 15 | /> | ||
| 14 | <Text className="text-center text-xl font-t3-bold">Select a thread</Text> | 16 | <Text className="text-center text-xl font-t3-bold">Select a thread</Text> |
| 15 | <Text className="text-center text-base text-foreground-muted"> | 17 | <Text className="text-center text-base text-foreground-muted"> |
| 16 | Choose a thread from the sidebar or start a new task. | 18 | Choose a thread from the sidebar or start a new task. |
apps/mobile/src/features/layout/workspace-pane-divider.tsx+6-10| ... | @@ -2,7 +2,7 @@ import { useCallback, useMemo, useRef, useState } from "react"; | ... | @@ -2,7 +2,7 @@ import { useCallback, useMemo, useRef, useState } from "react"; |
| 2 | import { Pressable, StyleSheet, View, type AccessibilityActionEvent } from "react-native"; | 2 | import { Pressable, StyleSheet, View, type AccessibilityActionEvent } from "react-native"; |
| 3 | import { Gesture, GestureDetector } from "react-native-gesture-handler"; | 3 | import { Gesture, GestureDetector } from "react-native-gesture-handler"; |
| 4 | import { runOnJS } from "react-native-reanimated"; | 4 | import { runOnJS } from "react-native-reanimated"; |
| 5 | import { useThemeColor } from "../../lib/useThemeColor"; | 5 | import { cn } from "../../lib/cn"; |
| 6 | 6 | ||
| 7 | const ACCESSIBILITY_RESIZE_STEP = 24; | 7 | const ACCESSIBILITY_RESIZE_STEP = 24; |
| 8 | 8 | ||
| ... | @@ -22,8 +22,6 @@ export function WorkspacePaneDivider(props: WorkspacePaneDividerProps) { | ... | @@ -22,8 +22,6 @@ export function WorkspacePaneDivider(props: WorkspacePaneDividerProps) { |
| 22 | latestProps.current = props; | 22 | latestProps.current = props; |
| 23 | const [hovered, setHovered] = useState(false); | 23 | const [hovered, setHovered] = useState(false); |
| 24 | const [dragging, setDragging] = useState(false); | 24 | const [dragging, setDragging] = useState(false); |
| 25 | const dividerColor = useThemeColor("--color-border"); | ||
| 26 | const activeDividerColor = useThemeColor("--color-primary"); | ||
| 27 | const handleResizeStart = useCallback(() => { | 25 | const handleResizeStart = useCallback(() => { |
| 28 | setDragging(true); | 26 | setDragging(true); |
| 29 | latestProps.current.onResizeStart?.(); | 27 | latestProps.current.onResizeStart?.(); |
| ... | @@ -81,11 +79,11 @@ export function WorkspacePaneDivider(props: WorkspacePaneDividerProps) { | ... | @@ -81,11 +79,11 @@ export function WorkspacePaneDivider(props: WorkspacePaneDividerProps) { |
| 81 | onHoverOut={() => setHovered(false)} | 79 | onHoverOut={() => setHovered(false)} |
| 82 | > | 80 | > |
| 83 | <View | 81 | <View |
| 84 | style={[ | 82 | className={cn( |
| 85 | styles.line, | 83 | "h-full self-center bg-border opacity-70", |
| 86 | { backgroundColor: dividerColor }, | 84 | hovered || dragging ? "w-0.5 bg-primary opacity-100" : "w-px", |
| 87 | (hovered || dragging) && [styles.activeLine, { backgroundColor: activeDividerColor }], | 85 | )} |
| 88 | ]} | 86 | style={[styles.line, (hovered || dragging) && styles.activeLine]} |
| 89 | /> | 87 | /> |
| 90 | </Pressable> | 88 | </Pressable> |
| 91 | </GestureDetector> | 89 | </GestureDetector> |
| ... | @@ -96,11 +94,9 @@ const styles = StyleSheet.create({ | ... | @@ -96,11 +94,9 @@ const styles = StyleSheet.create({ |
| 96 | line: { | 94 | line: { |
| 97 | alignSelf: "center", | 95 | alignSelf: "center", |
| 98 | height: "100%", | 96 | height: "100%", |
| 99 | opacity: 0.7, | ||
| 100 | width: StyleSheet.hairlineWidth, | 97 | width: StyleSheet.hairlineWidth, |
| 101 | }, | 98 | }, |
| 102 | activeLine: { | 99 | activeLine: { |
| 103 | opacity: 1, | ||
| 104 | width: 2, | 100 | width: 2, |
| 105 | }, | 101 | }, |
| 106 | }); | 102 | }); |
apps/mobile/src/features/projects/AddProjectScreen.tsx+25-20| ... | @@ -51,7 +51,6 @@ import { sourceControlEnvironment } from "../../state/sourceControl"; | ... | @@ -51,7 +51,6 @@ import { sourceControlEnvironment } from "../../state/sourceControl"; |
| 51 | import { AppText as Text, AppTextInput as TextInput } from "../../components/AppText"; | 51 | import { AppText as Text, AppTextInput as TextInput } from "../../components/AppText"; |
| 52 | import { ErrorBanner } from "../../components/ErrorBanner"; | 52 | import { ErrorBanner } from "../../components/ErrorBanner"; |
| 53 | import { SourceControlIcon } from "../../components/SourceControlIcon"; | 53 | import { SourceControlIcon } from "../../components/SourceControlIcon"; |
| 54 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 55 | import { uuidv4 } from "../../lib/uuid"; | 54 | import { uuidv4 } from "../../lib/uuid"; |
| 56 | import { useAtomCommand } from "../../state/use-atom-command"; | 55 | import { useAtomCommand } from "../../state/use-atom-command"; |
| 57 | import { useAtomQueryRunner } from "../../state/use-atom-query-runner"; | 56 | import { useAtomQueryRunner } from "../../state/use-atom-query-runner"; |
| ... | @@ -159,8 +158,6 @@ function ListRow(props: { | ... | @@ -159,8 +158,6 @@ function ListRow(props: { |
| 159 | readonly right?: ReactNode; | 158 | readonly right?: ReactNode; |
| 160 | readonly onPress?: () => void; | 159 | readonly onPress?: () => void; |
| 161 | }) { | 160 | }) { |
| 162 | const chevronColor = useThemeColor("--color-chevron"); | ||
| 163 | |||
| 164 | return ( | 161 | return ( |
| 165 | <Pressable | 162 | <Pressable |
| 166 | disabled={props.disabled} | 163 | disabled={props.disabled} |
| ... | @@ -192,7 +189,12 @@ function ListRow(props: { | ... | @@ -192,7 +189,12 @@ function ListRow(props: { |
| 192 | {"right" in props ? ( | 189 | {"right" in props ? ( |
| 193 | props.right | 190 | props.right |
| 194 | ) : !props.disabled ? ( | 191 | ) : !props.disabled ? ( |
| 195 | <SymbolView name="chevron.right" size={13} tintColor={chevronColor} type="monochrome" /> | 192 | <SymbolView |
| 193 | name="chevron.right" | ||
| 194 | size={13} | ||
| 195 | tintColorClassName={"accent-chevron"} | ||
| 196 | type="monochrome" | ||
| 197 | /> | ||
| 196 | ) : null} | 198 | ) : null} |
| 197 | </View> | 199 | </View> |
| 198 | </Pressable> | 200 | </Pressable> |
| ... | @@ -205,8 +207,6 @@ function PrimaryActionButton(props: { | ... | @@ -205,8 +207,6 @@ function PrimaryActionButton(props: { |
| 205 | readonly loading?: boolean; | 207 | readonly loading?: boolean; |
| 206 | readonly onPress: () => void; | 208 | readonly onPress: () => void; |
| 207 | }) { | 209 | }) { |
| 208 | const primaryForeground = useThemeColor("--color-primary-foreground"); | ||
| 209 | |||
| 210 | return ( | 210 | return ( |
| 211 | <Pressable | 211 | <Pressable |
| 212 | disabled={props.disabled} | 212 | disabled={props.disabled} |
| ... | @@ -214,7 +214,7 @@ function PrimaryActionButton(props: { | ... | @@ -214,7 +214,7 @@ function PrimaryActionButton(props: { |
| 214 | className="h-12 items-center justify-center rounded-full bg-primary active:opacity-70 disabled:opacity-45" | 214 | className="h-12 items-center justify-center rounded-full bg-primary active:opacity-70 disabled:opacity-45" |
| 215 | > | 215 | > |
| 216 | {props.loading ? ( | 216 | {props.loading ? ( |
| 217 | <ActivityIndicator color={String(primaryForeground)} /> | 217 | <ActivityIndicator colorClassName={String("accent-primary-foreground")} /> |
| 218 | ) : ( | 218 | ) : ( |
| 219 | <Text className="text-base font-t3-bold text-primary-foreground">{props.label}</Text> | 219 | <Text className="text-base font-t3-bold text-primary-foreground">{props.label}</Text> |
| 220 | )} | 220 | )} |
| ... | @@ -414,7 +414,6 @@ function SourceControlRow(props: { | ... | @@ -414,7 +414,6 @@ function SourceControlRow(props: { |
| 414 | readonly isFirst: boolean; | 414 | readonly isFirst: boolean; |
| 415 | }) { | 415 | }) { |
| 416 | const navigation = useNavigation(); | 416 | const navigation = useNavigation(); |
| 417 | const iconColor = useThemeColor("--color-icon"); | ||
| 418 | const title = | 417 | const title = |
| 419 | props.source === "url" ? "Git URL" : `${addProjectRemoteSourceLabel(props.source)} repository`; | 418 | props.source === "url" ? "Git URL" : `${addProjectRemoteSourceLabel(props.source)} repository`; |
| 420 | const subtitle = | 419 | const subtitle = |
| ... | @@ -423,9 +422,9 @@ function SourceControlRow(props: { | ... | @@ -423,9 +422,9 @@ function SourceControlRow(props: { |
| 423 | : `Clone ${addProjectRemoteSourceLabel(props.source)} ${props.hint}`; | 422 | : `Clone ${addProjectRemoteSourceLabel(props.source)} ${props.hint}`; |
| 424 | const icon = | 423 | const icon = |
| 425 | props.source === "url" ? ( | 424 | props.source === "url" ? ( |
| 426 | <SymbolView name="link" size={17} tintColor={iconColor} type="monochrome" /> | 425 | <SymbolView name="link" size={17} tintColorClassName={"accent-icon"} type="monochrome" /> |
| 427 | ) : ( | 426 | ) : ( |
| 428 | <SourceControlIcon kind={props.source} size={18} color={String(iconColor)} /> | 427 | <SourceControlIcon kind={props.source} size={18} colorClassName="accent-icon" /> |
| 429 | ); | 428 | ); |
| 430 | 429 | ||
| 431 | if (!props.ready) { | 430 | if (!props.ready) { |
| ... | @@ -454,8 +453,6 @@ function SourceControlRow(props: { | ... | @@ -454,8 +453,6 @@ function SourceControlRow(props: { |
| 454 | 453 | ||
| 455 | export function AddProjectSourceScreen() { | 454 | export function AddProjectSourceScreen() { |
| 456 | const navigation = useNavigation(); | 455 | const navigation = useNavigation(); |
| 457 | const accentColor = useThemeColor("--color-icon-muted"); | ||
| 458 | const iconColor = useThemeColor("--color-icon"); | ||
| 459 | const { environmentOptions, selectedEnvironment, setSelectedEnvironmentId } = | 456 | const { environmentOptions, selectedEnvironment, setSelectedEnvironmentId } = |
| 460 | useSelectedEnvironment(); | 457 | useSelectedEnvironment(); |
| 461 | const discoveryState = useEnvironmentQuery( | 458 | const discoveryState = useEnvironmentQuery( |
| ... | @@ -496,7 +493,7 @@ export function AddProjectSourceScreen() { | ... | @@ -496,7 +493,7 @@ export function AddProjectSourceScreen() { |
| 496 | <SymbolView | 493 | <SymbolView |
| 497 | name="server.rack" | 494 | name="server.rack" |
| 498 | size={17} | 495 | size={17} |
| 499 | tintColor={iconColor} | 496 | tintColorClassName={"accent-icon"} |
| 500 | type="monochrome" | 497 | type="monochrome" |
| 501 | /> | 498 | /> |
| 502 | } | 499 | } |
| ... | @@ -508,7 +505,7 @@ export function AddProjectSourceScreen() { | ... | @@ -508,7 +505,7 @@ export function AddProjectSourceScreen() { |
| 508 | <SymbolView | 505 | <SymbolView |
| 509 | name="checkmark" | 506 | name="checkmark" |
| 510 | size={14} | 507 | size={14} |
| 511 | tintColor={iconColor} | 508 | tintColorClassName={"accent-icon"} |
| 512 | type="monochrome" | 509 | type="monochrome" |
| 513 | /> | 510 | /> |
| 514 | ) : null | 511 | ) : null |
| ... | @@ -530,7 +527,7 @@ export function AddProjectSourceScreen() { | ... | @@ -530,7 +527,7 @@ export function AddProjectSourceScreen() { |
| 530 | <SymbolView | 527 | <SymbolView |
| 531 | name="folder.badge.plus" | 528 | name="folder.badge.plus" |
| 532 | size={17} | 529 | size={17} |
| 533 | tintColor={iconColor} | 530 | tintColorClassName={"accent-icon"} |
| 534 | type="monochrome" | 531 | type="monochrome" |
| 535 | /> | 532 | /> |
| 536 | } | 533 | } |
| ... | @@ -560,7 +557,9 @@ export function AddProjectSourceScreen() { | ... | @@ -560,7 +557,9 @@ export function AddProjectSourceScreen() { |
| 560 | ), | 557 | ), |
| 561 | )} | 558 | )} |
| 562 | </ListSection> | 559 | </ListSection> |
| 563 | {discoveryState.isPending ? <ActivityIndicator color={accentColor} /> : null} | 560 | {discoveryState.isPending ? ( |
| 561 | <ActivityIndicator colorClassName={"accent-icon-muted"} /> | ||
| 562 | ) : null} | ||
| 564 | </> | 563 | </> |
| 565 | ) : null} | 564 | ) : null} |
| 566 | </AddProjectShell> | 565 | </AddProjectShell> |
| ... | @@ -745,7 +744,6 @@ function FolderBrowser(props: { | ... | @@ -745,7 +744,6 @@ function FolderBrowser(props: { |
| 745 | }) => Promise<boolean>; | 744 | }) => Promise<boolean>; |
| 746 | readonly pinnedDirectoryName?: string; | 745 | readonly pinnedDirectoryName?: string; |
| 747 | }) { | 746 | }) { |
| 748 | const accentColor = useThemeColor("--color-icon-muted"); | ||
| 749 | const browsePath = useMemo( | 747 | const browsePath = useMemo( |
| 750 | () => getFilesystemBrowsePath(props.pathInput, props.environment.platform), | 748 | () => getFilesystemBrowsePath(props.pathInput, props.environment.platform), |
| 751 | [props.environment.platform, props.pathInput], | 749 | [props.environment.platform, props.pathInput], |
| ... | @@ -781,7 +779,7 @@ function FolderBrowser(props: { | ... | @@ -781,7 +779,7 @@ function FolderBrowser(props: { |
| 781 | <ListSection> | 779 | <ListSection> |
| 782 | {browseState.isPending && browseState.data === null ? ( | 780 | {browseState.isPending && browseState.data === null ? ( |
| 783 | <View className="items-center py-5"> | 781 | <View className="items-center py-5"> |
| 784 | <ActivityIndicator color={accentColor} /> | 782 | <ActivityIndicator colorClassName={"accent-icon-muted"} /> |
| 785 | </View> | 783 | </View> |
| 786 | ) : null} | 784 | ) : null} |
| 787 | {browsePath.canBrowseUp ? ( | 785 | {browsePath.canBrowseUp ? ( |
| ... | @@ -791,7 +789,7 @@ function FolderBrowser(props: { | ... | @@ -791,7 +789,7 @@ function FolderBrowser(props: { |
| 791 | <SymbolView | 789 | <SymbolView |
| 792 | name="arrow.turn.left.up" | 790 | name="arrow.turn.left.up" |
| 793 | size={17} | 791 | size={17} |
| 794 | tintColor={accentColor} | 792 | tintColorClassName={"accent-icon-muted"} |
| 795 | type="monochrome" | 793 | type="monochrome" |
| 796 | /> | 794 | /> |
| 797 | } | 795 | } |
| ... | @@ -810,7 +808,14 @@ function FolderBrowser(props: { | ... | @@ -810,7 +808,14 @@ function FolderBrowser(props: { |
| 810 | <ListRow | 808 | <ListRow |
| 811 | key={entry.fullPath} | 809 | key={entry.fullPath} |
| 812 | title={entry.name} | 810 | title={entry.name} |
| 813 | icon={<SymbolView name="folder" size={17} tintColor={accentColor} type="monochrome" />} | 811 | icon={ |
| 812 | <SymbolView | ||
| 813 | name="folder" | ||
| 814 | size={17} | ||
| 815 | tintColorClassName={"accent-icon-muted"} | ||
| 816 | type="monochrome" | ||
| 817 | /> | ||
| 818 | } | ||
| 814 | isFirst={index === 0 && !browsePath.canBrowseUp} | 819 | isFirst={index === 0 && !browsePath.canBrowseUp} |
| 815 | right={null} | 820 | right={null} |
| 816 | onPress={() => { | 821 | onPress={() => { |
apps/mobile/src/features/review/ReviewCommentComposerSheet.tsx+6-3| ... | @@ -14,7 +14,6 @@ import { ControlPill } from "../../components/ControlPill"; | ... | @@ -14,7 +14,6 @@ import { ControlPill } from "../../components/ControlPill"; |
| 14 | import { cn } from "../../lib/cn"; | 14 | import { cn } from "../../lib/cn"; |
| 15 | import type { DraftComposerImageAttachment } from "../../lib/composerImages"; | 15 | import type { DraftComposerImageAttachment } from "../../lib/composerImages"; |
| 16 | import { convertPastedImagesToAttachments, pickComposerImages } from "../../lib/composerImages"; | 16 | import { convertPastedImagesToAttachments, pickComposerImages } from "../../lib/composerImages"; |
| 17 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 18 | import { useNativePaste } from "../../lib/useNativePaste"; | 17 | import { useNativePaste } from "../../lib/useNativePaste"; |
| 19 | import { setPendingConnectionError } from "../../state/use-remote-environment-registry"; | 18 | import { setPendingConnectionError } from "../../state/use-remote-environment-registry"; |
| 20 | import { appendReviewCommentToDraft } from "../../state/use-thread-composer-state"; | 19 | import { appendReviewCommentToDraft } from "../../state/use-thread-composer-state"; |
| ... | @@ -46,7 +45,6 @@ export function ReviewCommentComposerSheet(props: ReviewCommentComposerSheetProp | ... | @@ -46,7 +45,6 @@ export function ReviewCommentComposerSheet(props: ReviewCommentComposerSheetProp |
| 46 | const insets = useSafeAreaInsets(); | 45 | const insets = useSafeAreaInsets(); |
| 47 | const { width } = useWindowDimensions(); | 46 | const { width } = useWindowDimensions(); |
| 48 | const { themeAppearance: selectedTheme } = useAppearancePreferences(); | 47 | const { themeAppearance: selectedTheme } = useAppearancePreferences(); |
| 49 | const iconTint = String(useThemeColor("--color-icon")); | ||
| 50 | const target = useReviewCommentTarget(); | 48 | const target = useReviewCommentTarget(); |
| 51 | const { codeSurface } = useAppearanceCodeSurface(); | 49 | const { codeSurface } = useAppearanceCodeSurface(); |
| 52 | const { environmentId, threadId } = props.route.params; | 50 | const { environmentId, threadId } = props.route.params; |
| ... | @@ -168,7 +166,12 @@ export function ReviewCommentComposerSheet(props: ReviewCommentComposerSheetProp | ... | @@ -168,7 +166,12 @@ export function ReviewCommentComposerSheet(props: ReviewCommentComposerSheetProp |
| 168 | className="bg-subtle h-12 w-12 items-center justify-center rounded-full" | 166 | className="bg-subtle h-12 w-12 items-center justify-center rounded-full" |
| 169 | onPress={dismissComposer} | 167 | onPress={dismissComposer} |
| 170 | > | 168 | > |
| 171 | <SymbolView name="xmark" size={18} tintColor={iconTint} type="monochrome" /> | 169 | <SymbolView |
| 170 | name="xmark" | ||
| 171 | size={18} | ||
| 172 | tintColorClassName={"accent-icon"} | ||
| 173 | type="monochrome" | ||
| 174 | /> | ||
| 172 | </Pressable> | 175 | </Pressable> |
| 173 | 176 | ||
| 174 | <Text className="text-lg font-t3-bold text-foreground">Add Comment</Text> | 177 | <Text className="text-lg font-t3-bold text-foreground">Add Comment</Text> |
apps/mobile/src/features/review/ReviewSheet.tsx+15-12| ... | @@ -37,7 +37,7 @@ import { ControlPillMenu } from "../../components/ControlPill"; | ... | @@ -37,7 +37,7 @@ import { ControlPillMenu } from "../../components/ControlPill"; |
| 37 | import { environmentCatalog } from "../../connection/catalog"; | 37 | import { environmentCatalog } from "../../connection/catalog"; |
| 38 | import { useEnvironmentPresentation } from "../../state/presentation"; | 38 | import { useEnvironmentPresentation } from "../../state/presentation"; |
| 39 | import { useAtomCommand } from "../../state/use-atom-command"; | 39 | import { useAtomCommand } from "../../state/use-atom-command"; |
| 40 | import { useThemeColor } from "../../lib/useThemeColor"; | 40 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 41 | import { IOS_NAV_BAR_HEIGHT } from "../../lib/layoutMetrics"; | 41 | import { IOS_NAV_BAR_HEIGHT } from "../../lib/layoutMetrics"; |
| 42 | import { useThreadDraftForThread } from "../../state/use-thread-composer-state"; | 42 | import { useThreadDraftForThread } from "../../state/use-thread-composer-state"; |
| 43 | import { EnvironmentConnectionNotice } from "../connection/EnvironmentConnectionNotice"; | 43 | import { EnvironmentConnectionNotice } from "../connection/EnvironmentConnectionNotice"; |
| ... | @@ -80,13 +80,11 @@ const SHOWCASE_ENABLED = process.env.EXPO_PUBLIC_SHOWCASE === "1"; | ... | @@ -80,13 +80,11 @@ const SHOWCASE_ENABLED = process.env.EXPO_PUBLIC_SHOWCASE === "1"; |
| 80 | 80 | ||
| 81 | const ReviewNotice = memo(function ReviewNotice(props: { readonly notice: string }) { | 81 | const ReviewNotice = memo(function ReviewNotice(props: { readonly notice: string }) { |
| 82 | return ( | 82 | return ( |
| 83 | <View className="border-b border-amber-200 bg-amber-50 px-4 py-3 dark:border-amber-900/60 dark:bg-amber-950/40"> | 83 | <View className="border-b border-adaptive-amber-200-900-a60 bg-adaptive-amber-50-950-a40 px-4 py-3"> |
| 84 | <Text className="text-xs font-t3-bold uppercase text-amber-700 dark:text-amber-300"> | 84 | <Text className="text-xs font-t3-bold uppercase text-adaptive-amber-700-300"> |
| 85 | Partial diff | 85 | Partial diff |
| 86 | </Text> | 86 | </Text> |
| 87 | <Text className="text-xs leading-normal text-amber-800 dark:text-amber-200"> | 87 | <Text className="text-xs leading-normal text-adaptive-amber-800-200">{props.notice}</Text> |
| 88 | {props.notice} | ||
| 89 | </Text> | ||
| 90 | </View> | 88 | </View> |
| 91 | ); | 89 | ); |
| 92 | }); | 90 | }); |
| ... | @@ -97,7 +95,6 @@ function ReviewSelectionActionBar(props: { | ... | @@ -97,7 +95,6 @@ function ReviewSelectionActionBar(props: { |
| 97 | readonly onOpenComment: (() => void) | null; | 95 | readonly onOpenComment: (() => void) | null; |
| 98 | readonly onClear: () => void; | 96 | readonly onClear: () => void; |
| 99 | }) { | 97 | }) { |
| 100 | const foreground = useThemeColor("--color-primary-foreground"); | ||
| 101 | if (!props.title) { | 98 | if (!props.title) { |
| 102 | return null; | 99 | return null; |
| 103 | } | 100 | } |
| ... | @@ -107,7 +104,7 @@ function ReviewSelectionActionBar(props: { | ... | @@ -107,7 +104,7 @@ function ReviewSelectionActionBar(props: { |
| 107 | <SymbolView | 104 | <SymbolView |
| 108 | name={props.onOpenComment ? "text.bubble" : "line.3.horizontal.decrease.circle"} | 105 | name={props.onOpenComment ? "text.bubble" : "line.3.horizontal.decrease.circle"} |
| 109 | size={16} | 106 | size={16} |
| 110 | tintColor={foreground} | 107 | tintColorClassName={"accent-primary-foreground"} |
| 111 | type="monochrome" | 108 | type="monochrome" |
| 112 | /> | 109 | /> |
| 113 | <Text className="text-base font-t3-bold text-primary-foreground">{props.title}</Text> | 110 | <Text className="text-base font-t3-bold text-primary-foreground">{props.title}</Text> |
| ... | @@ -144,7 +141,12 @@ function ReviewSelectionActionBar(props: { | ... | @@ -144,7 +141,12 @@ function ReviewSelectionActionBar(props: { |
| 144 | className="h-12 w-12 items-center justify-center rounded-full bg-primary" | 141 | className="h-12 w-12 items-center justify-center rounded-full bg-primary" |
| 145 | onPress={props.onClear} | 142 | onPress={props.onClear} |
| 146 | > | 143 | > |
| 147 | <SymbolView name="xmark" size={16} tintColor={foreground} type="monochrome" /> | 144 | <SymbolView |
| 145 | name="xmark" | ||
| 146 | size={16} | ||
| 147 | tintColorClassName={"accent-primary-foreground"} | ||
| 148 | type="monochrome" | ||
| 149 | /> | ||
| 148 | </Pressable> | 150 | </Pressable> |
| 149 | </View> | 151 | </View> |
| 150 | ); | 152 | ); |
| ... | @@ -217,8 +219,9 @@ function ReviewFileNavigator({ | ... | @@ -217,8 +219,9 @@ function ReviewFileNavigator({ |
| 217 | ref, | 219 | ref, |
| 218 | }: ReviewFileNavigatorProps) { | 220 | }: ReviewFileNavigatorProps) { |
| 219 | const insets = useSafeAreaInsets(); | 221 | const insets = useSafeAreaInsets(); |
| 220 | const sheetColor = String(useThemeColor("--color-sheet")); | 222 | const theme = useUniwindTheme(); |
| 221 | const foregroundColor = String(useThemeColor("--color-foreground")); | 223 | const sheetColor = theme["--color-sheet"]; |
| 224 | const foregroundColor = theme["--color-foreground"]; | ||
| 222 | const headerScrollEdgeEffects = nativeHeaderScrollEdgeEffects(Platform.OS, Platform.Version); | 225 | const headerScrollEdgeEffects = nativeHeaderScrollEdgeEffects(Platform.OS, Platform.Version); |
| 223 | const [fileSelection, setFileSelection] = useState<{ | 226 | const [fileSelection, setFileSelection] = useState<{ |
| 224 | readonly sectionId: string | null; | 227 | readonly sectionId: string | null; |
| ... | @@ -348,7 +351,7 @@ export function ReviewSheet(props: ReviewSheetProps) { | ... | @@ -348,7 +351,7 @@ export function ReviewSheet(props: ReviewSheetProps) { |
| 348 | const navigation = useNavigation(); | 351 | const navigation = useNavigation(); |
| 349 | const insets = useSafeAreaInsets(); | 352 | const insets = useSafeAreaInsets(); |
| 350 | const { themeAppearance: selectedTheme } = useAppearancePreferences(); | 353 | const { themeAppearance: selectedTheme } = useAppearancePreferences(); |
| 351 | const headerIcon = String(useThemeColor("--color-icon")); | 354 | const headerIcon = String(useUniwindTheme()["--color-icon"]); |
| 352 | const { environmentId, threadId } = props.route.params; | 355 | const { environmentId, threadId } = props.route.params; |
| 353 | const environment = useEnvironmentPresentation(environmentId); | 356 | const environment = useEnvironmentPresentation(environmentId); |
| 354 | const retryEnvironment = useAtomCommand(environmentCatalog.retryNow, "environment retry"); | 357 | const retryEnvironment = useAtomCommand(environmentCatalog.retryNow, "environment retry"); |
apps/mobile/src/features/review/nativeReviewDiffAdapter.test.ts+21-4| ... | @@ -1,5 +1,12 @@ | ... | @@ -1,5 +1,12 @@ |
| 1 | import { describe, expect, it } from "vite-plus/test"; | 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | import { MOBILE_THEME_IDS } from "../../lib/mobileTheme"; | 2 | import { |
| 3 | DEFAULT_MOBILE_THEME_ID, | ||
| 4 | getMobileThemeVariables, | ||
| 5 | MOBILE_THEME_IDS, | ||
| 6 | type MobileThemeAppearance, | ||
| 7 | type MobileThemeId, | ||
| 8 | } from "../../lib/mobileTheme"; | ||
| 9 | import { readDefaultMobileThemeVariables } from "../../lib/mobileTheme.test-support"; | ||
| 3 | 10 | ||
| 4 | import { | 11 | import { |
| 5 | createNativeReviewDiffTheme, | 12 | createNativeReviewDiffTheme, |
| ... | @@ -39,6 +46,12 @@ function buildInput(comments: BuildNativeReviewDiffDataInput["comments"]) { | ... | @@ -39,6 +46,12 @@ function buildInput(comments: BuildNativeReviewDiffDataInput["comments"]) { |
| 39 | return { parsedDiff, comments } satisfies BuildNativeReviewDiffDataInput; | 46 | return { parsedDiff, comments } satisfies BuildNativeReviewDiffDataInput; |
| 40 | } | 47 | } |
| 41 | 48 | ||
| 49 | function appTheme(themeId: MobileThemeId, appearance: MobileThemeAppearance) { | ||
| 50 | return themeId === DEFAULT_MOBILE_THEME_ID | ||
| 51 | ? readDefaultMobileThemeVariables(appearance) | ||
| 52 | : getMobileThemeVariables(themeId, appearance); | ||
| 53 | } | ||
| 54 | |||
| 42 | describe("getCachedNativeReviewDiffData", () => { | 55 | describe("getCachedNativeReviewDiffData", () => { |
| 43 | it("reuses the row model for equivalent empty comment arrays", () => { | 56 | it("reuses the row model for equivalent empty comment arrays", () => { |
| 44 | const first = getCachedNativeReviewDiffData(buildInput([])); | 57 | const first = getCachedNativeReviewDiffData(buildInput([])); |
| ... | @@ -61,7 +74,11 @@ describe("createNativeReviewDiffTheme", () => { | ... | @@ -61,7 +74,11 @@ describe("createNativeReviewDiffTheme", () => { |
| 61 | it("serializes every native color as cross-platform opaque hex", () => { | 74 | it("serializes every native color as cross-platform opaque hex", () => { |
| 62 | for (const themeId of MOBILE_THEME_IDS) { | 75 | for (const themeId of MOBILE_THEME_IDS) { |
| 63 | for (const appearance of ["light", "dark"] as const) { | 76 | for (const appearance of ["light", "dark"] as const) { |
| 64 | const theme = createNativeReviewDiffTheme(appearance, themeId); | 77 | const theme = createNativeReviewDiffTheme( |
| 78 | appearance, | ||
| 79 | themeId, | ||
| 80 | appTheme(themeId, appearance), | ||
| 81 | ); | ||
| 65 | for (const color of Object.values(theme)) { | 82 | for (const color of Object.values(theme)) { |
| 66 | expect(color, `${themeId}/${appearance}`).toMatch(/^#[\da-f]{6}$/i); | 83 | expect(color, `${themeId}/${appearance}`).toMatch(/^#[\da-f]{6}$/i); |
| 67 | } | 84 | } |
| ... | @@ -70,8 +87,8 @@ describe("createNativeReviewDiffTheme", () => { | ... | @@ -70,8 +87,8 @@ describe("createNativeReviewDiffTheme", () => { |
| 70 | }); | 87 | }); |
| 71 | 88 | ||
| 72 | it("uses the selected app palette for native code surfaces", () => { | 89 | it("uses the selected app palette for native code surfaces", () => { |
| 73 | const standard = createNativeReviewDiffTheme("dark", "t3-code"); | 90 | const standard = createNativeReviewDiffTheme("dark", "t3-code", appTheme("t3-code", "dark")); |
| 74 | const iris = createNativeReviewDiffTheme("dark", "iris"); | 91 | const iris = createNativeReviewDiffTheme("dark", "iris", appTheme("iris", "dark")); |
| 75 | 92 | ||
| 76 | expect(iris.background).not.toBe(standard.background); | 93 | expect(iris.background).not.toBe(standard.background); |
| 77 | expect(iris.hunkText).not.toBe(standard.hunkText); | 94 | expect(iris.hunkText).not.toBe(standard.hunkText); |
apps/mobile/src/features/review/nativeReviewDiffAdapter.ts+3-7| ... | @@ -8,11 +8,7 @@ import { pipe } from "effect/Function"; | ... | @@ -8,11 +8,7 @@ import { pipe } from "effect/Function"; |
| 8 | import type { ResolvedMobileCodeSurface } from "../../lib/appearancePreferences"; | 8 | import type { ResolvedMobileCodeSurface } from "../../lib/appearancePreferences"; |
| 9 | import { resolveMobileCodeSurface } from "../../lib/appearancePreferences"; | 9 | import { resolveMobileCodeSurface } from "../../lib/appearancePreferences"; |
| 10 | import { MOBILE_CODE_SURFACE } from "../../lib/typography"; | 10 | import { MOBILE_CODE_SURFACE } from "../../lib/typography"; |
| 11 | import { | 11 | import { type MobileThemeId, type MobileThemeVariables } from "../../lib/mobileTheme"; |
| 12 | DEFAULT_MOBILE_THEME_ID, | ||
| 13 | getMobileThemeVariables, | ||
| 14 | type MobileThemeId, | ||
| 15 | } from "../../lib/mobileTheme"; | ||
| 16 | import { getMobileTerminalTheme, type TerminalAppearanceScheme } from "../terminal/terminalTheme"; | 12 | import { getMobileTerminalTheme, type TerminalAppearanceScheme } from "../terminal/terminalTheme"; |
| 17 | import { computeWordAltDiffRanges } from "./reviewWordDiffs"; | 13 | import { computeWordAltDiffRanges } from "./reviewWordDiffs"; |
| 18 | import { | 14 | import { |
| ... | @@ -137,10 +133,10 @@ function buildReviewCommentsCacheKey(comments: ReadonlyArray<ReviewInlineComment | ... | @@ -137,10 +133,10 @@ function buildReviewCommentsCacheKey(comments: ReadonlyArray<ReviewInlineComment |
| 137 | 133 | ||
| 138 | export function createNativeReviewDiffTheme( | 134 | export function createNativeReviewDiffTheme( |
| 139 | scheme: TerminalAppearanceScheme, | 135 | scheme: TerminalAppearanceScheme, |
| 140 | themeId: MobileThemeId = DEFAULT_MOBILE_THEME_ID, | 136 | themeId: MobileThemeId, |
| 137 | appTheme: MobileThemeVariables, | ||
| 141 | ): NativeReviewDiffTheme { | 138 | ): NativeReviewDiffTheme { |
| 142 | const terminalTheme = getMobileTerminalTheme(themeId, scheme); | 139 | const terminalTheme = getMobileTerminalTheme(themeId, scheme); |
| 143 | const appTheme = getMobileThemeVariables(themeId, scheme); | ||
| 144 | const [, terminalRed] = terminalTheme.palette; | 140 | const [, terminalRed] = terminalTheme.palette; |
| 145 | // Swift expects #RRGGBB/#RRGGBBAA while Android expects #RRGGBB/#AARRGGBB. | 141 | // Swift expects #RRGGBB/#RRGGBBAA while Android expects #RRGGBB/#AARRGGBB. |
| 146 | // Flatten translucent app tokens onto the code surface so both native | 142 | // Flatten translucent app tokens onto the code surface so both native |
apps/mobile/src/features/review/useNativeReviewDiffBridge.ts+6-1| ... | @@ -6,6 +6,7 @@ import { useAppearanceCodeSurface } from "../settings/appearance/useAppearanceCo | ... | @@ -6,6 +6,7 @@ import { useAppearanceCodeSurface } from "../settings/appearance/useAppearanceCo |
| 6 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | 6 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; |
| 7 | import { useNativeReviewDiffHighlighting } from "./useNativeReviewDiffHighlighting"; | 7 | import { useNativeReviewDiffHighlighting } from "./useNativeReviewDiffHighlighting"; |
| 8 | import { buildNativeReviewTokensResetKey } from "./reviewDiffBridgeKeys"; | 8 | import { buildNativeReviewTokensResetKey } from "./reviewDiffBridgeKeys"; |
| 9 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; | ||
| 9 | 10 | ||
| 10 | export { buildNativeReviewTokensResetKey, hashReviewDiffKey } from "./reviewDiffBridgeKeys"; | 11 | export { buildNativeReviewTokensResetKey, hashReviewDiffKey } from "./reviewDiffBridgeKeys"; |
| 11 | 12 | ||
| ... | @@ -31,11 +32,15 @@ export function useNativeReviewDiffBridge(input: { | ... | @@ -31,11 +32,15 @@ export function useNativeReviewDiffBridge(input: { |
| 31 | } = input; | 32 | } = input; |
| 32 | const { nativeReviewDiffStyle } = useAppearanceCodeSurface(); | 33 | const { nativeReviewDiffStyle } = useAppearanceCodeSurface(); |
| 33 | const { themeAppearance: scheme, themeId } = useAppearancePreferences(); | 34 | const { themeAppearance: scheme, themeId } = useAppearancePreferences(); |
| 35 | const appTheme = useUniwindTheme(); | ||
| 34 | const [collapsedCommentIds, setCollapsedCommentIds] = useState<ReadonlySet<string>>( | 36 | const [collapsedCommentIds, setCollapsedCommentIds] = useState<ReadonlySet<string>>( |
| 35 | () => new Set(), | 37 | () => new Set(), |
| 36 | ); | 38 | ); |
| 37 | 39 | ||
| 38 | const theme = useMemo(() => createNativeReviewDiffTheme(scheme, themeId), [scheme, themeId]); | 40 | const theme = useMemo( |
| 41 | () => createNativeReviewDiffTheme(scheme, themeId, appTheme), | ||
| 42 | [appTheme, scheme, themeId], | ||
| 43 | ); | ||
| 39 | const rowsJson = useMemo(() => JSON.stringify(data.rows), [data.rows]); | 44 | const rowsJson = useMemo(() => JSON.stringify(data.rows), [data.rows]); |
| 40 | const collapsedFileIdsJson = useMemo(() => JSON.stringify(collapsedFileIds), [collapsedFileIds]); | 45 | const collapsedFileIdsJson = useMemo(() => JSON.stringify(collapsedFileIds), [collapsedFileIds]); |
| 41 | const viewedFileIdsJson = useMemo(() => JSON.stringify(viewedFileIds), [viewedFileIds]); | 46 | const viewedFileIdsJson = useMemo(() => JSON.stringify(viewedFileIds), [viewedFileIds]); |
apps/mobile/src/features/settings/SettingsClientStorageRouteScreen.tsx+8-10| ... | @@ -1,12 +1,11 @@ | ... | @@ -1,12 +1,11 @@ |
| 1 | import { useAtomSet, useAtomValue } from "@effect/atom-react"; | 1 | import { useAtomSet, useAtomValue } from "@effect/atom-react"; |
| 2 | import { AsyncResult } from "effect/unstable/reactivity"; | 2 | import { AsyncResult } from "effect/unstable/reactivity"; |
| 3 | import { SymbolView } from "expo-symbols"; | ||
| 4 | import { useMemo } from "react"; | 3 | import { useMemo } from "react"; |
| 5 | import { ActivityIndicator, Alert, Pressable, ScrollView, View } from "react-native"; | 4 | import { ActivityIndicator, Alert, Pressable, ScrollView, View } from "react-native"; |
| 6 | import { useSafeAreaInsets } from "react-native-safe-area-context"; | 5 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 7 | 6 | ||
| 8 | import { AppText as Text } from "../../components/AppText"; | 7 | import { AppText as Text } from "../../components/AppText"; |
| 9 | import { useThemeColor } from "../../lib/useThemeColor"; | 8 | import { SymbolView } from "../../components/AppSymbol"; |
| 10 | import { | 9 | import { |
| 11 | clearClientCacheAtom, | 10 | clearClientCacheAtom, |
| 12 | clientCacheSummaryAtom, | 11 | clientCacheSummaryAtom, |
| ... | @@ -17,8 +16,6 @@ import { SettingsSection } from "./components/SettingsSection"; | ... | @@ -17,8 +16,6 @@ import { SettingsSection } from "./components/SettingsSection"; |
| 17 | 16 | ||
| 18 | export function SettingsClientStorageRouteScreen() { | 17 | export function SettingsClientStorageRouteScreen() { |
| 19 | const insets = useSafeAreaInsets(); | 18 | const insets = useSafeAreaInsets(); |
| 20 | const iconColor = useThemeColor("--color-icon"); | ||
| 21 | const dangerForegroundColor = useThemeColor("--color-danger-foreground"); | ||
| 22 | const summaryResult = useAtomValue(clientCacheSummaryAtom); | 19 | const summaryResult = useAtomValue(clientCacheSummaryAtom); |
| 23 | const clearResult = useAtomValue(clearClientCacheAtom); | 20 | const clearResult = useAtomValue(clearClientCacheAtom); |
| 24 | const clearCache = useAtomSet(clearClientCacheAtom); | 21 | const clearCache = useAtomSet(clearClientCacheAtom); |
| ... | @@ -84,7 +81,7 @@ export function SettingsClientStorageRouteScreen() { | ... | @@ -84,7 +81,7 @@ export function SettingsClientStorageRouteScreen() { |
| 84 | <SymbolView | 81 | <SymbolView |
| 85 | name="exclamationmark.triangle" | 82 | name="exclamationmark.triangle" |
| 86 | size={28} | 83 | size={28} |
| 87 | tintColor={dangerForegroundColor} | 84 | tintColorClassName={"accent-danger-foreground"} |
| 88 | type="monochrome" | 85 | type="monochrome" |
| 89 | weight="regular" | 86 | weight="regular" |
| 90 | /> | 87 | /> |
| ... | @@ -119,7 +116,7 @@ export function SettingsClientStorageRouteScreen() { | ... | @@ -119,7 +116,7 @@ export function SettingsClientStorageRouteScreen() { |
| 119 | <SymbolView | 116 | <SymbolView |
| 120 | name="checkmark.circle" | 117 | name="checkmark.circle" |
| 121 | size={28} | 118 | size={28} |
| 122 | tintColor={iconColor} | 119 | tintColorClassName={"accent-icon"} |
| 123 | type="monochrome" | 120 | type="monochrome" |
| 124 | weight="regular" | 121 | weight="regular" |
| 125 | /> | 122 | /> |
| ... | @@ -142,14 +139,16 @@ export function SettingsClientStorageRouteScreen() { | ... | @@ -142,14 +139,16 @@ export function SettingsClientStorageRouteScreen() { |
| 142 | <SymbolView | 139 | <SymbolView |
| 143 | name="trash" | 140 | name="trash" |
| 144 | size={22} | 141 | size={22} |
| 145 | tintColor={dangerForegroundColor} | 142 | tintColorClassName={"accent-danger-foreground"} |
| 146 | type="monochrome" | 143 | type="monochrome" |
| 147 | weight="regular" | 144 | weight="regular" |
| 148 | /> | 145 | /> |
| 149 | <Text className="flex-1 text-lg tabular-nums text-danger-foreground"> | 146 | <Text className="flex-1 text-lg tabular-nums text-danger-foreground"> |
| 150 | {summary ? `Clear ${formatBytes(summary.payloadBytes)}` : "Clear caches"} | 147 | {summary ? `Clear ${formatBytes(summary.payloadBytes)}` : "Clear caches"} |
| 151 | </Text> | 148 | </Text> |
| 152 | {isClearing ? <ActivityIndicator color={dangerForegroundColor} /> : null} | 149 | {isClearing ? ( |
| 150 | <ActivityIndicator colorClassName={"accent-danger-foreground"} /> | ||
| 151 | ) : null} | ||
| 153 | </Pressable> | 152 | </Pressable> |
| 154 | </SettingsSection> | 153 | </SettingsSection> |
| 155 | <Text className="px-2 text-sm leading-normal text-foreground-muted"> | 154 | <Text className="px-2 text-sm leading-normal text-foreground-muted"> |
| ... | @@ -174,7 +173,6 @@ function CacheEnvironmentRow(props: { | ... | @@ -174,7 +173,6 @@ function CacheEnvironmentRow(props: { |
| 174 | readonly first: boolean; | 173 | readonly first: boolean; |
| 175 | readonly onClear: () => void; | 174 | readonly onClear: () => void; |
| 176 | }) { | 175 | }) { |
| 177 | const iconColor = useThemeColor("--color-icon"); | ||
| 178 | return ( | 176 | return ( |
| 179 | <View | 177 | <View |
| 180 | className={ | 178 | className={ |
| ... | @@ -186,7 +184,7 @@ function CacheEnvironmentRow(props: { | ... | @@ -186,7 +184,7 @@ function CacheEnvironmentRow(props: { |
| 186 | <SymbolView | 184 | <SymbolView |
| 187 | name="desktopcomputer" | 185 | name="desktopcomputer" |
| 188 | size={22} | 186 | size={22} |
| 189 | tintColor={iconColor} | 187 | tintColorClassName={"accent-icon"} |
| 190 | type="monochrome" | 188 | type="monochrome" |
| 191 | weight="regular" | 189 | weight="regular" |
| 192 | /> | 190 | /> |
apps/mobile/src/features/settings/SettingsEnvironmentsRouteScreen.tsx+3-4| ... | @@ -12,7 +12,7 @@ import { CloudEnvironmentRows } from "../connection/CloudEnvironmentRows"; | ... | @@ -12,7 +12,7 @@ import { CloudEnvironmentRows } from "../connection/CloudEnvironmentRows"; |
| 12 | import { ConnectionEnvironmentRow } from "../connection/ConnectionEnvironmentRow"; | 12 | import { ConnectionEnvironmentRow } from "../connection/ConnectionEnvironmentRow"; |
| 13 | import { splitEnvironmentSections } from "../connection/environmentSections"; | 13 | import { splitEnvironmentSections } from "../connection/environmentSections"; |
| 14 | import { cn } from "../../lib/cn"; | 14 | import { cn } from "../../lib/cn"; |
| 15 | import { useThemeColor } from "../../lib/useThemeColor"; | 15 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 16 | import { useRemoteConnections } from "../../state/use-remote-environment-registry"; | 16 | import { useRemoteConnections } from "../../state/use-remote-environment-registry"; |
| 17 | import { | 17 | import { |
| 18 | applyShowcaseLocalEnvironmentDisplayUrls, | 18 | applyShowcaseLocalEnvironmentDisplayUrls, |
| ... | @@ -44,8 +44,7 @@ export function SettingsEnvironmentsRouteScreen() { | ... | @@ -44,8 +44,7 @@ export function SettingsEnvironmentsRouteScreen() { |
| 44 | : environmentSections.connectedCloudEnvironments; | 44 | : environmentSections.connectedCloudEnvironments; |
| 45 | const hasLocalEnvironments = localEnvironments.length > 0; | 45 | const hasLocalEnvironments = localEnvironments.length > 0; |
| 46 | const [expandedId, setExpandedId] = useState<EnvironmentId | null>(null); | 46 | const [expandedId, setExpandedId] = useState<EnvironmentId | null>(null); |
| 47 | const accentColor = useThemeColor("--color-icon-muted"); | 47 | const headerIconColor = useUniwindTheme()["--color-icon"]; |
| 48 | const headerIconColor = useThemeColor("--color-icon"); | ||
| 49 | 48 | ||
| 50 | const handleToggle = useCallback((environmentId: EnvironmentId) => { | 49 | const handleToggle = useCallback((environmentId: EnvironmentId) => { |
| 51 | setExpandedId((prev) => (prev === environmentId ? null : environmentId)); | 50 | setExpandedId((prev) => (prev === environmentId ? null : environmentId)); |
| ... | @@ -148,7 +147,7 @@ export function SettingsEnvironmentsRouteScreen() { | ... | @@ -148,7 +147,7 @@ export function SettingsEnvironmentsRouteScreen() { |
| 148 | <SymbolView | 147 | <SymbolView |
| 149 | name="point.3.connected.trianglepath.dotted" | 148 | name="point.3.connected.trianglepath.dotted" |
| 150 | size={20} | 149 | size={20} |
| 151 | tintColor={accentColor} | 150 | tintColorClassName={"accent-icon-muted"} |
| 152 | type="monochrome" | 151 | type="monochrome" |
| 153 | /> | 152 | /> |
| 154 | </View> | 153 | </View> |
apps/mobile/src/features/settings/SettingsProjectGroupingRouteScreen.tsx+1-3| ... | @@ -8,7 +8,6 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"; | ... | @@ -8,7 +8,6 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 8 | import { AndroidScreenHeader } from "../../components/AndroidScreenHeader"; | 8 | import { AndroidScreenHeader } from "../../components/AndroidScreenHeader"; |
| 9 | import { AppText as Text } from "../../components/AppText"; | 9 | import { AppText as Text } from "../../components/AppText"; |
| 10 | import { SymbolView } from "../../components/AppSymbol"; | 10 | import { SymbolView } from "../../components/AppSymbol"; |
| 11 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 12 | import { NativeStackScreenOptions } from "../../native/StackHeader"; | 11 | import { NativeStackScreenOptions } from "../../native/StackHeader"; |
| 13 | import { | 12 | import { |
| 14 | mobileProjectGroupingModePatch, | 13 | mobileProjectGroupingModePatch, |
| ... | @@ -42,7 +41,6 @@ const GROUPING_OPTIONS: ReadonlyArray<{ | ... | @@ -42,7 +41,6 @@ const GROUPING_OPTIONS: ReadonlyArray<{ |
| 42 | export function SettingsProjectGroupingRouteScreen() { | 41 | export function SettingsProjectGroupingRouteScreen() { |
| 43 | const navigation = useNavigation(); | 42 | const navigation = useNavigation(); |
| 44 | const insets = useSafeAreaInsets(); | 43 | const insets = useSafeAreaInsets(); |
| 45 | const checkmarkColor = useThemeColor("--color-icon"); | ||
| 46 | const preferencesResult = useAtomValue(mobilePreferencesAtom); | 44 | const preferencesResult = useAtomValue(mobilePreferencesAtom); |
| 47 | const savePreferences = useAtomSet(updateMobilePreferencesAtom); | 45 | const savePreferences = useAtomSet(updateMobilePreferencesAtom); |
| 48 | const preferencesReady = AsyncResult.isSuccess(preferencesResult) && !preferencesResult.waiting; | 46 | const preferencesReady = AsyncResult.isSuccess(preferencesResult) && !preferencesResult.waiting; |
| ... | @@ -92,7 +90,7 @@ export function SettingsProjectGroupingRouteScreen() { | ... | @@ -92,7 +90,7 @@ export function SettingsProjectGroupingRouteScreen() { |
| 92 | <SymbolView | 90 | <SymbolView |
| 93 | name="checkmark" | 91 | name="checkmark" |
| 94 | size={18} | 92 | size={18} |
| 95 | tintColor={checkmarkColor} | 93 | tintColorClassName={"accent-icon"} |
| 96 | type="monochrome" | 94 | type="monochrome" |
| 97 | weight="semibold" | 95 | weight="semibold" |
| 98 | /> | 96 | /> |
apps/mobile/src/features/settings/SettingsRouteScreen.tsx+1-3| ... | @@ -33,7 +33,6 @@ import { hasCloudPublicConfig, resolveRelayClerkTokenOptions } from "../cloud/pu | ... | @@ -33,7 +33,6 @@ import { hasCloudPublicConfig, resolveRelayClerkTokenOptions } from "../cloud/pu |
| 33 | import { withNativeGlassHeaderItem } from "../layout/native-glass-header-items"; | 33 | import { withNativeGlassHeaderItem } from "../layout/native-glass-header-items"; |
| 34 | import { WorkspaceSidebarToolbar } from "../layout/workspace-sidebar-toolbar"; | 34 | import { WorkspaceSidebarToolbar } from "../layout/workspace-sidebar-toolbar"; |
| 35 | import { runtime } from "../../lib/runtime"; | 35 | import { runtime } from "../../lib/runtime"; |
| 36 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 37 | import { mobilePreferencesAtom, updateMobilePreferencesAtom } from "../../state/preferences"; | 36 | import { mobilePreferencesAtom, updateMobilePreferencesAtom } from "../../state/preferences"; |
| 38 | import { useThreadListV2Enabled } from "../threads/use-thread-list-v2-enabled"; | 37 | import { useThreadListV2Enabled } from "../threads/use-thread-list-v2-enabled"; |
| 39 | import { | 38 | import { |
| ... | @@ -585,7 +584,6 @@ function LegacySettingsSection() { | ... | @@ -585,7 +584,6 @@ function LegacySettingsSection() { |
| 585 | } | 584 | } |
| 586 | 585 | ||
| 587 | function AppSettingsSection() { | 586 | function AppSettingsSection() { |
| 588 | const icon = useThemeColor("--color-icon"); | ||
| 589 | const [updateState, setUpdateState] = useState<AppUpdateCheckState>("idle"); | 587 | const [updateState, setUpdateState] = useState<AppUpdateCheckState>("idle"); |
| 590 | const updateInFlight = useRef(false); | 588 | const updateInFlight = useRef(false); |
| 591 | const hiddenUpdateTapCount = useRef(0); | 589 | const hiddenUpdateTapCount = useRef(0); |
| ... | @@ -655,7 +653,7 @@ function AppSettingsSection() { | ... | @@ -655,7 +653,7 @@ function AppSettingsSection() { |
| 655 | <SymbolView | 653 | <SymbolView |
| 656 | name="info.circle" | 654 | name="info.circle" |
| 657 | size={22} | 655 | size={22} |
| 658 | tintColor={icon} | 656 | tintColorClassName={"accent-icon"} |
| 659 | type="monochrome" | 657 | type="monochrome" |
| 660 | weight="regular" | 658 | weight="regular" |
| 661 | /> | 659 | /> |
apps/mobile/src/features/settings/appearance/AppearancePreferencesProvider.tsx+118-49| ... | @@ -1,15 +1,23 @@ | ... | @@ -1,15 +1,23 @@ |
| 1 | import { createContext, use, useCallback, useLayoutEffect, useMemo, type ReactNode } from "react"; | 1 | import { |
| 2 | import { useColorScheme } from "react-native"; | 2 | createContext, |
| 3 | startTransition, | ||
| 4 | use, | ||
| 5 | useCallback, | ||
| 6 | useLayoutEffect, | ||
| 7 | useMemo, | ||
| 8 | useRef, | ||
| 9 | type ReactNode, | ||
| 10 | } from "react"; | ||
| 11 | import { Appearance, useColorScheme } from "react-native"; | ||
| 3 | 12 | ||
| 4 | import { useAtomSet, useAtomValue } from "@effect/atom-react"; | 13 | import { useAtomSet, useAtomValue } from "@effect/atom-react"; |
| 5 | import { AsyncResult } from "effect/unstable/reactivity"; | 14 | import { AsyncResult } from "effect/unstable/reactivity"; |
| 6 | 15 | ||
| 7 | import { Uniwind } from "uniwind"; | 16 | import { ScopedTheme, Uniwind } from "uniwind"; |
| 8 | 17 | ||
| 9 | import { | 18 | import { |
| 10 | resolveAppearance, | 19 | resolveAppearance, |
| 11 | resolveAppearancePreferences, | 20 | resolveAppearancePreferences, |
| 12 | resolveTextScaleVariables, | ||
| 13 | type ResolvedAppearance, | 21 | type ResolvedAppearance, |
| 14 | } from "../../../lib/appearancePreferences"; | 22 | } from "../../../lib/appearancePreferences"; |
| 15 | import { mobilePreferencesAtom, updateMobilePreferencesAtom } from "../../../state/preferences"; | 23 | import { mobilePreferencesAtom, updateMobilePreferencesAtom } from "../../../state/preferences"; |
| ... | @@ -17,7 +25,6 @@ import type { Preferences } from "../../../persistence/mobile-preferences"; | ... | @@ -17,7 +25,6 @@ import type { Preferences } from "../../../persistence/mobile-preferences"; |
| 17 | import { | 25 | import { |
| 18 | createMobileThemePairPatch, | 26 | createMobileThemePairPatch, |
| 19 | createMobileThemeSelectionPatch, | 27 | createMobileThemeSelectionPatch, |
| 20 | getMobileThemeVariables, | ||
| 21 | normalizeMobileThemeMode, | 28 | normalizeMobileThemeMode, |
| 22 | resolveMobileThemeIds, | 29 | resolveMobileThemeIds, |
| 23 | type MobileThemeAppearance, | 30 | type MobileThemeAppearance, |
| ... | @@ -25,6 +32,11 @@ import { | ... | @@ -25,6 +32,11 @@ import { |
| 25 | type MobileThemeIds, | 32 | type MobileThemeIds, |
| 26 | type MobileThemeMode, | 33 | type MobileThemeMode, |
| 27 | } from "../../../lib/mobileTheme"; | 34 | } from "../../../lib/mobileTheme"; |
| 35 | import { | ||
| 36 | createMobileThemeRuntimeOperations, | ||
| 37 | getMobileUniwindThemeName, | ||
| 38 | type MobileThemeRuntimeState, | ||
| 39 | } from "../../../lib/mobileThemeRuntime"; | ||
| 28 | import { cacheTerminalFontSize } from "../../terminal/terminalUiState"; | 40 | import { cacheTerminalFontSize } from "../../terminal/terminalUiState"; |
| 29 | 41 | ||
| 30 | interface AppearancePreferencesContextValue { | 42 | interface AppearancePreferencesContextValue { |
| ... | @@ -51,30 +63,6 @@ interface AppearancePreferencesContextValue { | ... | @@ -51,30 +63,6 @@ interface AppearancePreferencesContextValue { |
| 51 | 63 | ||
| 52 | const AppearancePreferencesContext = createContext<AppearancePreferencesContextValue | null>(null); | 64 | const AppearancePreferencesContext = createContext<AppearancePreferencesContextValue | null>(null); |
| 53 | 65 | ||
| 54 | /** | ||
| 55 | * Injects palette and text-scale variables into both adaptive stylesheets. | ||
| 56 | * Updating the active sheet last lets the visible app settle in one pass. | ||
| 57 | */ | ||
| 58 | function applyAppearanceVariables(baseFontSize: number, themeIds: MobileThemeIds) { | ||
| 59 | const textVariables = resolveTextScaleVariables(baseFontSize); | ||
| 60 | const currentTheme = Uniwind.currentTheme; | ||
| 61 | const activeAppearance = | ||
| 62 | currentTheme === "light" || currentTheme === "dark" ? currentTheme : null; | ||
| 63 | |||
| 64 | for (const theme of ["light", "dark"] as const) { | ||
| 65 | const variables = { ...getMobileThemeVariables(themeIds[theme], theme), ...textVariables }; | ||
| 66 | if (theme !== activeAppearance) { | ||
| 67 | Uniwind.updateCSSVariables(theme, variables); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | if (activeAppearance !== null) { | ||
| 71 | Uniwind.updateCSSVariables(activeAppearance, { | ||
| 72 | ...getMobileThemeVariables(themeIds[activeAppearance], activeAppearance), | ||
| 73 | ...textVariables, | ||
| 74 | }); | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | export function AppearancePreferencesProvider(props: { readonly children: ReactNode }) { | 66 | export function AppearancePreferencesProvider(props: { readonly children: ReactNode }) { |
| 79 | const preferencesResult = useAtomValue(mobilePreferencesAtom); | 67 | const preferencesResult = useAtomValue(mobilePreferencesAtom); |
| 80 | const savePreferences = useAtomSet(updateMobilePreferencesAtom); | 68 | const savePreferences = useAtomSet(updateMobilePreferencesAtom); |
| ... | @@ -88,54 +76,135 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN | ... | @@ -88,54 +76,135 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN |
| 88 | ); | 76 | ); |
| 89 | const themeMode = normalizeMobileThemeMode(storedPreferences?.themeMode); | 77 | const themeMode = normalizeMobileThemeMode(storedPreferences?.themeMode); |
| 90 | const themeAppearance = themeMode === "system" ? systemColorScheme : themeMode; | 78 | const themeAppearance = themeMode === "system" ? systemColorScheme : themeMode; |
| 91 | const themeIds = useMemo( | 79 | const resolvedThemeIds = resolveMobileThemeIds(storedPreferences ?? {}); |
| 92 | () => resolveMobileThemeIds(storedPreferences ?? {}), | 80 | const themeIds = useMemo<MobileThemeIds>( |
| 93 | [storedPreferences], | 81 | () => ({ light: resolvedThemeIds.light, dark: resolvedThemeIds.dark }), |
| 82 | [resolvedThemeIds.dark, resolvedThemeIds.light], | ||
| 94 | ); | 83 | ); |
| 95 | const themeId = themeIds[themeAppearance]; | 84 | const themeId = themeIds[themeAppearance]; |
| 96 | const isReady = AsyncResult.isSuccess(preferencesResult) && !preferencesResult.waiting; | 85 | const activeThemeName = getMobileUniwindThemeName(themeId, themeAppearance); |
| 86 | const { baseFontSize, codeFontSize, codeWordBreak, terminalFontSize } = preferences; | ||
| 87 | const appearance = useMemo( | ||
| 88 | () => resolveAppearance({ baseFontSize, codeFontSize, codeWordBreak, terminalFontSize }), | ||
| 89 | [baseFontSize, codeFontSize, codeWordBreak, terminalFontSize], | ||
| 90 | ); | ||
| 91 | // Preference patches are optimistic. Keep controls interactive while a save is | ||
| 92 | // in flight so rapid theme choices can supersede one another immediately. | ||
| 93 | const isReady = AsyncResult.isSuccess(preferencesResult); | ||
| 94 | const runtimeState = useMemo<MobileThemeRuntimeState>( | ||
| 95 | () => ({ | ||
| 96 | baseFontSize, | ||
| 97 | themeAppearance, | ||
| 98 | themeMode, | ||
| 99 | }), | ||
| 100 | [baseFontSize, themeAppearance, themeMode], | ||
| 101 | ); | ||
| 102 | const appliedRuntimeStateRef = useRef<MobileThemeRuntimeState | null>(null); | ||
| 103 | const selectedThemeIdsRef = useRef(themeIds); | ||
| 97 | 104 | ||
| 98 | useLayoutEffect(() => { | 105 | const applyThemeRuntime = useCallback((next: MobileThemeRuntimeState) => { |
| 99 | applyAppearanceVariables(preferences.baseFontSize, themeIds); | 106 | const operations = createMobileThemeRuntimeOperations(appliedRuntimeStateRef.current, next); |
| 100 | Uniwind.setTheme(themeMode); | 107 | for (const operation of operations) { |
| 101 | cacheTerminalFontSize(resolveAppearance(preferences).terminalFontSize); | 108 | if (operation.kind === "update-text-variables") { |
| 102 | }, [preferences, themeIds, themeMode]); | 109 | Uniwind.updateCSSVariables(operation.themeName, operation.variables); |
| 110 | continue; | ||
| 111 | } | ||
| 112 | if (operation.kind === "set-appearance-mode") { | ||
| 113 | Appearance.setColorScheme( | ||
| 114 | operation.themeMode === "system" ? "unspecified" : operation.appearance, | ||
| 115 | ); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | appliedRuntimeStateRef.current = next; | ||
| 119 | }, []); | ||
| 120 | |||
| 121 | const syncThemeRuntime = useCallback( | ||
| 122 | (next: MobileThemeRuntimeState) => applyThemeRuntime(next), | ||
| 123 | [applyThemeRuntime], | ||
| 124 | ); | ||
| 103 | 125 | ||
| 104 | const updatePreferences = useCallback( | 126 | const updatePreferences = useCallback( |
| 105 | (patch: Partial<Preferences>) => { | 127 | (patch: Partial<Preferences>) => { |
| 128 | startTransition(() => savePreferences(patch)); | ||
| 129 | }, | ||
| 130 | [savePreferences], | ||
| 131 | ); | ||
| 132 | |||
| 133 | const updateThemePreferences = useCallback( | ||
| 134 | (patch: Partial<Preferences>) => { | ||
| 135 | // Theme selection owns the visible root ScopedTheme value. Keep its | ||
| 136 | // optimistic atom update urgent so the first frame after a press is the | ||
| 137 | // complete new palette rather than a deferred transition render. | ||
| 106 | savePreferences(patch); | 138 | savePreferences(patch); |
| 107 | }, | 139 | }, |
| 108 | [savePreferences], | 140 | [savePreferences], |
| 109 | ); | 141 | ); |
| 110 | 142 | ||
| 143 | useLayoutEffect(() => { | ||
| 144 | selectedThemeIdsRef.current = themeIds; | ||
| 145 | syncThemeRuntime(runtimeState); | ||
| 146 | cacheTerminalFontSize(appearance.terminalFontSize); | ||
| 147 | }, [appearance.terminalFontSize, runtimeState, syncThemeRuntime, themeIds]); | ||
| 148 | |||
| 111 | const setThemeIdForAppearance = useCallback( | 149 | const setThemeIdForAppearance = useCallback( |
| 112 | (appearance: MobileThemeAppearance, value: MobileThemeId) => { | 150 | (appearance: MobileThemeAppearance, value: MobileThemeId) => { |
| 113 | updatePreferences( | 151 | const patch = createMobileThemeSelectionPatch( |
| 114 | createMobileThemeSelectionPatch(themeIds, themeAppearance, appearance, value), | 152 | selectedThemeIdsRef.current, |
| 153 | themeAppearance, | ||
| 154 | appearance, | ||
| 155 | value, | ||
| 115 | ); | 156 | ); |
| 157 | selectedThemeIdsRef.current = resolveMobileThemeIds(patch); | ||
| 158 | updateThemePreferences(patch); | ||
| 116 | }, | 159 | }, |
| 117 | [themeAppearance, themeIds, updatePreferences], | 160 | [themeAppearance, updateThemePreferences], |
| 118 | ); | 161 | ); |
| 119 | 162 | ||
| 120 | const setThemeIdForBothAppearances = useCallback( | 163 | const setThemeIdForBothAppearances = useCallback( |
| 121 | (value: MobileThemeId) => { | 164 | (value: MobileThemeId) => { |
| 122 | updatePreferences(createMobileThemePairPatch(value)); | 165 | const patch = createMobileThemePairPatch(value); |
| 166 | selectedThemeIdsRef.current = resolveMobileThemeIds(patch); | ||
| 167 | updateThemePreferences(patch); | ||
| 123 | }, | 168 | }, |
| 124 | [updatePreferences], | 169 | [updateThemePreferences], |
| 125 | ); | 170 | ); |
| 126 | 171 | ||
| 127 | const setThemeMode = useCallback( | 172 | const setThemeMode = useCallback( |
| 128 | (value: MobileThemeMode) => { | 173 | (value: MobileThemeMode) => { |
| 129 | updatePreferences({ themeMode: value }); | 174 | const current = appliedRuntimeStateRef.current ?? runtimeState; |
| 175 | |||
| 176 | // Clear a forced native appearance before publishing System. The | ||
| 177 | // resulting useColorScheme notification still sees the previous forced | ||
| 178 | // preference, so React batches the actual system palette into the one | ||
| 179 | // urgent preference commit below. | ||
| 180 | if (value === "system") { | ||
| 181 | Appearance.setColorScheme("unspecified"); | ||
| 182 | } | ||
| 183 | const nextAppearance = | ||
| 184 | value === "system" ? (Appearance.getColorScheme() === "dark" ? "dark" : "light") : value; | ||
| 185 | const next = { | ||
| 186 | ...current, | ||
| 187 | themeAppearance: nextAppearance, | ||
| 188 | themeMode: value, | ||
| 189 | }; | ||
| 190 | |||
| 191 | updateThemePreferences({ themeMode: value }); | ||
| 192 | if (value === "system") { | ||
| 193 | appliedRuntimeStateRef.current = next; | ||
| 194 | } else { | ||
| 195 | syncThemeRuntime(next); | ||
| 196 | } | ||
| 130 | }, | 197 | }, |
| 131 | [updatePreferences], | 198 | [runtimeState, syncThemeRuntime, updateThemePreferences], |
| 132 | ); | 199 | ); |
| 133 | 200 | ||
| 134 | const setBaseFontSize = useCallback( | 201 | const setBaseFontSize = useCallback( |
| 135 | (value: number) => { | 202 | (value: number) => { |
| 203 | const current = appliedRuntimeStateRef.current ?? runtimeState; | ||
| 204 | syncThemeRuntime({ ...current, baseFontSize: value }); | ||
| 136 | updatePreferences({ baseFontSize: value }); | 205 | updatePreferences({ baseFontSize: value }); |
| 137 | }, | 206 | }, |
| 138 | [updatePreferences], | 207 | [runtimeState, syncThemeRuntime, updatePreferences], |
| 139 | ); | 208 | ); |
| 140 | 209 | ||
| 141 | const setTerminalFontSize = useCallback( | 210 | const setTerminalFontSize = useCallback( |
| ... | @@ -161,7 +230,7 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN | ... | @@ -161,7 +230,7 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN |
| 161 | 230 | ||
| 162 | const value = useMemo( | 231 | const value = useMemo( |
| 163 | (): AppearancePreferencesContextValue => ({ | 232 | (): AppearancePreferencesContextValue => ({ |
| 164 | appearance: resolveAppearance(preferences), | 233 | appearance, |
| 165 | themeId, | 234 | themeId, |
| 166 | themeIds, | 235 | themeIds, |
| 167 | themeMode, | 236 | themeMode, |
| ... | @@ -176,7 +245,7 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN | ... | @@ -176,7 +245,7 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN |
| 176 | setCodeWordBreak, | 245 | setCodeWordBreak, |
| 177 | }), | 246 | }), |
| 178 | [ | 247 | [ |
| 179 | preferences, | 248 | appearance, |
| 180 | themeId, | 249 | themeId, |
| 181 | themeIds, | 250 | themeIds, |
| 182 | themeMode, | 251 | themeMode, |
| ... | @@ -194,7 +263,7 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN | ... | @@ -194,7 +263,7 @@ export function AppearancePreferencesProvider(props: { readonly children: ReactN |
| 194 | 263 | ||
| 195 | return ( | 264 | return ( |
| 196 | <AppearancePreferencesContext.Provider value={value}> | 265 | <AppearancePreferencesContext.Provider value={value}> |
| 197 | {props.children} | 266 | <ScopedTheme theme={activeThemeName}>{props.children}</ScopedTheme> |
| 198 | </AppearancePreferencesContext.Provider> | 267 | </AppearancePreferencesContext.Provider> |
| 199 | ); | 268 | ); |
| 200 | } | 269 | } |
apps/mobile/src/features/settings/appearance/components/AppearancePreviews.tsx+4-3| ... | @@ -5,7 +5,7 @@ import { | ... | @@ -5,7 +5,7 @@ import { |
| 5 | resolveMarkdownFontSizes, | 5 | resolveMarkdownFontSizes, |
| 6 | resolveMobileCodeSurface, | 6 | resolveMobileCodeSurface, |
| 7 | } from "../../../../lib/appearancePreferences"; | 7 | } from "../../../../lib/appearancePreferences"; |
| 8 | import { useThemeColor } from "../../../../lib/useThemeColor"; | 8 | import { useUniwindTheme } from "../../../../lib/useUniwindTheme"; |
| 9 | import { getMobileTerminalTheme } from "../../../terminal/terminalTheme"; | 9 | import { getMobileTerminalTheme } from "../../../terminal/terminalTheme"; |
| 10 | import { useAppearancePreferences } from "../AppearancePreferencesProvider"; | 10 | import { useAppearancePreferences } from "../AppearancePreferencesProvider"; |
| 11 | 11 | ||
| ... | @@ -138,8 +138,9 @@ export function CodeAppearancePreview(props: { | ... | @@ -138,8 +138,9 @@ export function CodeAppearancePreview(props: { |
| 138 | readonly wordBreak: boolean; | 138 | readonly wordBreak: boolean; |
| 139 | }) { | 139 | }) { |
| 140 | const surface = resolveMobileCodeSurface(props.fontSize); | 140 | const surface = resolveMobileCodeSurface(props.fontSize); |
| 141 | const lineNumberColor = useThemeColor("--color-icon-subtle"); | 141 | const theme = useUniwindTheme(); |
| 142 | const keywordColor = useThemeColor("--color-md-link"); | 142 | const lineNumberColor = theme["--color-icon-subtle"]; |
| 143 | const keywordColor = theme["--color-md-link"]; | ||
| 143 | 144 | ||
| 144 | const lineNumber = (line: CodePreviewLine, index: number) => ( | 145 | const lineNumber = (line: CodePreviewLine, index: number) => ( |
| 145 | <Text | 146 | <Text |
apps/mobile/src/features/settings/appearance/components/FontSizeSliderRow.tsx+7-8| ... | @@ -12,7 +12,7 @@ import Animated, { | ... | @@ -12,7 +12,7 @@ import Animated, { |
| 12 | import type { ComponentProps } from "react"; | 12 | import type { ComponentProps } from "react"; |
| 13 | 13 | ||
| 14 | import { AppText as Text } from "../../../../components/AppText"; | 14 | import { AppText as Text } from "../../../../components/AppText"; |
| 15 | import { useThemeColor } from "../../../../lib/useThemeColor"; | 15 | import { useUniwindTheme } from "../../../../lib/useUniwindTheme"; |
| 16 | 16 | ||
| 17 | type SymbolName = ComponentProps<typeof SymbolView>["name"]; | 17 | type SymbolName = ComponentProps<typeof SymbolView>["name"]; |
| 18 | 18 | ||
| ... | @@ -36,10 +36,9 @@ export function FontSizeSliderRow(props: { | ... | @@ -36,10 +36,9 @@ export function FontSizeSliderRow(props: { |
| 36 | readonly value: number; | 36 | readonly value: number; |
| 37 | readonly onChange: (value: number) => void; | 37 | readonly onChange: (value: number) => void; |
| 38 | }) { | 38 | }) { |
| 39 | const icon = useThemeColor("--color-icon"); | 39 | const theme = useUniwindTheme(); |
| 40 | const iconMuted = String(useThemeColor("--color-icon-muted")); | 40 | const trackColor = theme["--color-secondary-border"]; |
| 41 | const trackColor = String(useThemeColor("--color-secondary-border")); | 41 | const fillColor = theme["--color-primary"]; |
| 42 | const fillColor = String(useThemeColor("--color-primary")); | ||
| 43 | 42 | ||
| 44 | const latest = useRef(props); | 43 | const latest = useRef(props); |
| 45 | latest.current = props; | 44 | latest.current = props; |
| ... | @@ -141,7 +140,7 @@ export function FontSizeSliderRow(props: { | ... | @@ -141,7 +140,7 @@ export function FontSizeSliderRow(props: { |
| 141 | <SymbolView | 140 | <SymbolView |
| 142 | name={props.icon} | 141 | name={props.icon} |
| 143 | size={22} | 142 | size={22} |
| 144 | tintColor={icon} | 143 | tintColorClassName={"accent-icon"} |
| 145 | type="monochrome" | 144 | type="monochrome" |
| 146 | weight="regular" | 145 | weight="regular" |
| 147 | /> | 146 | /> |
| ... | @@ -152,7 +151,7 @@ export function FontSizeSliderRow(props: { | ... | @@ -152,7 +151,7 @@ export function FontSizeSliderRow(props: { |
| 152 | <SymbolView | 151 | <SymbolView |
| 153 | name="textformat.size.smaller" | 152 | name="textformat.size.smaller" |
| 154 | size={15} | 153 | size={15} |
| 155 | tintColor={iconMuted} | 154 | tintColorClassName={"accent-icon-muted"} |
| 156 | type="monochrome" | 155 | type="monochrome" |
| 157 | weight="regular" | 156 | weight="regular" |
| 158 | /> | 157 | /> |
| ... | @@ -204,7 +203,7 @@ export function FontSizeSliderRow(props: { | ... | @@ -204,7 +203,7 @@ export function FontSizeSliderRow(props: { |
| 204 | <SymbolView | 203 | <SymbolView |
| 205 | name="textformat.size.larger" | 204 | name="textformat.size.larger" |
| 206 | size={22} | 205 | size={22} |
| 207 | tintColor={iconMuted} | 206 | tintColorClassName={"accent-icon-muted"} |
| 208 | type="monochrome" | 207 | type="monochrome" |
| 209 | weight="regular" | 208 | weight="regular" |
| 210 | /> | 209 | /> |
apps/mobile/src/features/settings/appearance/sections/ThemeAppearanceSection.tsx+73-117| ... | @@ -1,22 +1,22 @@ | ... | @@ -1,22 +1,22 @@ |
| 1 | import { memo, useId } from "react"; | 1 | import { memo, useId } from "react"; |
| 2 | import { Pressable, View } from "react-native"; | 2 | import { Pressable, View } from "react-native"; |
| 3 | import Svg, { Circle, Defs, RadialGradient, Stop } from "react-native-svg"; | 3 | import Svg, { Circle, Defs, RadialGradient, Stop } from "react-native-svg"; |
| 4 | import { ScopedTheme } from "uniwind"; | ||
| 4 | 5 | ||
| 5 | import { mixThemePreviewBase, THEME_PREVIEW_RENDER_SPECS } from "@t3tools/shared/themePreview"; | 6 | import { mixThemePreviewBase, THEME_PREVIEW_RENDER_SPECS } from "@t3tools/shared/themePreview"; |
| 6 | 7 | ||
| 7 | import { SymbolView } from "../../../../components/AppSymbol"; | 8 | import { SymbolView } from "../../../../components/AppSymbol"; |
| 8 | import { AppText as Text } from "../../../../components/AppText"; | 9 | import { AppText as Text } from "../../../../components/AppText"; |
| 9 | import { | 10 | import { |
| 10 | getMobileThemeVariables, | ||
| 11 | getMobileThemePreviewColors, | 11 | getMobileThemePreviewColors, |
| 12 | MOBILE_THEME_OPTIONS, | 12 | MOBILE_THEME_OPTIONS, |
| 13 | type MobileThemeAppearance, | 13 | type MobileThemeAppearance, |
| 14 | type MobileThemeId, | 14 | type MobileThemeId, |
| 15 | type MobileThemeIds, | 15 | type MobileThemeIds, |
| 16 | type MobileThemeMode, | 16 | type MobileThemeMode, |
| 17 | type MobileThemeVariables, | ||
| 18 | } from "../../../../lib/mobileTheme"; | 17 | } from "../../../../lib/mobileTheme"; |
| 19 | import { useThemeColor } from "../../../../lib/useThemeColor"; | 18 | import { getMobileUniwindThemeName } from "../../../../lib/mobileThemeRuntime"; |
| 19 | import { cn } from "../../../../lib/cn"; | ||
| 20 | import { useAppearancePreferences } from "../AppearancePreferencesProvider"; | 20 | import { useAppearancePreferences } from "../AppearancePreferencesProvider"; |
| 21 | 21 | ||
| 22 | const APPEARANCE_MODES: ReadonlyArray<{ | 22 | const APPEARANCE_MODES: ReadonlyArray<{ |
| ... | @@ -28,6 +28,8 @@ const APPEARANCE_MODES: ReadonlyArray<{ | ... | @@ -28,6 +28,8 @@ const APPEARANCE_MODES: ReadonlyArray<{ |
| 28 | { id: "dark", label: "Dark" }, | 28 | { id: "dark", label: "Dark" }, |
| 29 | ]; | 29 | ]; |
| 30 | 30 | ||
| 31 | const previewPercentage = (value: number) => `${value * 100}%`; | ||
| 32 | |||
| 31 | const PreviewOrb = memo(function PreviewOrb(props: { | 33 | const PreviewOrb = memo(function PreviewOrb(props: { |
| 32 | readonly appearance: MobileThemeAppearance; | 34 | readonly appearance: MobileThemeAppearance; |
| 33 | readonly compact?: boolean; | 35 | readonly compact?: boolean; |
| ... | @@ -46,9 +48,6 @@ const PreviewOrb = memo(function PreviewOrb(props: { | ... | @@ -46,9 +48,6 @@ const PreviewOrb = memo(function PreviewOrb(props: { |
| 46 | Math.max(spec.action.center[0], 1 - spec.action.center[0]), | 48 | Math.max(spec.action.center[0], 1 - spec.action.center[0]), |
| 47 | Math.max(spec.action.center[1], 1 - spec.action.center[1]), | 49 | Math.max(spec.action.center[1], 1 - spec.action.center[1]), |
| 48 | ); | 50 | ); |
| 49 | const position = (value: number) => `${value * 100}%`; | ||
| 50 | const radius = (value: number) => `${value * 100}%`; | ||
| 51 | |||
| 52 | return ( | 51 | return ( |
| 53 | <View | 52 | <View |
| 54 | className={`${props.compact ? "size-14" : "size-16"} overflow-hidden rounded-full border`} | 53 | className={`${props.compact ? "size-14" : "size-16"} overflow-hidden rounded-full border`} |
| ... | @@ -60,33 +59,33 @@ const PreviewOrb = memo(function PreviewOrb(props: { | ... | @@ -60,33 +59,33 @@ const PreviewOrb = memo(function PreviewOrb(props: { |
| 60 | <Svg accessibilityElementsHidden height="100%" viewBox="0 0 64 64" width="100%"> | 59 | <Svg accessibilityElementsHidden height="100%" viewBox="0 0 64 64" width="100%"> |
| 61 | <Defs> | 60 | <Defs> |
| 62 | <RadialGradient | 61 | <RadialGradient |
| 63 | cx={position(spec.accent.center[0])} | 62 | cx={previewPercentage(spec.accent.center[0])} |
| 64 | cy={position(spec.accent.center[1])} | 63 | cy={previewPercentage(spec.accent.center[1])} |
| 65 | fx={position(spec.accent.center[0])} | 64 | fx={previewPercentage(spec.accent.center[0])} |
| 66 | fy={position(spec.accent.center[1])} | 65 | fy={previewPercentage(spec.accent.center[1])} |
| 67 | id={accentGradientId} | 66 | id={accentGradientId} |
| 68 | r={radius(accentRadius)} | 67 | r={previewPercentage(accentRadius)} |
| 69 | > | 68 | > |
| 70 | <Stop offset="0%" stopColor={colors.accent} stopOpacity={1} /> | 69 | <Stop offset="0%" stopColor={colors.accent} stopOpacity={1} /> |
| 71 | <Stop | 70 | <Stop |
| 72 | offset={position(spec.accent.middleOffset)} | 71 | offset={previewPercentage(spec.accent.middleOffset)} |
| 73 | stopColor={colors.accent} | 72 | stopColor={colors.accent} |
| 74 | stopOpacity={spec.accent.middleOpacity} | 73 | stopOpacity={spec.accent.middleOpacity} |
| 75 | /> | 74 | /> |
| 76 | <Stop | 75 | <Stop |
| 77 | offset={position(spec.accent.endOffset)} | 76 | offset={previewPercentage(spec.accent.endOffset)} |
| 78 | stopColor={colors.accent} | 77 | stopColor={colors.accent} |
| 79 | stopOpacity={0} | 78 | stopOpacity={0} |
| 80 | /> | 79 | /> |
| 81 | <Stop offset="100%" stopColor={colors.accent} stopOpacity={0} /> | 80 | <Stop offset="100%" stopColor={colors.accent} stopOpacity={0} /> |
| 82 | </RadialGradient> | 81 | </RadialGradient> |
| 83 | <RadialGradient | 82 | <RadialGradient |
| 84 | cx={position(spec.action.center[0])} | 83 | cx={previewPercentage(spec.action.center[0])} |
| 85 | cy={position(spec.action.center[1])} | 84 | cy={previewPercentage(spec.action.center[1])} |
| 86 | fx={position(spec.action.center[0])} | 85 | fx={previewPercentage(spec.action.center[0])} |
| 87 | fy={position(spec.action.center[1])} | 86 | fy={previewPercentage(spec.action.center[1])} |
| 88 | id={actionGradientId} | 87 | id={actionGradientId} |
| 89 | r={radius(actionRadius)} | 88 | r={previewPercentage(actionRadius)} |
| 90 | > | 89 | > |
| 91 | <Stop | 90 | <Stop |
| 92 | offset="0%" | 91 | offset="0%" |
| ... | @@ -94,7 +93,7 @@ const PreviewOrb = memo(function PreviewOrb(props: { | ... | @@ -94,7 +93,7 @@ const PreviewOrb = memo(function PreviewOrb(props: { |
| 94 | stopOpacity={spec.action.startOpacity} | 93 | stopOpacity={spec.action.startOpacity} |
| 95 | /> | 94 | /> |
| 96 | <Stop | 95 | <Stop |
| 97 | offset={position(spec.action.endOffset)} | 96 | offset={previewPercentage(spec.action.endOffset)} |
| 98 | stopColor={colors.messageAction} | 97 | stopColor={colors.messageAction} |
| 99 | stopOpacity={0} | 98 | stopOpacity={0} |
| 100 | /> | 99 | /> |
| ... | @@ -118,32 +117,26 @@ function ThemeCard(props: { | ... | @@ -118,32 +117,26 @@ function ThemeCard(props: { |
| 118 | readonly onSelect: (appearance: MobileThemeAppearance) => void; | 117 | readonly onSelect: (appearance: MobileThemeAppearance) => void; |
| 119 | readonly themeId: MobileThemeId; | 118 | readonly themeId: MobileThemeId; |
| 120 | }) { | 119 | }) { |
| 121 | const badgeBackground = useThemeColor("--color-card"); | ||
| 122 | const badgeIcon = useThemeColor("--color-icon"); | ||
| 123 | |||
| 124 | const choice = (appearance: MobileThemeAppearance, selected: boolean) => ( | 120 | const choice = (appearance: MobileThemeAppearance, selected: boolean) => ( |
| 125 | <Pressable | 121 | <Pressable |
| 122 | accessibilityHint={`Sets the ${appearance} appearance only`} | ||
| 126 | accessibilityLabel={`${props.label} ${appearance} theme`} | 123 | accessibilityLabel={`${props.label} ${appearance} theme`} |
| 127 | accessibilityRole="radio" | 124 | accessibilityRole="button" |
| 128 | accessibilityState={{ checked: selected, disabled: props.disabled }} | 125 | accessibilityState={{ disabled: props.disabled, selected }} |
| 129 | className={ | 126 | className={cn( |
| 130 | selected | 127 | "size-[66px] items-center justify-center rounded-full border-[3px] active:scale-[0.94]", |
| 131 | ? "size-[66px] items-center justify-center rounded-full border-[3px] border-primary" | 128 | selected ? "border-primary" : "border-transparent", |
| 132 | : "size-[66px] items-center justify-center rounded-full border-[3px] border-transparent" | 129 | )} |
| 133 | } | ||
| 134 | disabled={props.disabled} | 130 | disabled={props.disabled} |
| 135 | onPress={() => props.onSelect(appearance)} | 131 | onPress={() => props.onSelect(appearance)} |
| 136 | > | 132 | > |
| 137 | <PreviewOrb appearance={appearance} compact themeId={props.themeId} /> | 133 | <PreviewOrb appearance={appearance} compact themeId={props.themeId} /> |
| 138 | {selected ? ( | 134 | {selected ? ( |
| 139 | <View | 135 | <View className="absolute -bottom-0.5 -right-0.5 size-5 items-center justify-center rounded-full border border-border bg-card"> |
| 140 | className="absolute -bottom-0.5 -right-0.5 size-5 items-center justify-center rounded-full border border-border" | ||
| 141 | style={{ backgroundColor: badgeBackground }} | ||
| 142 | > | ||
| 143 | <SymbolView | 136 | <SymbolView |
| 144 | name={appearance === "light" ? "sun.max" : "moon"} | 137 | name={appearance === "light" ? "sun.max" : "moon"} |
| 145 | size={12} | 138 | size={12} |
| 146 | tintColor={badgeIcon} | 139 | tintColorClassName="accent-icon" |
| 147 | type="monochrome" | 140 | type="monochrome" |
| 148 | weight="medium" | 141 | weight="medium" |
| 149 | /> | 142 | /> |
| ... | @@ -158,7 +151,10 @@ function ThemeCard(props: { | ... | @@ -158,7 +151,10 @@ function ThemeCard(props: { |
| 158 | accessibilityHint="Sets both light and dark appearances" | 151 | accessibilityHint="Sets both light and dark appearances" |
| 159 | accessibilityLabel={`${props.label} theme`} | 152 | accessibilityLabel={`${props.label} theme`} |
| 160 | accessibilityRole="button" | 153 | accessibilityRole="button" |
| 161 | accessibilityState={{ disabled: props.disabled }} | 154 | accessibilityState={{ |
| 155 | disabled: props.disabled, | ||
| 156 | selected: props.lightSelected && props.darkSelected, | ||
| 157 | }} | ||
| 162 | className="absolute inset-0 rounded-[24px] active:bg-subtle" | 158 | className="absolute inset-0 rounded-[24px] active:bg-subtle" |
| 163 | disabled={props.disabled} | 159 | disabled={props.disabled} |
| 164 | onPress={props.onSelectBoth} | 160 | onPress={props.onSelectBoth} |
| ... | @@ -167,34 +163,26 @@ function ThemeCard(props: { | ... | @@ -167,34 +163,26 @@ function ThemeCard(props: { |
| 167 | {choice("light", props.lightSelected)} | 163 | {choice("light", props.lightSelected)} |
| 168 | {choice("dark", props.darkSelected)} | 164 | {choice("dark", props.darkSelected)} |
| 169 | </View> | 165 | </View> |
| 170 | <View className="min-h-8 flex-row items-center" pointerEvents="none"> | 166 | <Text |
| 171 | <Text className="min-w-0 flex-1 text-lg font-t3-medium" numberOfLines={1}> | 167 | className="min-w-0 flex-1 px-1 text-lg font-t3-medium" |
| 172 | {props.label} | 168 | numberOfLines={1} |
| 173 | </Text> | 169 | pointerEvents="none" |
| 174 | </View> | 170 | > |
| 171 | {props.label} | ||
| 172 | </Text> | ||
| 175 | </View> | 173 | </View> |
| 176 | ); | 174 | ); |
| 177 | } | 175 | } |
| 178 | 176 | ||
| 179 | function PreviewPane(props: { readonly colors: MobileThemeVariables; readonly compact?: boolean }) { | 177 | function PreviewPane(props: { readonly compact?: boolean }) { |
| 180 | return ( | 178 | return ( |
| 181 | <View | 179 | <View className="flex-1 overflow-hidden bg-screen"> |
| 182 | className="flex-1 overflow-hidden" | ||
| 183 | style={{ backgroundColor: props.colors["--color-screen"] }} | ||
| 184 | > | ||
| 185 | <View | 180 | <View |
| 186 | className={props.compact ? "h-[18px] gap-0.5 px-1" : "h-[18px] gap-1 px-1.5"} | 181 | className={cn("bg-card", props.compact ? "h-[18px] gap-0.5 px-1" : "h-[18px] gap-1 px-1.5")} |
| 187 | style={{ backgroundColor: props.colors["--color-card"] }} | ||
| 188 | > | 182 | > |
| 189 | <View className="mt-2 flex-row items-center gap-1"> | 183 | <View className="mt-2 flex-row items-center gap-1"> |
| 190 | <View | 184 | <View className="size-1.5 rounded-full bg-primary" /> |
| 191 | className="size-1.5 rounded-full" | 185 | <View className="h-1 flex-1 rounded-full bg-foreground-muted" /> |
| 192 | style={{ backgroundColor: props.colors["--color-primary"] }} | ||
| 193 | /> | ||
| 194 | <View | ||
| 195 | className="h-1 flex-1 rounded-full" | ||
| 196 | style={{ backgroundColor: props.colors["--color-foreground-muted"] }} | ||
| 197 | /> | ||
| 198 | </View> | 186 | </View> |
| 199 | </View> | 187 | </View> |
| 200 | <View | 188 | <View |
| ... | @@ -203,24 +191,12 @@ function PreviewPane(props: { readonly colors: MobileThemeVariables; readonly co | ... | @@ -203,24 +191,12 @@ function PreviewPane(props: { readonly colors: MobileThemeVariables; readonly co |
| 203 | } | 191 | } |
| 204 | > | 192 | > |
| 205 | <View className="gap-1"> | 193 | <View className="gap-1"> |
| 206 | <View | 194 | <View className="h-1.5 w-[72%] rounded-full bg-subtle-strong" /> |
| 207 | className="h-1.5 w-[72%] rounded-full" | 195 | <View className="h-1.5 w-[46%] rounded-full bg-subtle-strong" /> |
| 208 | style={{ backgroundColor: props.colors["--color-subtle-strong"] }} | ||
| 209 | /> | ||
| 210 | <View | ||
| 211 | className="h-1.5 w-[46%] rounded-full" | ||
| 212 | style={{ backgroundColor: props.colors["--color-subtle-strong"] }} | ||
| 213 | /> | ||
| 214 | </View> | 196 | </View> |
| 215 | <View className="items-end gap-1 pb-2"> | 197 | <View className="items-end gap-1 pb-2"> |
| 216 | <View | 198 | <View className="h-3 w-[78%] rounded-full bg-user-bubble" /> |
| 217 | className="h-3 w-[78%] rounded-full" | 199 | <View className="h-1 w-[38%] rounded-full bg-foreground-muted" /> |
| 218 | style={{ backgroundColor: props.colors["--color-user-bubble"] }} | ||
| 219 | /> | ||
| 220 | <View | ||
| 221 | className="h-1 w-[38%] rounded-full" | ||
| 222 | style={{ backgroundColor: props.colors["--color-foreground-muted"] }} | ||
| 223 | /> | ||
| 224 | </View> | 200 | </View> |
| 225 | </View> | 201 | </View> |
| 226 | </View> | 202 | </View> |
| ... | @@ -228,50 +204,31 @@ function PreviewPane(props: { readonly colors: MobileThemeVariables; readonly co | ... | @@ -228,50 +204,31 @@ function PreviewPane(props: { readonly colors: MobileThemeVariables; readonly co |
| 228 | } | 204 | } |
| 229 | 205 | ||
| 230 | function ModePreview(props: { readonly mode: MobileThemeMode; readonly themeIds: MobileThemeIds }) { | 206 | function ModePreview(props: { readonly mode: MobileThemeMode; readonly themeIds: MobileThemeIds }) { |
| 231 | const light = getMobileThemeVariables(props.themeIds.light, "light"); | 207 | if (props.mode === "system") { |
| 232 | const dark = getMobileThemeVariables(props.themeIds.dark, "dark"); | 208 | return ( |
| 233 | const currentBorder = useThemeColor("--color-border"); | 209 | <View className="h-24 w-14 self-center rounded-[16px] border-[1.5px] border-border bg-drawer p-[3px]"> |
| 234 | const currentFrame = useThemeColor("--color-drawer"); | 210 | <View className="flex-1 flex-row overflow-hidden rounded-[11px]"> |
| 235 | const currentIndicator = useThemeColor("--color-foreground-muted"); | 211 | <ScopedTheme theme={getMobileUniwindThemeName(props.themeIds.light, "light")}> |
| 236 | const frameColor = | 212 | <PreviewPane compact /> |
| 237 | props.mode === "light" | 213 | </ScopedTheme> |
| 238 | ? light["--color-border"] | 214 | <ScopedTheme theme={getMobileUniwindThemeName(props.themeIds.dark, "dark")}> |
| 239 | : props.mode === "dark" | 215 | <PreviewPane compact /> |
| 240 | ? dark["--color-border"] | 216 | </ScopedTheme> |
| 241 | : currentBorder; | 217 | </View> |
| 242 | const frameBackground = | 218 | <View className="absolute bottom-[6px] left-1/2 h-1 w-4 -translate-x-1/2 rounded-full bg-foreground-muted" /> |
| 243 | props.mode === "light" | 219 | </View> |
| 244 | ? light["--color-drawer"] | 220 | ); |
| 245 | : props.mode === "dark" | 221 | } |
| 246 | ? dark["--color-drawer"] | ||
| 247 | : currentFrame; | ||
| 248 | const indicatorColor = | ||
| 249 | props.mode === "light" | ||
| 250 | ? light["--color-foreground-muted"] | ||
| 251 | : props.mode === "dark" | ||
| 252 | ? dark["--color-foreground-muted"] | ||
| 253 | : currentIndicator; | ||
| 254 | 222 | ||
| 255 | return ( | 223 | return ( |
| 256 | <View | 224 | <ScopedTheme theme={getMobileUniwindThemeName(props.themeIds[props.mode], props.mode)}> |
| 257 | className="h-24 w-14 self-center rounded-[16px] p-[3px]" | 225 | <View className="h-24 w-14 self-center rounded-[16px] border-[1.5px] border-border bg-drawer p-[3px]"> |
| 258 | style={{ backgroundColor: frameBackground, borderColor: frameColor, borderWidth: 1.5 }} | 226 | <View className="flex-1 flex-row overflow-hidden rounded-[11px]"> |
| 259 | > | 227 | <PreviewPane /> |
| 260 | <View className="flex-1 flex-row overflow-hidden rounded-[11px]"> | 228 | </View> |
| 261 | {props.mode === "system" ? ( | 229 | <View className="absolute bottom-[6px] left-1/2 h-1 w-4 -translate-x-1/2 rounded-full bg-foreground-muted" /> |
| 262 | <> | ||
| 263 | <PreviewPane colors={light} compact /> | ||
| 264 | <PreviewPane colors={dark} compact /> | ||
| 265 | </> | ||
| 266 | ) : ( | ||
| 267 | <PreviewPane colors={props.mode === "light" ? light : dark} /> | ||
| 268 | )} | ||
| 269 | </View> | 230 | </View> |
| 270 | <View | 231 | </ScopedTheme> |
| 271 | className="absolute bottom-[6px] left-1/2 h-1 w-4 -translate-x-1/2 rounded-full" | ||
| 272 | style={{ backgroundColor: indicatorColor }} | ||
| 273 | /> | ||
| 274 | </View> | ||
| 275 | ); | 232 | ); |
| 276 | } | 233 | } |
| 277 | 234 | ||
| ... | @@ -288,11 +245,10 @@ function ModeCard(props: { | ... | @@ -288,11 +245,10 @@ function ModeCard(props: { |
| 288 | accessibilityLabel={`${props.label} appearance`} | 245 | accessibilityLabel={`${props.label} appearance`} |
| 289 | accessibilityRole="radio" | 246 | accessibilityRole="radio" |
| 290 | accessibilityState={{ checked: props.selected, disabled: props.disabled }} | 247 | accessibilityState={{ checked: props.selected, disabled: props.disabled }} |
| 291 | className={ | 248 | className={cn( |
| 292 | props.selected | 249 | "min-w-0 flex-1 gap-2 rounded-[24px] p-2 active:scale-[0.97]", |
| 293 | ? "min-w-0 flex-1 gap-2 rounded-[24px] border-2 border-primary bg-subtle p-2" | 250 | props.selected ? "border-2 border-primary bg-subtle" : "border border-border bg-card", |
| 294 | : "min-w-0 flex-1 gap-2 rounded-[24px] border border-border bg-card p-2" | 251 | )} |
| 295 | } | ||
| 296 | disabled={props.disabled} | 252 | disabled={props.disabled} |
| 297 | onPress={props.onPress} | 253 | onPress={props.onPress} |
| 298 | > | 254 | > |
apps/mobile/src/features/settings/appearance/useScaledTextRole.ts+22-24| ... | @@ -1,18 +1,12 @@ | ... | @@ -1,18 +1,12 @@ |
| 1 | import { useCSSVariable } from "uniwind"; | 1 | import { useMemo } from "react"; |
| 2 | 2 | ||
| 3 | import { | ||
| 4 | DEFAULT_BASE_FONT_SIZE, | ||
| 5 | normalizeBaseFontSize, | ||
| 6 | scaledTypographyLineHeight, | ||
| 7 | } from "../../../lib/appearancePreferences"; | ||
| 3 | import { MOBILE_TYPOGRAPHY } from "../../../lib/typography"; | 8 | import { MOBILE_TYPOGRAPHY } from "../../../lib/typography"; |
| 4 | 9 | import { useAppearancePreferences } from "./AppearancePreferencesProvider"; | |
| 5 | const TEXT_ROLE_VARIABLES = { | ||
| 6 | micro: "--text-3xs", | ||
| 7 | caption: "--text-2xs", | ||
| 8 | label: "--text-xs", | ||
| 9 | footnote: "--text-sm", | ||
| 10 | body: "--text-base", | ||
| 11 | headline: "--text-lg", | ||
| 12 | title: "--text-xl", | ||
| 13 | largeTitle: "--text-2xl", | ||
| 14 | display: "--text-3xl", | ||
| 15 | } as const satisfies Record<keyof typeof MOBILE_TYPOGRAPHY, string>; | ||
| 16 | 10 | ||
| 17 | export interface ScaledTextRole { | 11 | export interface ScaledTextRole { |
| 18 | readonly fontSize: number; | 12 | readonly fontSize: number; |
| ... | @@ -20,17 +14,21 @@ export interface ScaledTextRole { | ... | @@ -20,17 +14,21 @@ export interface ScaledTextRole { |
| 20 | } | 14 | } |
| 21 | 15 | ||
| 22 | /** | 16 | /** |
| 23 | * Reads a typography role's current size from the Uniwind `--text-*` CSS | 17 | * Mirrors the values injected into Uniwind for style-prop consumers that |
| 24 | * variables (scaled at runtime with the base font size). Use for style-prop | 18 | * cannot use a `text-*` class. This deliberately does not subscribe to CSS |
| 25 | * consumers that can't express their size as a `text-*` className. Reactive: | 19 | * variables, so palette-only setTheme calls remain native-only. |
| 26 | * re-renders when the appearance provider re-injects the variables. | ||
| 27 | */ | 20 | */ |
| 28 | export function useScaledTextRole(role: keyof typeof MOBILE_TYPOGRAPHY): ScaledTextRole { | 21 | export function useScaledTextRole(role: keyof typeof MOBILE_TYPOGRAPHY): ScaledTextRole { |
| 29 | const variable = TEXT_ROLE_VARIABLES[role]; | 22 | const { appearance } = useAppearancePreferences(); |
| 30 | const [fontSize, lineHeight] = useCSSVariable([variable, `${variable}--line-height`]); | 23 | return useMemo(() => { |
| 31 | 24 | const baseFontSize = normalizeBaseFontSize(appearance.baseFontSize); | |
| 32 | return { | 25 | const typography = MOBILE_TYPOGRAPHY[role]; |
| 33 | fontSize: typeof fontSize === "number" ? fontSize : MOBILE_TYPOGRAPHY[role].fontSize, | 26 | return { |
| 34 | lineHeight: typeof lineHeight === "number" ? lineHeight : MOBILE_TYPOGRAPHY[role].lineHeight, | 27 | fontSize: Math.max( |
| 35 | }; | 28 | 8, |
| 29 | Math.round(typography.fontSize * (baseFontSize / DEFAULT_BASE_FONT_SIZE)), | ||
| 30 | ), | ||
| 31 | lineHeight: scaledTypographyLineHeight(typography, baseFontSize), | ||
| 32 | }; | ||
| 33 | }, [appearance.baseFontSize, role]); | ||
| 36 | } | 34 | } |
apps/mobile/src/features/settings/components/SettingsLegalDocumentRouteScreen.tsx+3-7| ... | @@ -6,12 +6,10 @@ import { WebView } from "react-native-webview"; | ... | @@ -6,12 +6,10 @@ import { WebView } from "react-native-webview"; |
| 6 | import { AppText as Text } from "../../../components/AppText"; | 6 | import { AppText as Text } from "../../../components/AppText"; |
| 7 | import { LoadingStrip } from "../../../components/LoadingStrip"; | 7 | import { LoadingStrip } from "../../../components/LoadingStrip"; |
| 8 | import { SymbolView } from "../../../components/AppSymbol"; | 8 | import { SymbolView } from "../../../components/AppSymbol"; |
| 9 | import { useThemeColor } from "../../../lib/useThemeColor"; | ||
| 10 | import { isLegalDocumentUrl, LEGAL_URL } from "../lib/legal-document-url"; | 9 | import { isLegalDocumentUrl, LEGAL_URL } from "../lib/legal-document-url"; |
| 11 | 10 | ||
| 12 | export function SettingsLegalDocumentCloseHeaderButton() { | 11 | export function SettingsLegalDocumentCloseHeaderButton() { |
| 13 | const navigation = useNavigation(); | 12 | const navigation = useNavigation(); |
| 14 | const iconColor = useThemeColor("--color-icon"); | ||
| 15 | 13 | ||
| 16 | return ( | 14 | return ( |
| 17 | <Pressable | 15 | <Pressable |
| ... | @@ -24,7 +22,7 @@ export function SettingsLegalDocumentCloseHeaderButton() { | ... | @@ -24,7 +22,7 @@ export function SettingsLegalDocumentCloseHeaderButton() { |
| 24 | <SymbolView | 22 | <SymbolView |
| 25 | name="xmark" | 23 | name="xmark" |
| 26 | size={18} | 24 | size={18} |
| 27 | tintColor={iconColor} | 25 | tintColorClassName={"accent-icon"} |
| 28 | type="monochrome" | 26 | type="monochrome" |
| 29 | weight="semibold" | 27 | weight="semibold" |
| 30 | /> | 28 | /> |
| ... | @@ -37,7 +35,6 @@ export function SettingsLegalDocumentExternalHeaderButton({ | ... | @@ -37,7 +35,6 @@ export function SettingsLegalDocumentExternalHeaderButton({ |
| 37 | }: { | 35 | }: { |
| 38 | readonly externalUrl?: string; | 36 | readonly externalUrl?: string; |
| 39 | }) { | 37 | }) { |
| 40 | const iconColor = useThemeColor("--color-icon"); | ||
| 41 | const safeExternalUrl = isLegalDocumentUrl(externalUrl) ? externalUrl : LEGAL_URL; | 38 | const safeExternalUrl = isLegalDocumentUrl(externalUrl) ? externalUrl : LEGAL_URL; |
| 42 | 39 | ||
| 43 | return ( | 40 | return ( |
| ... | @@ -51,7 +48,7 @@ export function SettingsLegalDocumentExternalHeaderButton({ | ... | @@ -51,7 +48,7 @@ export function SettingsLegalDocumentExternalHeaderButton({ |
| 51 | <SymbolView | 48 | <SymbolView |
| 52 | name="safari" | 49 | name="safari" |
| 53 | size={19} | 50 | size={19} |
| 54 | tintColor={iconColor} | 51 | tintColorClassName={"accent-icon"} |
| 55 | type="monochrome" | 52 | type="monochrome" |
| 56 | weight="regular" | 53 | weight="regular" |
| 57 | /> | 54 | /> |
| ... | @@ -69,7 +66,6 @@ export function SettingsLegalDocumentRouteScreen({ | ... | @@ -69,7 +66,6 @@ export function SettingsLegalDocumentRouteScreen({ |
| 69 | documentUrl, | 66 | documentUrl, |
| 70 | }: SettingsLegalDocumentRouteScreenProps) { | 67 | }: SettingsLegalDocumentRouteScreenProps) { |
| 71 | const navigation = useNavigation<NavigationProp<ParamListBase>>(); | 68 | const navigation = useNavigation<NavigationProp<ParamListBase>>(); |
| 72 | const iconColor = useThemeColor("--color-icon"); | ||
| 73 | const [reloadKey, setReloadKey] = useState(0); | 69 | const [reloadKey, setReloadKey] = useState(0); |
| 74 | const [loadProgress, setLoadProgress] = useState(0); | 70 | const [loadProgress, setLoadProgress] = useState(0); |
| 75 | const [loadError, setLoadError] = useState<string | null>(null); | 71 | const [loadError, setLoadError] = useState<string | null>(null); |
| ... | @@ -94,7 +90,7 @@ export function SettingsLegalDocumentRouteScreen({ | ... | @@ -94,7 +90,7 @@ export function SettingsLegalDocumentRouteScreen({ |
| 94 | <SymbolView | 90 | <SymbolView |
| 95 | name="exclamationmark.triangle" | 91 | name="exclamationmark.triangle" |
| 96 | size={32} | 92 | size={32} |
| 97 | tintColor={iconColor} | 93 | tintColorClassName={"accent-icon"} |
| 98 | type="monochrome" | 94 | type="monochrome" |
| 99 | weight="regular" | 95 | weight="regular" |
| 100 | /> | 96 | /> |
apps/mobile/src/features/settings/components/SettingsRow.tsx+8-5| ... | @@ -5,7 +5,6 @@ import { Pressable, View } from "react-native"; | ... | @@ -5,7 +5,6 @@ import { Pressable, View } from "react-native"; |
| 5 | import { SymbolView } from "../../../components/AppSymbol"; | 5 | import { SymbolView } from "../../../components/AppSymbol"; |
| 6 | 6 | ||
| 7 | import { AppText as Text } from "../../../components/AppText"; | 7 | import { AppText as Text } from "../../../components/AppText"; |
| 8 | import { useThemeColor } from "../../../lib/useThemeColor"; | ||
| 9 | import type { SettingsLegalDocumentTarget, SettingsSheetTarget } from "./settings-sheet-targets"; | 8 | import type { SettingsLegalDocumentTarget, SettingsSheetTarget } from "./settings-sheet-targets"; |
| 10 | 9 | ||
| 11 | type SymbolName = ComponentProps<typeof SymbolView>["name"]; | 10 | type SymbolName = ComponentProps<typeof SymbolView>["name"]; |
| ... | @@ -20,8 +19,6 @@ export function SettingsRow(props: { | ... | @@ -20,8 +19,6 @@ export function SettingsRow(props: { |
| 20 | readonly onPress?: () => void; | 19 | readonly onPress?: () => void; |
| 21 | }) { | 20 | }) { |
| 22 | const navigation = useNavigation(); | 21 | const navigation = useNavigation(); |
| 23 | const icon = useThemeColor("--color-icon"); | ||
| 24 | const chevron = useThemeColor("--color-chevron"); | ||
| 25 | const content = ( | 22 | const content = ( |
| 26 | <View | 23 | <View |
| 27 | className={ | 24 | className={ |
| ... | @@ -30,7 +27,13 @@ export function SettingsRow(props: { | ... | @@ -30,7 +27,13 @@ export function SettingsRow(props: { |
| 30 | : "flex-row items-center gap-4 p-4" | 27 | : "flex-row items-center gap-4 p-4" |
| 31 | } | 28 | } |
| 32 | > | 29 | > |
| 33 | <SymbolView name={props.icon} size={22} tintColor={icon} type="monochrome" weight="regular" /> | 30 | <SymbolView |
| 31 | name={props.icon} | ||
| 32 | size={22} | ||
| 33 | tintColorClassName={"accent-icon"} | ||
| 34 | type="monochrome" | ||
| 35 | weight="regular" | ||
| 36 | /> | ||
| 34 | <Text className="shrink-0 text-lg text-foreground" numberOfLines={1}> | 37 | <Text className="shrink-0 text-lg text-foreground" numberOfLines={1}> |
| 35 | {props.label} | 38 | {props.label} |
| 36 | </Text> | 39 | </Text> |
| ... | @@ -48,7 +51,7 @@ export function SettingsRow(props: { | ... | @@ -48,7 +51,7 @@ export function SettingsRow(props: { |
| 48 | <SymbolView | 51 | <SymbolView |
| 49 | name="chevron.right" | 52 | name="chevron.right" |
| 50 | size={16} | 53 | size={16} |
| 51 | tintColor={chevron} | 54 | tintColorClassName={"accent-chevron"} |
| 52 | type="monochrome" | 55 | type="monochrome" |
| 53 | weight="semibold" | 56 | weight="semibold" |
| 54 | /> | 57 | /> |
apps/mobile/src/features/settings/components/SettingsSwitchRow.tsx+7-4| ... | @@ -4,7 +4,6 @@ import { View } from "react-native"; | ... | @@ -4,7 +4,6 @@ import { View } from "react-native"; |
| 4 | import { SymbolView } from "../../../components/AppSymbol"; | 4 | import { SymbolView } from "../../../components/AppSymbol"; |
| 5 | import { AppText as Text } from "../../../components/AppText"; | 5 | import { AppText as Text } from "../../../components/AppText"; |
| 6 | import { ThemedSwitch } from "../../../components/ThemedSwitch"; | 6 | import { ThemedSwitch } from "../../../components/ThemedSwitch"; |
| 7 | import { useThemeColor } from "../../../lib/useThemeColor"; | ||
| 8 | 7 | ||
| 9 | type SymbolName = ComponentProps<typeof SymbolView>["name"]; | 8 | type SymbolName = ComponentProps<typeof SymbolView>["name"]; |
| 10 | 9 | ||
| ... | @@ -16,8 +15,6 @@ export function SettingsSwitchRow(props: { | ... | @@ -16,8 +15,6 @@ export function SettingsSwitchRow(props: { |
| 16 | readonly value: boolean; | 15 | readonly value: boolean; |
| 17 | readonly onValueChange: (value: boolean) => void; | 16 | readonly onValueChange: (value: boolean) => void; |
| 18 | }) { | 17 | }) { |
| 19 | const icon = useThemeColor("--color-icon"); | ||
| 20 | |||
| 21 | return ( | 18 | return ( |
| 22 | <View | 19 | <View |
| 23 | className={ | 20 | className={ |
| ... | @@ -26,7 +23,13 @@ export function SettingsSwitchRow(props: { | ... | @@ -26,7 +23,13 @@ export function SettingsSwitchRow(props: { |
| 26 | : "flex-row items-center gap-4 p-4" | 23 | : "flex-row items-center gap-4 p-4" |
| 27 | } | 24 | } |
| 28 | > | 25 | > |
| 29 | <SymbolView name={props.icon} size={22} tintColor={icon} type="monochrome" weight="regular" /> | 26 | <SymbolView |
| 27 | name={props.icon} | ||
| 28 | size={22} | ||
| 29 | tintColorClassName={"accent-icon"} | ||
| 30 | type="monochrome" | ||
| 31 | weight="regular" | ||
| 32 | /> | ||
| 30 | <View className="min-w-0 flex-1"> | 33 | <View className="min-w-0 flex-1"> |
| 31 | <Text className="text-lg text-foreground">{props.label}</Text> | 34 | <Text className="text-lg text-foreground">{props.label}</Text> |
| 32 | {props.subtitle ? ( | 35 | {props.subtitle ? ( |
apps/mobile/src/features/terminal/ThreadTerminalPanel.tsx+6-3| ... | @@ -4,7 +4,6 @@ import { memo, useCallback, useEffect, useMemo, useRef } from "react"; | ... | @@ -4,7 +4,6 @@ import { memo, useCallback, useEffect, useMemo, useRef } from "react"; |
| 4 | import { Pressable, View } from "react-native"; | 4 | import { Pressable, View } from "react-native"; |
| 5 | 5 | ||
| 6 | import { AppText as Text } from "../../components/AppText"; | 6 | import { AppText as Text } from "../../components/AppText"; |
| 7 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 8 | import { terminalEnvironment } from "../../state/terminal"; | 7 | import { terminalEnvironment } from "../../state/terminal"; |
| 9 | import { useAtomCommand } from "../../state/use-atom-command"; | 8 | import { useAtomCommand } from "../../state/use-atom-command"; |
| 10 | import { useAttachedTerminalSession } from "../../state/use-terminal-session"; | 9 | import { useAttachedTerminalSession } from "../../state/use-terminal-session"; |
| ... | @@ -36,7 +35,6 @@ export const ThreadTerminalPanel = memo(function ThreadTerminalPanel( | ... | @@ -36,7 +35,6 @@ export const ThreadTerminalPanel = memo(function ThreadTerminalPanel( |
| 36 | const closeTerminal = useAtomCommand(terminalEnvironment.close, "terminal close"); | 35 | const closeTerminal = useAtomCommand(terminalEnvironment.close, "terminal close"); |
| 37 | const openTerminal = useAtomCommand(terminalEnvironment.open, "terminal open"); | 36 | const openTerminal = useAtomCommand(terminalEnvironment.open, "terminal open"); |
| 38 | const nativeTerminalAvailable = hasNativeTerminalSurface(); | 37 | const nativeTerminalAvailable = hasNativeTerminalSurface(); |
| 39 | const iconColor = useThemeColor("--color-icon"); | ||
| 40 | const terminalId = DEFAULT_TERMINAL_ID; | 38 | const terminalId = DEFAULT_TERMINAL_ID; |
| 41 | const lastGridSizeRef = useRef<TerminalGridSize>({ | 39 | const lastGridSizeRef = useRef<TerminalGridSize>({ |
| 42 | cols: DEFAULT_TERMINAL_COLS, | 40 | cols: DEFAULT_TERMINAL_COLS, |
| ... | @@ -236,7 +234,12 @@ export const ThreadTerminalPanel = memo(function ThreadTerminalPanel( | ... | @@ -236,7 +234,12 @@ export const ThreadTerminalPanel = memo(function ThreadTerminalPanel( |
| 236 | className="h-8 w-8 items-center justify-center rounded-[8px] bg-subtle" | 234 | className="h-8 w-8 items-center justify-center rounded-[8px] bg-subtle" |
| 237 | onPress={props.onClose} | 235 | onPress={props.onClose} |
| 238 | > | 236 | > |
| 239 | <SymbolView name="xmark" size={13} tintColor={iconColor} type="monochrome" /> | 237 | <SymbolView |
| 238 | name="xmark" | ||
| 239 | size={13} | ||
| 240 | tintColorClassName={"accent-icon"} | ||
| 241 | type="monochrome" | ||
| 242 | /> | ||
| 240 | </Pressable> | 243 | </Pressable> |
| 241 | </View> | 244 | </View> |
| 242 | </View> | 245 | </View> |
apps/mobile/src/features/threads/ComposerCommandPopover.tsx+14-17| ... | @@ -5,13 +5,12 @@ import { | ... | @@ -5,13 +5,12 @@ import { |
| 5 | import type { ServerProviderSkill, ServerProviderSlashCommand } from "@t3tools/contracts"; | 5 | import type { ServerProviderSkill, ServerProviderSlashCommand } from "@t3tools/contracts"; |
| 6 | import type { ComposerTriggerKind } from "@t3tools/shared/composerTrigger"; | 6 | import type { ComposerTriggerKind } from "@t3tools/shared/composerTrigger"; |
| 7 | import { memo } from "react"; | 7 | import { memo } from "react"; |
| 8 | import { Pressable, ScrollView, View, type ViewStyle } from "react-native"; | 8 | import { Pressable, ScrollView, StyleSheet, View, type ViewStyle } from "react-native"; |
| 9 | 9 | ||
| 10 | import { SymbolView, type AppSymbolName } from "../../components/AppSymbol"; | 10 | import { SymbolView, type AppSymbolName } from "../../components/AppSymbol"; |
| 11 | import { AppText as Text } from "../../components/AppText"; | 11 | import { AppText as Text } from "../../components/AppText"; |
| 12 | import { GlassSurface } from "../../components/GlassSurface"; | 12 | import { GlassSurface } from "../../components/GlassSurface"; |
| 13 | import { PierreEntryIcon } from "../../components/PierreEntryIcon"; | 13 | import { PierreEntryIcon } from "../../components/PierreEntryIcon"; |
| 14 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 15 | export type ComposerCommandItem = | 14 | export type ComposerCommandItem = |
| 16 | | { | 15 | | { |
| 17 | readonly id: string; | 16 | readonly id: string; |
| ... | @@ -51,7 +50,6 @@ interface ComposerCommandPopoverProps { | ... | @@ -51,7 +50,6 @@ interface ComposerCommandPopoverProps { |
| 51 | } | 50 | } |
| 52 | 51 | ||
| 53 | function PopoverSurface(props: { readonly children: React.ReactNode; readonly style?: ViewStyle }) { | 52 | function PopoverSurface(props: { readonly children: React.ReactNode; readonly style?: ViewStyle }) { |
| 54 | const tintColor = useThemeColor("--color-glass-surface"); | ||
| 55 | const baseStyle: ViewStyle = { | 53 | const baseStyle: ViewStyle = { |
| 56 | borderRadius: 16, | 54 | borderRadius: 16, |
| 57 | overflow: "hidden", | 55 | overflow: "hidden", |
| ... | @@ -59,7 +57,11 @@ function PopoverSurface(props: { readonly children: React.ReactNode; readonly st | ... | @@ -59,7 +57,11 @@ function PopoverSurface(props: { readonly children: React.ReactNode; readonly st |
| 59 | }; | 57 | }; |
| 60 | 58 | ||
| 61 | return ( | 59 | return ( |
| 62 | <GlassSurface glassEffectStyle="clear" tintColor={tintColor} style={baseStyle}> | 60 | <GlassSurface |
| 61 | glassEffectStyle="clear" | ||
| 62 | tintColorClassName="accent-glass-surface" | ||
| 63 | style={baseStyle} | ||
| 64 | > | ||
| 63 | {props.children} | 65 | {props.children} |
| 64 | </GlassSurface> | 66 | </GlassSurface> |
| 65 | ); | 67 | ); |
| ... | @@ -122,27 +124,22 @@ const CommandRow = memo(function CommandRow(props: { | ... | @@ -122,27 +124,22 @@ const CommandRow = memo(function CommandRow(props: { |
| 122 | readonly isSlashSkill: boolean; | 124 | readonly isSlashSkill: boolean; |
| 123 | }) { | 125 | }) { |
| 124 | const iconName = itemIcon(props.item); | 126 | const iconName = itemIcon(props.item); |
| 125 | const iconColor = useThemeColor("--color-icon-subtle"); | ||
| 126 | const borderColor = useThemeColor("--color-border"); | ||
| 127 | 127 | ||
| 128 | return ( | 128 | return ( |
| 129 | <Pressable | 129 | <Pressable |
| 130 | onPress={props.onPress} | 130 | onPress={props.onPress} |
| 131 | style={({ pressed }) => ({ | 131 | className="flex-row items-center gap-2.5 border-border px-3.5 py-2.5 active:opacity-60" |
| 132 | flexDirection: "row", | 132 | style={{ borderBottomWidth: props.isLast ? 0 : StyleSheet.hairlineWidth }} |
| 133 | alignItems: "center", | ||
| 134 | paddingHorizontal: 14, | ||
| 135 | paddingVertical: 10, | ||
| 136 | gap: 10, | ||
| 137 | opacity: pressed ? 0.6 : 1, | ||
| 138 | borderBottomWidth: props.isLast ? 0 : 0.5, | ||
| 139 | borderBottomColor: borderColor, | ||
| 140 | })} | ||
| 141 | > | 133 | > |
| 142 | {props.item.type === "path" ? ( | 134 | {props.item.type === "path" ? ( |
| 143 | <PierreEntryIcon path={props.item.path} kind={props.item.kind} size={16} /> | 135 | <PierreEntryIcon path={props.item.path} kind={props.item.kind} size={16} /> |
| 144 | ) : iconName ? ( | 136 | ) : iconName ? ( |
| 145 | <SymbolView name={iconName} size={14} tintColor={iconColor} type="monochrome" /> | 137 | <SymbolView |
| 138 | name={iconName} | ||
| 139 | size={14} | ||
| 140 | tintColorClassName={"accent-icon-subtle"} | ||
| 141 | type="monochrome" | ||
| 142 | /> | ||
| 146 | ) : null} | 143 | ) : null} |
| 147 | <Text className="shrink-0 text-base font-t3-medium text-foreground" numberOfLines={1}> | 144 | <Text className="shrink-0 text-base font-t3-medium text-foreground" numberOfLines={1}> |
| 148 | {props.isSlashSkill && props.item.type === "skill" ? ( | 145 | {props.isSlashSkill && props.item.type === "skill" ? ( |
apps/mobile/src/features/threads/GitActionProgressOverlay.tsx+17-17| ... | @@ -2,7 +2,7 @@ import * as Haptics from "expo-haptics"; | ... | @@ -2,7 +2,7 @@ import * as Haptics from "expo-haptics"; |
| 2 | import { isLiquidGlassSupported, LiquidGlassView } from "@callstack/liquid-glass"; | 2 | import { isLiquidGlassSupported, LiquidGlassView } from "@callstack/liquid-glass"; |
| 3 | import { SymbolView } from "../../components/AppSymbol"; | 3 | import { SymbolView } from "../../components/AppSymbol"; |
| 4 | import { useCallback, useEffect, useRef } from "react"; | 4 | import { useCallback, useEffect, useRef } from "react"; |
| 5 | import { ActivityIndicator, Pressable, StyleSheet, View } from "react-native"; | 5 | import { ActivityIndicator, Pressable, StyleSheet, useColorScheme, View } from "react-native"; |
| 6 | import Animated, { FadeIn, FadeOut, LinearTransition } from "react-native-reanimated"; | 6 | import Animated, { FadeIn, FadeOut, LinearTransition } from "react-native-reanimated"; |
| 7 | import { useSafeAreaInsets } from "react-native-safe-area-context"; | 7 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 8 | 8 | ||
| ... | @@ -10,9 +10,8 @@ import { AppText as Text } from "../../components/AppText"; | ... | @@ -10,9 +10,8 @@ import { AppText as Text } from "../../components/AppText"; |
| 10 | import { APP_BAR_HEIGHT } from "../../lib/layoutMetrics"; | 10 | import { APP_BAR_HEIGHT } from "../../lib/layoutMetrics"; |
| 11 | import { themeColorWithAlpha } from "../../lib/mobileTheme"; | 11 | import { themeColorWithAlpha } from "../../lib/mobileTheme"; |
| 12 | import { tryOpenExternalUrl } from "../../lib/openExternalUrl"; | 12 | import { tryOpenExternalUrl } from "../../lib/openExternalUrl"; |
| 13 | import { useThemeColor } from "../../lib/useThemeColor"; | 13 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 14 | import type { GitActionProgress } from "../../state/use-vcs-action-state"; | 14 | import type { GitActionProgress } from "../../state/use-vcs-action-state"; |
| 15 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | ||
| 16 | 15 | ||
| 17 | const OVERLAY_LAYOUT_TRANSITION = LinearTransition.duration(220); | 16 | const OVERLAY_LAYOUT_TRANSITION = LinearTransition.duration(220); |
| 18 | const OVERLAY_TOP_GAP = 8; | 17 | const OVERLAY_TOP_GAP = 8; |
| ... | @@ -68,17 +67,16 @@ export function GitActionProgressOverlay(props: { | ... | @@ -68,17 +67,16 @@ export function GitActionProgressOverlay(props: { |
| 68 | 67 | ||
| 69 | function OverlayContent(props: { readonly progress: GitActionProgress }) { | 68 | function OverlayContent(props: { readonly progress: GitActionProgress }) { |
| 70 | const { progress } = props; | 69 | const { progress } = props; |
| 71 | const iconColor = useThemeColor("--color-icon"); | 70 | const colors = useUniwindTheme(); |
| 72 | const glassSurface = useThemeColor("--color-glass-surface"); | 71 | const glassSurface = colors["--color-glass-surface"]; |
| 73 | const foreground = useThemeColor("--color-foreground"); | 72 | const foreground = colors["--color-foreground"]; |
| 74 | const shadowColor = useThemeColor("--color-primary-shadow"); | 73 | const shadowColor = colors["--color-primary-shadow"]; |
| 75 | const { themeAppearance } = useAppearancePreferences(); | 74 | const isDarkMode = useColorScheme() === "dark"; |
| 76 | const isDarkMode = themeAppearance === "dark"; | ||
| 77 | const glassTint = themeColorWithAlpha(String(glassSurface), isDarkMode ? 0.48 : 0.38); | 75 | const glassTint = themeColorWithAlpha(String(glassSurface), isDarkMode ? 0.48 : 0.38); |
| 78 | const glassBorder = themeColorWithAlpha(String(foreground), isDarkMode ? 0.18 : 0.12); | 76 | const glassBorder = themeColorWithAlpha(String(foreground), isDarkMode ? 0.18 : 0.12); |
| 79 | const content = ( | 77 | const content = ( |
| 80 | <> | 78 | <> |
| 81 | <OverlayIcon phase={progress.phase} iconColor={iconColor} /> | 79 | <OverlayIcon phase={progress.phase} /> |
| 82 | 80 | ||
| 83 | <View className="flex-1 gap-0.5"> | 81 | <View className="flex-1 gap-0.5"> |
| 84 | {progress.label ? ( | 82 | {progress.label ? ( |
| ... | @@ -94,7 +92,12 @@ function OverlayContent(props: { readonly progress: GitActionProgress }) { | ... | @@ -94,7 +92,12 @@ function OverlayContent(props: { readonly progress: GitActionProgress }) { |
| 94 | </View> | 92 | </View> |
| 95 | 93 | ||
| 96 | {progress.prUrl ? ( | 94 | {progress.prUrl ? ( |
| 97 | <SymbolView name="arrow.up.right" size={13} tintColor={iconColor} type="monochrome" /> | 95 | <SymbolView |
| 96 | name="arrow.up.right" | ||
| 97 | size={13} | ||
| 98 | tintColorClassName={"accent-icon"} | ||
| 99 | type="monochrome" | ||
| 100 | /> | ||
| 98 | ) : null} | 101 | ) : null} |
| 99 | </> | 102 | </> |
| 100 | ); | 103 | ); |
| ... | @@ -140,7 +143,7 @@ function OverlayContent(props: { readonly progress: GitActionProgress }) { | ... | @@ -140,7 +143,7 @@ function OverlayContent(props: { readonly progress: GitActionProgress }) { |
| 140 | 143 | ||
| 141 | const bgClass = | 144 | const bgClass = |
| 142 | progress.phase === "error" | 145 | progress.phase === "error" |
| 143 | ? "bg-red-50 dark:bg-red-950/80 border-red-200 dark:border-red-800" | 146 | ? "border-adaptive-red-200-800 bg-adaptive-red-50-950-a80" |
| 144 | : "bg-card border-border"; | 147 | : "bg-card border-border"; |
| 145 | 148 | ||
| 146 | return ( | 149 | return ( |
| ... | @@ -153,13 +156,10 @@ function OverlayContent(props: { readonly progress: GitActionProgress }) { | ... | @@ -153,13 +156,10 @@ function OverlayContent(props: { readonly progress: GitActionProgress }) { |
| 153 | ); | 156 | ); |
| 154 | } | 157 | } |
| 155 | 158 | ||
| 156 | function OverlayIcon(props: { | 159 | function OverlayIcon(props: { readonly phase: GitActionProgress["phase"] }) { |
| 157 | readonly phase: GitActionProgress["phase"]; | ||
| 158 | readonly iconColor: ReturnType<typeof useThemeColor>; | ||
| 159 | }) { | ||
| 160 | switch (props.phase) { | 160 | switch (props.phase) { |
| 161 | case "running": | 161 | case "running": |
| 162 | return <ActivityIndicator size="small" />; | 162 | return <ActivityIndicator size="small" colorClassName={"accent-icon"} />; |
| 163 | case "success": | 163 | case "success": |
| 164 | return ( | 164 | return ( |
| 165 | <View className="h-6 w-6 items-center justify-center rounded-full bg-green-500"> | 165 | <View className="h-6 w-6 items-center justify-center rounded-full bg-green-500"> |
apps/mobile/src/features/threads/NewTaskContextPickerScreens.tsx+10-9| ... | @@ -24,7 +24,7 @@ import { AppText as Text } from "../../components/AppText"; | ... | @@ -24,7 +24,7 @@ import { AppText as Text } from "../../components/AppText"; |
| 24 | import { ThemedSwitch } from "../../components/ThemedSwitch"; | 24 | import { ThemedSwitch } from "../../components/ThemedSwitch"; |
| 25 | import { cn } from "../../lib/cn"; | 25 | import { cn } from "../../lib/cn"; |
| 26 | import { useFontFamily } from "../../lib/useFontFamily"; | 26 | import { useFontFamily } from "../../lib/useFontFamily"; |
| 27 | import { useThemeColor } from "../../lib/useThemeColor"; | 27 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 28 | import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader"; | 28 | import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader"; |
| 29 | import { useAtomCommand } from "../../state/use-atom-command"; | 29 | import { useAtomCommand } from "../../state/use-atom-command"; |
| 30 | import { vcsEnvironment } from "../../state/vcs"; | 30 | import { vcsEnvironment } from "../../state/vcs"; |
| ... | @@ -45,9 +45,6 @@ function SelectionRow(props: { | ... | @@ -45,9 +45,6 @@ function SelectionRow(props: { |
| 45 | readonly subtitle?: string; | 45 | readonly subtitle?: string; |
| 46 | readonly title: string; | 46 | readonly title: string; |
| 47 | }) { | 47 | }) { |
| 48 | const iconColor = useThemeColor("--color-icon-muted"); | ||
| 49 | const checkmarkColor = useThemeColor("--color-icon"); | ||
| 50 | |||
| 51 | return ( | 48 | return ( |
| 52 | <Pressable | 49 | <Pressable |
| 53 | accessibilityLabel={[props.title, props.subtitle].filter(Boolean).join(", ")} | 50 | accessibilityLabel={[props.title, props.subtitle].filter(Boolean).join(", ")} |
| ... | @@ -62,7 +59,12 @@ function SelectionRow(props: { | ... | @@ -62,7 +59,12 @@ function SelectionRow(props: { |
| 62 | style={{ opacity: props.disabled ? 0.45 : 1 }} | 59 | style={{ opacity: props.disabled ? 0.45 : 1 }} |
| 63 | > | 60 | > |
| 64 | {props.icon ? ( | 61 | {props.icon ? ( |
| 65 | <SymbolView name={props.icon} size={17} tintColor={iconColor} type="monochrome" /> | 62 | <SymbolView |
| 63 | name={props.icon} | ||
| 64 | size={17} | ||
| 65 | tintColorClassName={"accent-icon-muted"} | ||
| 66 | type="monochrome" | ||
| 67 | /> | ||
| 66 | ) : null} | 68 | ) : null} |
| 67 | <View className="min-w-0 flex-1 gap-0.5"> | 69 | <View className="min-w-0 flex-1 gap-0.5"> |
| 68 | <Text className="text-base font-t3-medium text-foreground" numberOfLines={1}> | 70 | <Text className="text-base font-t3-medium text-foreground" numberOfLines={1}> |
| ... | @@ -78,7 +80,7 @@ function SelectionRow(props: { | ... | @@ -78,7 +80,7 @@ function SelectionRow(props: { |
| 78 | <SymbolView | 80 | <SymbolView |
| 79 | name="checkmark" | 81 | name="checkmark" |
| 80 | size={16} | 82 | size={16} |
| 81 | tintColor={checkmarkColor} | 83 | tintColorClassName={"accent-icon"} |
| 82 | type="monochrome" | 84 | type="monochrome" |
| 83 | weight="semibold" | 85 | weight="semibold" |
| 84 | /> | 86 | /> |
| ... | @@ -191,8 +193,7 @@ export function NewTaskBranchPickerRouteScreen() { | ... | @@ -191,8 +193,7 @@ export function NewTaskBranchPickerRouteScreen() { |
| 191 | const flow = useNewTaskFlow(); | 193 | const flow = useNewTaskFlow(); |
| 192 | const navigation = useNavigation(); | 194 | const navigation = useNavigation(); |
| 193 | const insets = useSafeAreaInsets(); | 195 | const insets = useSafeAreaInsets(); |
| 194 | const placeholderColor = useThemeColor("--color-placeholder"); | 196 | const foregroundColor = useUniwindTheme()["--color-foreground"]; |
| 195 | const foregroundColor = useThemeColor("--color-foreground"); | ||
| 196 | const fontFamily = useFontFamily("regular"); | 197 | const fontFamily = useFontFamily("regular"); |
| 197 | const switchRef = useAtomCommand(vcsEnvironment.switchRef, { reportFailure: false }); | 198 | const switchRef = useAtomCommand(vcsEnvironment.switchRef, { reportFailure: false }); |
| 198 | const [switchingBranchName, setSwitchingBranchName] = useState<string | null>(null); | 199 | const [switchingBranchName, setSwitchingBranchName] = useState<string | null>(null); |
| ... | @@ -419,7 +420,7 @@ export function NewTaskBranchPickerRouteScreen() { | ... | @@ -419,7 +420,7 @@ export function NewTaskBranchPickerRouteScreen() { |
| 419 | className="h-11 rounded-xl bg-card px-4 text-base text-foreground" | 420 | className="h-11 rounded-xl bg-card px-4 text-base text-foreground" |
| 420 | onChangeText={flow.setBranchQuery} | 421 | onChangeText={flow.setBranchQuery} |
| 421 | placeholder="Find a branch" | 422 | placeholder="Find a branch" |
| 422 | placeholderTextColor={placeholderColor} | 423 | placeholderTextColorClassName={"accent-placeholder"} |
| 423 | style={{ color: foregroundColor, fontFamily }} | 424 | style={{ color: foregroundColor, fontFamily }} |
| 424 | value={flow.branchQuery} | 425 | value={flow.branchQuery} |
| 425 | /> | 426 | /> |
apps/mobile/src/features/threads/NewTaskDraftScreen.tsx+24-26| ... | @@ -13,8 +13,7 @@ import { | ... | @@ -13,8 +13,7 @@ import { |
| 13 | useKeyboardState, | 13 | useKeyboardState, |
| 14 | } from "react-native-keyboard-controller"; | 14 | } from "react-native-keyboard-controller"; |
| 15 | import { useSafeAreaInsets } from "react-native-safe-area-context"; | 15 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 16 | import { useThemeColor } from "../../lib/useThemeColor"; | 16 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 17 | import { themeColorWithAlpha } from "../../lib/mobileTheme"; | ||
| 18 | import { useFontFamily } from "../../lib/useFontFamily"; | 17 | import { useFontFamily } from "../../lib/useFontFamily"; |
| 19 | 18 | ||
| 20 | import { | 19 | import { |
| ... | @@ -43,7 +42,6 @@ import { | ... | @@ -43,7 +42,6 @@ import { |
| 43 | import { makeTurnCommandMetadata } from "../../lib/commandMetadata"; | 42 | import { makeTurnCommandMetadata } from "../../lib/commandMetadata"; |
| 44 | import { convertPastedImagesToAttachments, pickComposerImages } from "../../lib/composerImages"; | 43 | import { convertPastedImagesToAttachments, pickComposerImages } from "../../lib/composerImages"; |
| 45 | import { useScaledTextRole } from "../settings/appearance/useScaledTextRole"; | 44 | import { useScaledTextRole } from "../settings/appearance/useScaledTextRole"; |
| 46 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | ||
| 47 | import { | 45 | import { |
| 48 | clearComposerDraftContent, | 46 | clearComposerDraftContent, |
| 49 | getComposerDraftSnapshot, | 47 | getComposerDraftSnapshot, |
| ... | @@ -71,17 +69,32 @@ function NewTaskWorkspaceIcon(props: { | ... | @@ -71,17 +69,32 @@ function NewTaskWorkspaceIcon(props: { |
| 71 | readonly workspaceMode: "local" | "worktree"; | 69 | readonly workspaceMode: "local" | "worktree"; |
| 72 | readonly worktreePath: string | null; | 70 | readonly worktreePath: string | null; |
| 73 | }) { | 71 | }) { |
| 74 | const iconColor = useThemeColor("--color-icon-muted"); | ||
| 75 | |||
| 76 | if (props.workspaceMode === "local" && props.worktreePath === null) { | 72 | if (props.workspaceMode === "local" && props.worktreePath === null) { |
| 77 | return <SymbolView name="folder" size={16} tintColor={iconColor} type="monochrome" />; | 73 | return ( |
| 74 | <SymbolView | ||
| 75 | name="folder" | ||
| 76 | size={16} | ||
| 77 | tintColorClassName={"accent-icon-muted"} | ||
| 78 | type="monochrome" | ||
| 79 | /> | ||
| 80 | ); | ||
| 78 | } | 81 | } |
| 79 | 82 | ||
| 80 | return ( | 83 | return ( |
| 81 | <View className="size-4"> | 84 | <View className="size-4"> |
| 82 | <SymbolView name="folder" size={16} tintColor={iconColor} type="monochrome" /> | 85 | <SymbolView |
| 86 | name="folder" | ||
| 87 | size={16} | ||
| 88 | tintColorClassName={"accent-icon-muted"} | ||
| 89 | type="monochrome" | ||
| 90 | /> | ||
| 83 | <View className="absolute -right-1 -bottom-1"> | 91 | <View className="absolute -right-1 -bottom-1"> |
| 84 | <SymbolView name="arrow.triangle.branch" size={9} tintColor={iconColor} type="monochrome" /> | 92 | <SymbolView |
| 93 | name="arrow.triangle.branch" | ||
| 94 | size={9} | ||
| 95 | tintColorClassName={"accent-icon-muted"} | ||
| 96 | type="monochrome" | ||
| 97 | /> | ||
| 85 | </View> | 98 | </View> |
| 86 | </View> | 99 | </View> |
| 87 | ); | 100 | ); |
| ... | @@ -109,7 +122,6 @@ export function NewTaskDraftScreen(props: { | ... | @@ -109,7 +122,6 @@ export function NewTaskDraftScreen(props: { |
| 109 | reserveShare, | 122 | reserveShare, |
| 110 | } = useIncomingShare(); | 123 | } = useIncomingShare(); |
| 111 | const insets = useSafeAreaInsets(); | 124 | const insets = useSafeAreaInsets(); |
| 112 | const { themeAppearance: colorScheme } = useAppearancePreferences(); | ||
| 113 | const isKeyboardVisible = useKeyboardState((state) => state.isVisible); | 125 | const isKeyboardVisible = useKeyboardState((state) => state.isVisible); |
| 114 | const controlsBottomPadding = Math.max(insets.bottom, 10); | 126 | const controlsBottomPadding = Math.max(insets.bottom, 10); |
| 115 | const keyboardOpenedOffset = Math.max(0, controlsBottomPadding - 8); | 127 | const keyboardOpenedOffset = Math.max(0, controlsBottomPadding - 8); |
| ... | @@ -291,13 +303,9 @@ export function NewTaskDraftScreen(props: { | ... | @@ -291,13 +303,9 @@ export function NewTaskDraftScreen(props: { |
| 291 | }; | 303 | }; |
| 292 | }, [props.pendingTaskId, cancelEditingPendingTask]); | 304 | }, [props.pendingTaskId, cancelEditingPendingTask]); |
| 293 | 305 | ||
| 294 | const foregroundColor = useThemeColor("--color-foreground"); | 306 | const foregroundColor = useUniwindTheme()["--color-foreground"]; |
| 295 | const sheetColor = String(useThemeColor("--color-sheet")); | ||
| 296 | const projectUnderlineColor = useThemeColor("--color-foreground-muted"); | ||
| 297 | const regularFontFamily = useFontFamily("regular"); | 307 | const regularFontFamily = useFontFamily("regular"); |
| 298 | const bodyText = useScaledTextRole("body"); | 308 | const bodyText = useScaledTextRole("body"); |
| 299 | const sheetFadeOpaque = sheetColor; | ||
| 300 | const sheetFadeTransparent = themeColorWithAlpha(sheetColor, 0); | ||
| 301 | 309 | ||
| 302 | // A new navigation to this mounted screen delivers a fresh initialProjectRef | 310 | // A new navigation to this mounted screen delivers a fresh initialProjectRef |
| 303 | // reference — treat it as a new request and let it apply again. | 311 | // reference — treat it as a new request and let it apply again. |
| ... | @@ -797,7 +805,6 @@ export function NewTaskDraftScreen(props: { | ... | @@ -797,7 +805,6 @@ export function NewTaskDraftScreen(props: { |
| 797 | } | 805 | } |
| 798 | 806 | ||
| 799 | const isAndroid = Platform.OS === "android"; | 807 | const isAndroid = Platform.OS === "android"; |
| 800 | const isDarkMode = colorScheme === "dark"; | ||
| 801 | const canStart = | 808 | const canStart = |
| 802 | Boolean(flow.selectedProject) && | 809 | Boolean(flow.selectedProject) && |
| 803 | Boolean(flow.selectedModel) && | 810 | Boolean(flow.selectedModel) && |
| ... | @@ -874,11 +881,7 @@ export function NewTaskDraftScreen(props: { | ... | @@ -874,11 +881,7 @@ export function NewTaskDraftScreen(props: { |
| 874 | accessibilityRole="button" | 881 | accessibilityRole="button" |
| 875 | disabled={isIncomingShareTransferPending} | 882 | disabled={isIncomingShareTransferPending} |
| 876 | onPress={chooseProject} | 883 | onPress={chooseProject} |
| 877 | className="min-w-0 max-w-[250px] active:opacity-65" | 884 | className="min-w-0 max-w-[250px] border-b border-foreground-muted active:opacity-65" |
| 878 | style={{ | ||
| 879 | borderBottomColor: projectUnderlineColor, | ||
| 880 | borderBottomWidth: 1, | ||
| 881 | }} | ||
| 882 | > | 885 | > |
| 883 | <Text | 886 | <Text |
| 884 | className="text-2xl font-t3-medium tracking-tight text-foreground" | 887 | className="text-2xl font-t3-medium tracking-tight text-foreground" |
| ... | @@ -960,7 +963,6 @@ export function NewTaskDraftScreen(props: { | ... | @@ -960,7 +963,6 @@ export function NewTaskDraftScreen(props: { |
| 960 | 963 | ||
| 961 | <ComposerSurface | 964 | <ComposerSurface |
| 962 | animateLayout={false} | 965 | animateLayout={false} |
| 963 | isDarkMode={isDarkMode} | ||
| 964 | style={{ | 966 | style={{ |
| 965 | borderRadius: 26, | 967 | borderRadius: 26, |
| 966 | minHeight: 140, | 968 | minHeight: 140, |
| ... | @@ -984,11 +986,7 @@ export function NewTaskDraftScreen(props: { | ... | @@ -984,11 +986,7 @@ export function NewTaskDraftScreen(props: { |
| 984 | {promptEditor} | 986 | {promptEditor} |
| 985 | 987 | ||
| 986 | <ComposerToolbarRow paddingBottom={0} paddingHorizontal={0} paddingTop={4}> | 988 | <ComposerToolbarRow paddingBottom={0} paddingHorizontal={0} paddingTop={4}> |
| 987 | <ComposerToolbarScroller | 989 | <ComposerToolbarScroller contentPaddingRight={8} fadeSurface="sheet"> |
| 988 | fadeOpaque={sheetFadeOpaque} | ||
| 989 | fadeTransparent={sheetFadeTransparent} | ||
| 990 | contentPaddingRight={8} | ||
| 991 | > | ||
| 992 | <ComposerToolbarButton | 990 | <ComposerToolbarButton |
| 993 | accessibilityLabel="Add attachment" | 991 | accessibilityLabel="Add attachment" |
| 994 | disabled={isIncomingShareTransferPending} | 992 | disabled={isIncomingShareTransferPending} |
apps/mobile/src/features/threads/NewTaskRouteScreen.tsx+4-5| ... | @@ -10,7 +10,6 @@ import type { EnvironmentProject } from "@t3tools/client-runtime/state/shell"; | ... | @@ -10,7 +10,6 @@ import type { EnvironmentProject } from "@t3tools/client-runtime/state/shell"; |
| 10 | import { useEffect, useRef } from "react"; | 10 | import { useEffect, useRef } from "react"; |
| 11 | import { ActivityIndicator, Alert, Platform, Pressable, ScrollView, View } from "react-native"; | 11 | import { ActivityIndicator, Alert, Platform, Pressable, ScrollView, View } from "react-native"; |
| 12 | import { useSafeAreaInsets } from "react-native-safe-area-context"; | 12 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 13 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 14 | import { cn } from "../../lib/cn"; | 13 | import { cn } from "../../lib/cn"; |
| 15 | 14 | ||
| 16 | import { AndroidScreenHeader } from "../../components/AndroidScreenHeader"; | 15 | import { AndroidScreenHeader } from "../../components/AndroidScreenHeader"; |
| ... | @@ -91,8 +90,6 @@ export function NewTaskRouteScreen({ route }: StaticScreenProps<NewTaskRoutePara | ... | @@ -91,8 +90,6 @@ export function NewTaskRouteScreen({ route }: StaticScreenProps<NewTaskRoutePara |
| 91 | const isFocused = useIsFocused(); | 90 | const isFocused = useIsFocused(); |
| 92 | const { layout } = useAdaptiveWorkspaceLayout(); | 91 | const { layout } = useAdaptiveWorkspaceLayout(); |
| 93 | const insets = useSafeAreaInsets(); | 92 | const insets = useSafeAreaInsets(); |
| 94 | const chevronColor = useThemeColor("--color-chevron"); | ||
| 95 | const accentColor = useThemeColor("--color-icon-muted"); | ||
| 96 | const { getShare, releaseShareReservation } = useIncomingShare(); | 93 | const { getShare, releaseShareReservation } = useIncomingShare(); |
| 97 | const routeShareId = Array.isArray(route.params?.incomingShareId) | 94 | const routeShareId = Array.isArray(route.params?.incomingShareId) |
| 98 | ? route.params.incomingShareId[0] | 95 | ? route.params.incomingShareId[0] |
| ... | @@ -242,7 +239,9 @@ export function NewTaskRouteScreen({ route }: StaticScreenProps<NewTaskRoutePara | ... | @@ -242,7 +239,9 @@ export function NewTaskRouteScreen({ route }: StaticScreenProps<NewTaskRoutePara |
| 242 | > | 239 | > |
| 243 | {projectScopes.length === 0 ? ( | 240 | {projectScopes.length === 0 ? ( |
| 244 | <View collapsable={false} className="items-center gap-3 rounded-[24px] bg-card px-6 py-8"> | 241 | <View collapsable={false} className="items-center gap-3 rounded-[24px] bg-card px-6 py-8"> |
| 245 | {projectEmptyState.loading ? <ActivityIndicator color={accentColor} /> : null} | 242 | {projectEmptyState.loading ? ( |
| 243 | <ActivityIndicator colorClassName={"accent-icon-muted"} /> | ||
| 244 | ) : null} | ||
| 246 | <Text className="text-center text-lg font-t3-bold text-foreground"> | 245 | <Text className="text-center text-lg font-t3-bold text-foreground"> |
| 247 | {projectEmptyState.title} | 246 | {projectEmptyState.title} |
| 248 | </Text> | 247 | </Text> |
| ... | @@ -308,7 +307,7 @@ export function NewTaskRouteScreen({ route }: StaticScreenProps<NewTaskRoutePara | ... | @@ -308,7 +307,7 @@ export function NewTaskRouteScreen({ route }: StaticScreenProps<NewTaskRoutePara |
| 308 | <SymbolView | 307 | <SymbolView |
| 309 | name="chevron.right" | 308 | name="chevron.right" |
| 310 | size={14} | 309 | size={14} |
| 311 | tintColor={chevronColor} | 310 | tintColorClassName={"accent-chevron"} |
| 312 | type="monochrome" | 311 | type="monochrome" |
| 313 | /> | 312 | /> |
| 314 | </Pressable> | 313 | </Pressable> |
apps/mobile/src/features/threads/PendingApprovalCard.tsx+8-8| ... | @@ -28,15 +28,15 @@ export function PendingApprovalCard(props: PendingApprovalCardProps) { | ... | @@ -28,15 +28,15 @@ export function PendingApprovalCard(props: PendingApprovalCardProps) { |
| 28 | // Opaque for the same reason as PendingUserInputCard: nothing blurs the feed | 28 | // Opaque for the same reason as PendingUserInputCard: nothing blurs the feed |
| 29 | // behind this card, so a translucent surface bleeds messages through it. | 29 | // behind this card, so a translucent surface bleeds messages through it. |
| 30 | return ( | 30 | return ( |
| 31 | <View className="gap-2.5 rounded-[20px] border border-neutral-200 bg-neutral-100 p-4 dark:border-white/6 dark:bg-neutral-900"> | 31 | <View className="gap-2.5 rounded-[20px] border border-adaptive-neutral-200-white-a6 bg-adaptive-neutral-100-900 p-4"> |
| 32 | <Text className="font-t3-bold text-2xs uppercase tracking-[1.1px] text-sky-700 dark:text-sky-300"> | 32 | <Text className="font-t3-bold text-2xs uppercase tracking-[1.1px] text-adaptive-sky-700-300"> |
| 33 | Approval needed | 33 | Approval needed |
| 34 | </Text> | 34 | </Text> |
| 35 | <Text className="font-t3-bold text-lg text-neutral-950 dark:text-neutral-50"> | 35 | <Text className="font-t3-bold text-lg text-adaptive-neutral-950-50"> |
| 36 | {props.approval.appName ?? props.approval.requestKind} | 36 | {props.approval.appName ?? props.approval.requestKind} |
| 37 | </Text> | 37 | </Text> |
| 38 | {props.approval.detail ? ( | 38 | {props.approval.detail ? ( |
| 39 | <Text className="font-sans text-sm leading-normal text-neutral-600 dark:text-neutral-400"> | 39 | <Text className="font-sans text-sm leading-normal text-adaptive-neutral-600-400"> |
| 40 | {props.approval.detail} | 40 | {props.approval.detail} |
| 41 | </Text> | 41 | </Text> |
| 42 | ) : null} | 42 | ) : null} |
| ... | @@ -48,8 +48,8 @@ export function PendingApprovalCard(props: PendingApprovalCardProps) { | ... | @@ -48,8 +48,8 @@ export function PendingApprovalCard(props: PendingApprovalCardProps) { |
| 48 | option.decision === "accept" | 48 | option.decision === "accept" |
| 49 | ? "bg-blue-500" | 49 | ? "bg-blue-500" |
| 50 | : option.decision === "decline" | 50 | : option.decision === "decline" |
| 51 | ? "bg-rose-100 dark:bg-rose-500/18" | 51 | ? "bg-adaptive-rose-100-500-a18" |
| 52 | : "bg-neutral-200 dark:bg-neutral-800" | 52 | : "bg-adaptive-neutral-200-800" |
| 53 | }`} | 53 | }`} |
| 54 | disabled={props.respondingApprovalId === props.approval.requestId} | 54 | disabled={props.respondingApprovalId === props.approval.requestId} |
| 55 | onPress={() => void props.onRespond(props.approval.requestId, option.decision)} | 55 | onPress={() => void props.onRespond(props.approval.requestId, option.decision)} |
| ... | @@ -59,8 +59,8 @@ export function PendingApprovalCard(props: PendingApprovalCardProps) { | ... | @@ -59,8 +59,8 @@ export function PendingApprovalCard(props: PendingApprovalCardProps) { |
| 59 | option.decision === "accept" | 59 | option.decision === "accept" |
| 60 | ? "font-t3-extrabold text-white" | 60 | ? "font-t3-extrabold text-white" |
| 61 | : option.decision === "decline" | 61 | : option.decision === "decline" |
| 62 | ? "font-t3-bold text-rose-700 dark:text-rose-300" | 62 | ? "font-t3-bold text-adaptive-rose-700-300" |
| 63 | : "font-t3-bold text-neutral-950 dark:text-neutral-50" | 63 | : "font-t3-bold text-adaptive-neutral-950-50" |
| 64 | }`} | 64 | }`} |
| 65 | > | 65 | > |
| 66 | {option.label} | 66 | {option.label} |
apps/mobile/src/features/threads/PendingUserInputCard.tsx+28-20| ... | @@ -18,7 +18,6 @@ import { SymbolView } from "../../components/AppSymbol"; | ... | @@ -18,7 +18,6 @@ import { SymbolView } from "../../components/AppSymbol"; |
| 18 | import { AppText as Text, AppTextInput as TextInput } from "../../components/AppText"; | 18 | import { AppText as Text, AppTextInput as TextInput } from "../../components/AppText"; |
| 19 | import { ControlPill } from "../../components/ControlPill"; | 19 | import { ControlPill } from "../../components/ControlPill"; |
| 20 | import { cn } from "../../lib/cn"; | 20 | import { cn } from "../../lib/cn"; |
| 21 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 22 | import { | 21 | import { |
| 23 | isPendingUserInputOptionSelected, | 22 | isPendingUserInputOptionSelected, |
| 24 | type PendingUserInput, | 23 | type PendingUserInput, |
| ... | @@ -87,7 +86,6 @@ const EXPANDED_CARD_IS_OVERLAY = Platform.OS === "ios"; | ... | @@ -87,7 +86,6 @@ const EXPANDED_CARD_IS_OVERLAY = Platform.OS === "ios"; |
| 87 | const CARD_LAYOUT_TRANSITION = LinearTransition.duration(200); | 86 | const CARD_LAYOUT_TRANSITION = LinearTransition.duration(200); |
| 88 | 87 | ||
| 89 | export function PendingUserInputCard(props: PendingUserInputCardProps) { | 88 | export function PendingUserInputCard(props: PendingUserInputCardProps) { |
| 90 | const iconSubtle = useThemeColor("--color-icon-subtle"); | ||
| 91 | const questionCount = props.pendingUserInput.questions.length; | 89 | const questionCount = props.pendingUserInput.questions.length; |
| 92 | 90 | ||
| 93 | const cardCoverage = props.cardCoverage; | 91 | const cardCoverage = props.cardCoverage; |
| ... | @@ -163,7 +161,7 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { | ... | @@ -163,7 +161,7 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { |
| 163 | pointerEvents={props.collapsed ? "auto" : "none"} | 161 | pointerEvents={props.collapsed ? "auto" : "none"} |
| 164 | accessibilityElementsHidden={!props.collapsed} | 162 | accessibilityElementsHidden={!props.collapsed} |
| 165 | importantForAccessibility={props.collapsed ? "auto" : "no-hide-descendants"} | 163 | importantForAccessibility={props.collapsed ? "auto" : "no-hide-descendants"} |
| 166 | className="flex-row items-center gap-2 rounded-full border border-neutral-200 bg-neutral-100 py-1.5 pl-4 pr-1.5 dark:border-white/6 dark:bg-neutral-900" | 164 | className="flex-row items-center gap-2 rounded-full border border-adaptive-neutral-200-white-a6 bg-adaptive-neutral-100-900 py-1.5 pl-4 pr-1.5" |
| 167 | > | 165 | > |
| 168 | <Pressable | 166 | <Pressable |
| 169 | accessibilityRole="button" | 167 | accessibilityRole="button" |
| ... | @@ -173,14 +171,19 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { | ... | @@ -173,14 +171,19 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { |
| 173 | onPress={props.onToggleCollapsed} | 171 | onPress={props.onToggleCollapsed} |
| 174 | className="min-h-10 flex-1 flex-row items-center gap-2 active:opacity-70" | 172 | className="min-h-10 flex-1 flex-row items-center gap-2 active:opacity-70" |
| 175 | > | 173 | > |
| 176 | <Text className="font-t3-bold text-2xs uppercase tracking-[1.1px] text-sky-700 dark:text-sky-300"> | 174 | <Text className="font-t3-bold text-2xs uppercase tracking-[1.1px] text-adaptive-sky-700-300"> |
| 177 | User input needed | 175 | User input needed |
| 178 | </Text> | 176 | </Text> |
| 179 | <Text className="font-sans text-xs text-neutral-500 dark:text-neutral-400"> | 177 | <Text className="font-sans text-xs text-adaptive-neutral-500-400"> |
| 180 | {questionCount} question{questionCount === 1 ? "" : "s"} | 178 | {questionCount} question{questionCount === 1 ? "" : "s"} |
| 181 | </Text> | 179 | </Text> |
| 182 | <View className="flex-1" /> | 180 | <View className="flex-1" /> |
| 183 | <SymbolView name="chevron.up" size={12} tintColor={iconSubtle} type="monochrome" /> | 181 | <SymbolView |
| 182 | name="chevron.up" | ||
| 183 | size={12} | ||
| 184 | tintColorClassName={"accent-icon-subtle"} | ||
| 185 | type="monochrome" | ||
| 186 | /> | ||
| 184 | </Pressable> | 187 | </Pressable> |
| 185 | {props.onStopThread ? ( | 188 | {props.onStopThread ? ( |
| 186 | <ControlPill | 189 | <ControlPill |
| ... | @@ -213,7 +216,7 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { | ... | @@ -213,7 +216,7 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { |
| 213 | : FadeOutDown.duration(USER_INPUT_TOGGLE_DURATION_MS).easing(Easing.out(Easing.cubic)) | 216 | : FadeOutDown.duration(USER_INPUT_TOGGLE_DURATION_MS).easing(Easing.out(Easing.cubic)) |
| 214 | } | 217 | } |
| 215 | layout={CARD_LAYOUT_TRANSITION} | 218 | layout={CARD_LAYOUT_TRANSITION} |
| 216 | className="overflow-hidden gap-2.5 rounded-[20px] border border-neutral-200 bg-neutral-100 p-4 dark:border-white/6 dark:bg-neutral-900" | 219 | className="overflow-hidden gap-2.5 rounded-[20px] border border-adaptive-neutral-200-white-a6 bg-adaptive-neutral-100-900 p-4" |
| 217 | style={ | 220 | style={ |
| 218 | EXPANDED_CARD_IS_OVERLAY | 221 | EXPANDED_CARD_IS_OVERLAY |
| 219 | ? [{ maxHeight: props.maxHeight }, cardAnimatedStyle] | 222 | ? [{ maxHeight: props.maxHeight }, cardAnimatedStyle] |
| ... | @@ -227,15 +230,20 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { | ... | @@ -227,15 +230,20 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { |
| 227 | className="flex-row items-start gap-2" | 230 | className="flex-row items-start gap-2" |
| 228 | > | 231 | > |
| 229 | <View className="flex-1 gap-2.5"> | 232 | <View className="flex-1 gap-2.5"> |
| 230 | <Text className="font-t3-bold text-2xs uppercase tracking-[1.1px] text-sky-700 dark:text-sky-300"> | 233 | <Text className="font-t3-bold text-2xs uppercase tracking-[1.1px] text-adaptive-sky-700-300"> |
| 231 | User input needed | 234 | User input needed |
| 232 | </Text> | 235 | </Text> |
| 233 | <Text className="font-t3-bold text-lg text-neutral-950 dark:text-neutral-50"> | 236 | <Text className="font-t3-bold text-lg text-adaptive-neutral-950-50"> |
| 234 | Fill in the pending answers | 237 | Fill in the pending answers |
| 235 | </Text> | 238 | </Text> |
| 236 | </View> | 239 | </View> |
| 237 | <View className="h-8 w-8 items-center justify-center rounded-full bg-neutral-200/70 dark:bg-white/8"> | 240 | <View className="h-8 w-8 items-center justify-center rounded-full bg-adaptive-neutral-200-a70-white-a8"> |
| 238 | <SymbolView name="chevron.down" size={13} tintColor={iconSubtle} type="monochrome" /> | 241 | <SymbolView |
| 242 | name="chevron.down" | ||
| 243 | size={13} | ||
| 244 | tintColorClassName={"accent-icon-subtle"} | ||
| 245 | type="monochrome" | ||
| 246 | /> | ||
| 239 | </View> | 247 | </View> |
| 240 | </Pressable> | 248 | </Pressable> |
| 241 | <ScrollView | 249 | <ScrollView |
| ... | @@ -251,10 +259,10 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { | ... | @@ -251,10 +259,10 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { |
| 251 | const draft = props.drafts[question.id]; | 259 | const draft = props.drafts[question.id]; |
| 252 | return ( | 260 | return ( |
| 253 | <View key={question.id} className="gap-2 pt-1"> | 261 | <View key={question.id} className="gap-2 pt-1"> |
| 254 | <Text className="font-t3-bold text-xs uppercase tracking-[1px] text-neutral-500 dark:text-neutral-500"> | 262 | <Text className="font-t3-bold text-xs uppercase tracking-[1px] text-neutral-500"> |
| 255 | {question.header} | 263 | {question.header} |
| 256 | </Text> | 264 | </Text> |
| 257 | <Text className="font-sans text-base leading-snug text-neutral-950 dark:text-neutral-50"> | 265 | <Text className="font-sans text-base leading-snug text-adaptive-neutral-950-50"> |
| 258 | {question.question} | 266 | {question.question} |
| 259 | </Text> | 267 | </Text> |
| 260 | <View className="gap-2"> | 268 | <View className="gap-2"> |
| ... | @@ -268,8 +276,8 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { | ... | @@ -268,8 +276,8 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { |
| 268 | className={cn( | 276 | className={cn( |
| 269 | "min-h-12 w-full rounded-2xl border px-3.5 py-3", | 277 | "min-h-12 w-full rounded-2xl border px-3.5 py-3", |
| 270 | selected | 278 | selected |
| 271 | ? "border-blue-300/50 bg-blue-50 dark:border-blue-400/28 dark:bg-blue-400/14" | 279 | ? "border-adaptive-blue-300-a50-blue-400-a28 bg-adaptive-blue-50-blue-400-a14" |
| 272 | : "border-neutral-200 bg-white dark:border-white/6 dark:bg-neutral-950/70", | 280 | : "border-adaptive-neutral-200-white-a6 bg-adaptive-white-neutral-950-a70", |
| 273 | )} | 281 | )} |
| 274 | onPress={() => | 282 | onPress={() => |
| 275 | props.onSelectOption( | 283 | props.onSelectOption( |
| ... | @@ -284,14 +292,14 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { | ... | @@ -284,14 +292,14 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { |
| 284 | className={cn( | 292 | className={cn( |
| 285 | "font-t3-bold text-sm", | 293 | "font-t3-bold text-sm", |
| 286 | selected | 294 | selected |
| 287 | ? "text-sky-700 dark:text-sky-300" | 295 | ? "text-adaptive-sky-700-300" |
| 288 | : "text-neutral-700 dark:text-neutral-200", | 296 | : "text-adaptive-neutral-600-300", |
| 289 | )} | 297 | )} |
| 290 | > | 298 | > |
| 291 | {option.label} | 299 | {option.label} |
| 292 | </Text> | 300 | </Text> |
| 293 | {description ? ( | 301 | {description ? ( |
| 294 | <Text className="font-sans text-sm leading-5 text-neutral-500 dark:text-neutral-400"> | 302 | <Text className="font-sans text-sm leading-5 text-adaptive-neutral-500-400"> |
| 295 | {description} | 303 | {description} |
| 296 | </Text> | 304 | </Text> |
| 297 | ) : null} | 305 | ) : null} |
| ... | @@ -308,7 +316,7 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { | ... | @@ -308,7 +316,7 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { |
| 308 | onFocus={() => props.onInputFocusChange?.(true)} | 316 | onFocus={() => props.onInputFocusChange?.(true)} |
| 309 | onBlur={() => props.onInputFocusChange?.(false)} | 317 | onBlur={() => props.onInputFocusChange?.(false)} |
| 310 | placeholder="Or type a custom answer" | 318 | placeholder="Or type a custom answer" |
| 311 | className="min-h-[54px] rounded-2xl border border-neutral-200 bg-white px-3.5 py-3 font-sans text-base text-neutral-950 dark:border-white/8 dark:bg-neutral-950/70 dark:text-neutral-50" | 319 | className="min-h-[54px] rounded-2xl border border-adaptive-neutral-200-white-a8 bg-adaptive-white-neutral-950-a70 px-3.5 py-3 font-sans text-base text-adaptive-neutral-950-50" |
| 312 | /> | 320 | /> |
| 313 | </View> | 321 | </View> |
| 314 | ); | 322 | ); |
| ... | @@ -317,7 +325,7 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { | ... | @@ -317,7 +325,7 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { |
| 317 | <Pressable | 325 | <Pressable |
| 318 | className={cn( | 326 | className={cn( |
| 319 | "items-center justify-center rounded-2xl px-4 py-3.5", | 327 | "items-center justify-center rounded-2xl px-4 py-3.5", |
| 320 | props.answers ? "bg-blue-500" : "bg-neutral-200 dark:bg-neutral-700/60", | 328 | props.answers ? "bg-blue-500" : "bg-adaptive-neutral-200-700-a60", |
| 321 | )} | 329 | )} |
| 322 | disabled={ | 330 | disabled={ |
| 323 | props.answers === null || props.respondingUserInputId === props.pendingUserInput.requestId | 331 | props.answers === null || props.respondingUserInputId === props.pendingUserInput.requestId |
apps/mobile/src/features/threads/ThreadComposer.tsx+9-46| ... | @@ -16,15 +16,7 @@ import { | ... | @@ -16,15 +16,7 @@ import { |
| 16 | import { StackActions, useFocusEffect, useNavigation } from "@react-navigation/native"; | 16 | import { StackActions, useFocusEffect, useNavigation } from "@react-navigation/native"; |
| 17 | import type { ReactNode } from "react"; | 17 | import type { ReactNode } from "react"; |
| 18 | import { memo, useCallback, useEffect, useMemo, useRef, useState, type RefObject } from "react"; | 18 | import { memo, useCallback, useEffect, useMemo, useRef, useState, type RefObject } from "react"; |
| 19 | import { | 19 | import { ActivityIndicator, Image, Platform, Pressable, View, type ViewStyle } from "react-native"; |
| 20 | ActivityIndicator, | ||
| 21 | Image, | ||
| 22 | Platform, | ||
| 23 | Pressable, | ||
| 24 | StyleSheet, | ||
| 25 | View, | ||
| 26 | type ViewStyle, | ||
| 27 | } from "react-native"; | ||
| 28 | import ImageViewing from "react-native-image-viewing"; | 20 | import ImageViewing from "react-native-image-viewing"; |
| 29 | import Animated, { | 21 | import Animated, { |
| 30 | FadeIn, | 22 | FadeIn, |
| ... | @@ -33,8 +25,7 @@ import Animated, { | ... | @@ -33,8 +25,7 @@ import Animated, { |
| 33 | FadeOutDown, | 25 | FadeOutDown, |
| 34 | LinearTransition, | 26 | LinearTransition, |
| 35 | } from "react-native-reanimated"; | 27 | } from "react-native-reanimated"; |
| 36 | import { useThemeColor } from "../../lib/useThemeColor"; | 28 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 37 | import { themeColorWithAlpha } from "../../lib/mobileTheme"; | ||
| 38 | import { armAgentAwarenessLiveActivityForLocalWork } from "../agent-awareness/remoteRegistration"; | 29 | import { armAgentAwarenessLiveActivityForLocalWork } from "../agent-awareness/remoteRegistration"; |
| 39 | import { scopedThreadKey } from "../../lib/scopedEntities"; | 30 | import { scopedThreadKey } from "../../lib/scopedEntities"; |
| 40 | 31 | ||
| ... | @@ -57,7 +48,6 @@ import { ProviderIcon } from "../../components/ProviderIcon"; | ... | @@ -57,7 +48,6 @@ import { ProviderIcon } from "../../components/ProviderIcon"; |
| 57 | import type { DraftComposerImageAttachment } from "../../lib/composerImages"; | 48 | import type { DraftComposerImageAttachment } from "../../lib/composerImages"; |
| 58 | import { buildModelOptions, groupByProvider } from "../../lib/modelOptions"; | 49 | import { buildModelOptions, groupByProvider } from "../../lib/modelOptions"; |
| 59 | import { useScaledTextRole } from "../settings/appearance/useScaledTextRole"; | 50 | import { useScaledTextRole } from "../settings/appearance/useScaledTextRole"; |
| 60 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | ||
| 61 | import type { RemoteClientConnectionState } from "../../lib/connection"; | 51 | import type { RemoteClientConnectionState } from "../../lib/connection"; |
| 62 | import { | 52 | import { |
| 63 | insertRankedSearchResult, | 53 | insertRankedSearchResult, |
| ... | @@ -142,19 +132,14 @@ const COMPOSER_LAYOUT_TRANSITION = | ... | @@ -142,19 +132,14 @@ const COMPOSER_LAYOUT_TRANSITION = |
| 142 | export function ComposerSurface(props: { | 132 | export function ComposerSurface(props: { |
| 143 | readonly children: ReactNode; | 133 | readonly children: ReactNode; |
| 144 | readonly style: ViewStyle; | 134 | readonly style: ViewStyle; |
| 145 | readonly isDarkMode: boolean; | ||
| 146 | /** Existing thread composers morph between pill and card layouts. */ | 135 | /** Existing thread composers morph between pill and card layouts. */ |
| 147 | readonly animateLayout?: boolean; | 136 | readonly animateLayout?: boolean; |
| 148 | }) { | 137 | }) { |
| 149 | const cardColor = useThemeColor("--color-card-translucent"); | ||
| 150 | const borderColor = useThemeColor("--color-border"); | ||
| 151 | const shadowColor = useThemeColor("--color-primary-shadow"); | ||
| 152 | // Drop shadow lives on a wrapper: `overflow: "hidden"` on the surface itself | 138 | // Drop shadow lives on a wrapper: `overflow: "hidden"` on the surface itself |
| 153 | // (needed to clip content to the pill shape) would clip the shadow on iOS. | 139 | // (needed to clip content to the pill shape) would clip the shadow on iOS. |
| 154 | const shadowStyle: ViewStyle = { | 140 | const shadowStyle: ViewStyle = { |
| 155 | borderRadius: props.style.borderRadius, | 141 | borderRadius: props.style.borderRadius, |
| 156 | shadowColor, | 142 | shadowOpacity: 1, |
| 157 | shadowOpacity: props.isDarkMode ? 0.35 : 0.12, | ||
| 158 | shadowRadius: 14, | 143 | shadowRadius: 14, |
| 159 | shadowOffset: { width: 0, height: 6 }, | 144 | shadowOffset: { width: 0, height: 6 }, |
| 160 | elevation: 10, | 145 | elevation: 10, |
| ... | @@ -162,16 +147,13 @@ export function ComposerSurface(props: { | ... | @@ -162,16 +147,13 @@ export function ComposerSurface(props: { |
| 162 | 147 | ||
| 163 | return ( | 148 | return ( |
| 164 | <Animated.View | 149 | <Animated.View |
| 150 | className="shadow-adaptive-black-a15-a35" | ||
| 165 | layout={props.animateLayout === false ? undefined : COMPOSER_LAYOUT_TRANSITION} | 151 | layout={props.animateLayout === false ? undefined : COMPOSER_LAYOUT_TRANSITION} |
| 166 | style={shadowStyle} | 152 | style={shadowStyle} |
| 167 | > | 153 | > |
| 168 | <GlassSurface | 154 | <GlassSurface |
| 169 | chrome="none" | 155 | chrome="none" |
| 170 | fallbackStyle={{ | 156 | fallbackClassName="border border-border bg-card-translucent" |
| 171 | backgroundColor: cardColor, | ||
| 172 | borderWidth: 1, | ||
| 173 | borderColor, | ||
| 174 | }} | ||
| 175 | glassEffectStyle="regular" | 157 | glassEffectStyle="regular" |
| 176 | // The composer is a passive material containing interactive controls. | 158 | // The composer is a passive material containing interactive controls. |
| 177 | // Expo GlassView defaults to non-interactive and both layouts share it. | 159 | // Expo GlassView defaults to non-interactive and both layouts share it. |
| ... | @@ -240,8 +222,6 @@ const ComposerConnectionStatusPill = memo(function ComposerConnectionStatusPill( | ... | @@ -240,8 +222,6 @@ const ComposerConnectionStatusPill = memo(function ComposerConnectionStatusPill( |
| 240 | readonly status: ComposerStatusPillState; | 222 | readonly status: ComposerStatusPillState; |
| 241 | }) { | 223 | }) { |
| 242 | const isReconnecting = props.status.kind !== "unavailable"; | 224 | const isReconnecting = props.status.kind !== "unavailable"; |
| 243 | const indicatorColor = useThemeColor("--color-icon-muted"); | ||
| 244 | |||
| 245 | return ( | 225 | return ( |
| 246 | <Animated.View | 226 | <Animated.View |
| 247 | className="absolute inset-x-0 bottom-full items-center pb-2" | 227 | className="absolute inset-x-0 bottom-full items-center pb-2" |
| ... | @@ -255,7 +235,7 @@ const ComposerConnectionStatusPill = memo(function ComposerConnectionStatusPill( | ... | @@ -255,7 +235,7 @@ const ComposerConnectionStatusPill = memo(function ComposerConnectionStatusPill( |
| 255 | className="max-w-full flex-row items-center gap-2 rounded-full bg-card px-3 py-2 shadow-sm active:opacity-70" | 235 | className="max-w-full flex-row items-center gap-2 rounded-full bg-card px-3 py-2 shadow-sm active:opacity-70" |
| 256 | > | 236 | > |
| 257 | {isReconnecting ? ( | 237 | {isReconnecting ? ( |
| 258 | <ActivityIndicator size="small" color={indicatorColor} /> | 238 | <ActivityIndicator size="small" colorClassName={"accent-icon-muted"} /> |
| 259 | ) : ( | 239 | ) : ( |
| 260 | <View className="h-2 w-2 rounded-full bg-red-500" /> | 240 | <View className="h-2 w-2 rounded-full bg-red-500" /> |
| 261 | )} | 241 | )} |
| ... | @@ -272,9 +252,7 @@ const ComposerConnectionStatusPill = memo(function ComposerConnectionStatusPill( | ... | @@ -272,9 +252,7 @@ const ComposerConnectionStatusPill = memo(function ComposerConnectionStatusPill( |
| 272 | 252 | ||
| 273 | export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposerProps) { | 253 | export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposerProps) { |
| 274 | const navigation = useNavigation(); | 254 | const navigation = useNavigation(); |
| 275 | const { themeAppearance } = useAppearancePreferences(); | 255 | const foregroundColor = useUniwindTheme()["--color-foreground"]; |
| 276 | const isDarkMode = themeAppearance === "dark"; | ||
| 277 | const foregroundColor = useThemeColor("--color-foreground"); | ||
| 278 | const bodyText = useScaledTextRole("body"); | 256 | const bodyText = useScaledTextRole("body"); |
| 279 | const fallbackInputRef = useRef<ComposerEditorHandle>(null); | 257 | const fallbackInputRef = useRef<ComposerEditorHandle>(null); |
| 280 | const inputRef = props.editorRef ?? fallbackInputRef; | 258 | const inputRef = props.editorRef ?? fallbackInputRef; |
| ... | @@ -342,11 +320,6 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer | ... | @@ -342,11 +320,6 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer |
| 342 | environmentLabel: props.environmentLabel, | 320 | environmentLabel: props.environmentLabel, |
| 343 | threadSyncPhase: props.threadSyncPhase, | 321 | threadSyncPhase: props.threadSyncPhase, |
| 344 | }); | 322 | }); |
| 345 | const toolbarSurface = String(useThemeColor("--color-card")); | ||
| 346 | const backdropSurface = String(useThemeColor("--color-screen")); | ||
| 347 | const toolbarFadeOpaque = themeColorWithAlpha(toolbarSurface, 0.95); | ||
| 348 | const toolbarFadeTransparent = themeColorWithAlpha(toolbarSurface, 0); | ||
| 349 | const backdropGradient = `linear-gradient(to bottom, ${themeColorWithAlpha(backdropSurface, 0)} 0%, ${themeColorWithAlpha(backdropSurface, 0.6)} 55%, ${themeColorWithAlpha(backdropSurface, 0.9)} 100%)`; | ||
| 350 | const selectedProviderStatus = useMemo(() => { | 323 | const selectedProviderStatus = useMemo(() => { |
| 351 | if (!props.serverConfig) return null; | 324 | if (!props.serverConfig) return null; |
| 352 | return ( | 325 | return ( |
| ... | @@ -723,13 +696,8 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer | ... | @@ -723,13 +696,8 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer |
| 723 | silently drops experimental_backgroundImage on Android, which left this | 696 | silently drops experimental_backgroundImage on Android, which left this |
| 724 | strip fully transparent and the feed text legible through the composer. */} | 697 | strip fully transparent and the feed text legible through the composer. */} |
| 725 | <View | 698 | <View |
| 699 | className="absolute inset-0 bg-linear-to-b from-screen/0 via-screen/60 to-screen/90" | ||
| 726 | pointerEvents="none" | 700 | pointerEvents="none" |
| 727 | style={[ | ||
| 728 | StyleSheet.absoluteFill, | ||
| 729 | { | ||
| 730 | experimental_backgroundImage: backdropGradient, | ||
| 731 | }, | ||
| 732 | ]} | ||
| 733 | /> | 701 | /> |
| 734 | <Animated.View | 702 | <Animated.View |
| 735 | className="relative w-full self-center" | 703 | className="relative w-full self-center" |
| ... | @@ -755,7 +723,6 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer | ... | @@ -755,7 +723,6 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer |
| 755 | ) : null} | 723 | ) : null} |
| 756 | 724 | ||
| 757 | <ComposerSurface | 725 | <ComposerSurface |
| 758 | isDarkMode={isDarkMode} | ||
| 759 | style={ | 726 | style={ |
| 760 | isExpanded | 727 | isExpanded |
| 761 | ? { | 728 | ? { |
| ... | @@ -865,11 +832,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer | ... | @@ -865,11 +832,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer |
| 865 | ) : null} | 832 | ) : null} |
| 866 | {isExpanded ? ( | 833 | {isExpanded ? ( |
| 867 | <ComposerToolbarRow paddingBottom={0} paddingHorizontal={0} paddingTop={4}> | 834 | <ComposerToolbarRow paddingBottom={0} paddingHorizontal={0} paddingTop={4}> |
| 868 | <ComposerToolbarScroller | 835 | <ComposerToolbarScroller contentPaddingRight={8}> |
| 869 | fadeOpaque={toolbarFadeOpaque} | ||
| 870 | fadeTransparent={toolbarFadeTransparent} | ||
| 871 | contentPaddingRight={8} | ||
| 872 | > | ||
| 873 | <ComposerToolbarButton | 836 | <ComposerToolbarButton |
| 874 | accessibilityLabel="Add attachment" | 837 | accessibilityLabel="Add attachment" |
| 875 | icon="plus" | 838 | icon="plus" |
apps/mobile/src/features/threads/ThreadFeed.tsx+46-56| ... | @@ -46,7 +46,7 @@ import { TouchableOpacity } from "react-native-gesture-handler"; | ... | @@ -46,7 +46,7 @@ import { TouchableOpacity } from "react-native-gesture-handler"; |
| 46 | import ImageViewing from "react-native-image-viewing"; | 46 | import ImageViewing from "react-native-image-viewing"; |
| 47 | import { useSafeAreaInsets } from "react-native-safe-area-context"; | 47 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 48 | import Animated, { FadeIn, FadeInUp, type SharedValue } from "react-native-reanimated"; | 48 | import Animated, { FadeIn, FadeInUp, type SharedValue } from "react-native-reanimated"; |
| 49 | import { useThemeColor } from "../../lib/useThemeColor"; | 49 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 50 | import { IOS_NAV_BAR_HEIGHT } from "../../lib/layoutMetrics"; | 50 | import { IOS_NAV_BAR_HEIGHT } from "../../lib/layoutMetrics"; |
| 51 | import { useFontFamily } from "../../lib/useFontFamily"; | 51 | import { useFontFamily } from "../../lib/useFontFamily"; |
| 52 | import { scopedThreadKey } from "../../lib/scopedEntities"; | 52 | import { scopedThreadKey } from "../../lib/scopedEntities"; |
| ... | @@ -209,7 +209,6 @@ function ThreadMarkdownImageView(props: { | ... | @@ -209,7 +209,6 @@ function ThreadMarkdownImageView(props: { |
| 209 | readonly alt: string | null; | 209 | readonly alt: string | null; |
| 210 | readonly onPressImage: (uri: string) => void; | 210 | readonly onPressImage: (uri: string) => void; |
| 211 | }) { | 211 | }) { |
| 212 | const codeBackground = useThemeColor("--color-md-code-bg"); | ||
| 213 | const [availableWidth, setAvailableWidth] = useState(0); | 212 | const [availableWidth, setAvailableWidth] = useState(0); |
| 214 | const [sourceSize, setSourceSize] = useState<{ width: number; height: number } | null>(null); | 213 | const [sourceSize, setSourceSize] = useState<{ width: number; height: number } | null>(null); |
| 215 | const [failedUri, setFailedUri] = useState<string | null>(null); | 214 | const [failedUri, setFailedUri] = useState<string | null>(null); |
| ... | @@ -242,12 +241,9 @@ function ThreadMarkdownImageView(props: { | ... | @@ -242,12 +241,9 @@ function ThreadMarkdownImageView(props: { |
| 242 | > | 241 | > |
| 243 | {props.uri === null || failed ? ( | 242 | {props.uri === null || failed ? ( |
| 244 | <View | 243 | <View |
| 244 | className="items-center justify-center rounded-[10px] bg-md-code-bg" | ||
| 245 | style={{ | 245 | style={{ |
| 246 | ...frameStyle, | 246 | ...frameStyle, |
| 247 | borderRadius: 10, | ||
| 248 | backgroundColor: codeBackground, | ||
| 249 | alignItems: "center", | ||
| 250 | justifyContent: "center", | ||
| 251 | }} | 247 | }} |
| 252 | > | 248 | > |
| 253 | {failed ? ( | 249 | {failed ? ( |
| ... | @@ -265,13 +261,9 @@ function ThreadMarkdownImageView(props: { | ... | @@ -265,13 +261,9 @@ function ThreadMarkdownImageView(props: { |
| 265 | style={{ alignSelf: "flex-start" }} | 261 | style={{ alignSelf: "flex-start" }} |
| 266 | > | 262 | > |
| 267 | <View | 263 | <View |
| 264 | className="items-center justify-center overflow-hidden rounded-[10px] bg-md-code-bg" | ||
| 268 | style={{ | 265 | style={{ |
| 269 | ...frameStyle, | 266 | ...frameStyle, |
| 270 | borderRadius: 10, | ||
| 271 | backgroundColor: codeBackground, | ||
| 272 | alignItems: "center", | ||
| 273 | justifyContent: "center", | ||
| 274 | overflow: "hidden", | ||
| 275 | }} | 267 | }} |
| 276 | > | 268 | > |
| 277 | <ThreadMarkdownImageRequest | 269 | <ThreadMarkdownImageRequest |
| ... | @@ -556,23 +548,18 @@ function MarkdownCodeBlock(props: { | ... | @@ -556,23 +548,18 @@ function MarkdownCodeBlock(props: { |
| 556 | } | 548 | } |
| 557 | 549 | ||
| 558 | function useReviewCommentColors(): ReviewCommentColors { | 550 | function useReviewCommentColors(): ReviewCommentColors { |
| 559 | const background = useThemeColor("--color-card"); | 551 | const theme = useUniwindTheme(); |
| 560 | const border = useThemeColor("--color-border"); | ||
| 561 | const mutedBackground = useThemeColor("--color-subtle"); | ||
| 562 | const text = useThemeColor("--color-foreground"); | ||
| 563 | const mutedText = useThemeColor("--color-foreground-muted"); | ||
| 564 | const codeBackground = useThemeColor("--color-md-code-bg"); | ||
| 565 | 552 | ||
| 566 | return useMemo( | 553 | return useMemo( |
| 567 | () => ({ | 554 | () => ({ |
| 568 | background, | 555 | background: theme["--color-card"], |
| 569 | border, | 556 | border: theme["--color-border"], |
| 570 | mutedBackground, | 557 | mutedBackground: theme["--color-subtle"], |
| 571 | text, | 558 | text: theme["--color-foreground"], |
| 572 | mutedText, | 559 | mutedText: theme["--color-foreground-muted"], |
| 573 | codeBackground, | 560 | codeBackground: theme["--color-md-code-bg"], |
| 574 | }), | 561 | }), |
| 575 | [background, border, codeBackground, mutedBackground, mutedText, text], | 562 | [theme], |
| 576 | ); | 563 | ); |
| 577 | } | 564 | } |
| 578 | 565 | ||
| ... | @@ -590,25 +577,26 @@ function useMarkdownStyles( | ... | @@ -590,25 +577,26 @@ function useMarkdownStyles( |
| 590 | [appearance.baseFontSize], | 577 | [appearance.baseFontSize], |
| 591 | ); | 578 | ); |
| 592 | const themeMode = themeAppearance; | 579 | const themeMode = themeAppearance; |
| 593 | const markdownBodyColor = String(useThemeColor("--color-md-body")); | 580 | const theme = useUniwindTheme(); |
| 594 | const markdownStrongColor = String(useThemeColor("--color-md-strong")); | 581 | const markdownBodyColor = theme["--color-md-body"]; |
| 595 | const markdownLinkColor = String(useThemeColor("--color-md-link")); | 582 | const markdownStrongColor = theme["--color-md-strong"]; |
| 596 | const markdownBlockquoteBg = String(useThemeColor("--color-md-blockquote-bg")); | 583 | const markdownLinkColor = theme["--color-md-link"]; |
| 597 | const markdownBlockquoteBorder = String(useThemeColor("--color-md-blockquote-border")); | 584 | const markdownBlockquoteBg = theme["--color-md-blockquote-bg"]; |
| 598 | const markdownCodeBg = String(useThemeColor("--color-md-code-bg")); | 585 | const markdownBlockquoteBorder = theme["--color-md-blockquote-border"]; |
| 599 | const markdownCodeText = String(useThemeColor("--color-md-code-text")); | 586 | const markdownCodeBg = theme["--color-md-code-bg"]; |
| 600 | const markdownInlineCodeText = String(useThemeColor("--color-foreground-secondary")); | 587 | const markdownCodeText = theme["--color-md-code-text"]; |
| 601 | const markdownHrColor = String(useThemeColor("--color-md-hr")); | 588 | const markdownInlineCodeText = theme["--color-foreground-secondary"]; |
| 602 | const markdownUserBodyColor = String(useThemeColor("--color-user-bubble-foreground")); | 589 | const markdownHrColor = theme["--color-md-hr"]; |
| 603 | const markdownUserCodeBg = String(useThemeColor("--color-md-user-code-bg")); | 590 | const markdownUserBodyColor = theme["--color-user-bubble-foreground"]; |
| 604 | const markdownUserCodeText = String(useThemeColor("--color-md-user-code-text")); | 591 | const markdownUserCodeBg = theme["--color-md-user-code-bg"]; |
| 605 | const markdownUserInlineCodeText = String(useThemeColor("--color-user-bubble-foreground-muted")); | 592 | const markdownUserCodeText = theme["--color-md-user-code-text"]; |
| 606 | const markdownUserFenceBg = String(useThemeColor("--color-md-user-fence-bg")); | 593 | const markdownUserInlineCodeText = theme["--color-user-bubble-foreground-muted"]; |
| 607 | const markdownUserFenceText = String(useThemeColor("--color-md-user-fence-text")); | 594 | const markdownUserFenceBg = theme["--color-md-user-fence-bg"]; |
| 608 | const iconSubtleColor = String(useThemeColor("--color-icon-subtle")); | 595 | const markdownUserFenceText = theme["--color-md-user-fence-text"]; |
| 609 | const inlineSkillForeground = String(useThemeColor("--color-inline-skill-foreground")); | 596 | const iconSubtleColor = theme["--color-icon-subtle"]; |
| 610 | const userBubbleSkillForeground = String(useThemeColor("--color-user-bubble-skill-foreground")); | 597 | const inlineSkillForeground = theme["--color-inline-skill-foreground"]; |
| 611 | const userBubbleForegroundMuted = String(useThemeColor("--color-user-bubble-foreground-muted")); | 598 | const userBubbleSkillForeground = theme["--color-user-bubble-skill-foreground"]; |
| 599 | const userBubbleForegroundMuted = theme["--color-user-bubble-foreground-muted"]; | ||
| 612 | const regularFontFamily = useFontFamily("regular"); | 600 | const regularFontFamily = useFontFamily("regular"); |
| 613 | const boldFontFamily = useFontFamily("bold"); | 601 | const boldFontFamily = useFontFamily("bold"); |
| 614 | 602 | ||
| ... | @@ -1008,7 +996,7 @@ function renderFeedEntry( | ... | @@ -1008,7 +996,7 @@ function renderFeedEntry( |
| 1008 | accessibilityState={{ expanded: entry.expanded }} | 996 | accessibilityState={{ expanded: entry.expanded }} |
| 1009 | onPress={() => props.onToggleTurnFold(entry.turnId)} | 997 | onPress={() => props.onToggleTurnFold(entry.turnId)} |
| 1010 | hitSlop={4} | 998 | hitSlop={4} |
| 1011 | className="mb-3 min-h-11 flex-row items-center gap-2 border-b border-neutral-200/80 px-2 dark:border-white/[0.08]" | 999 | className="mb-3 min-h-11 flex-row items-center gap-2 border-b border-adaptive-neutral-200-a80-white-a8 px-2" |
| 1012 | > | 1000 | > |
| 1013 | <Text className="font-t3-medium text-sm tabular-nums text-foreground-muted"> | 1001 | <Text className="font-t3-medium text-sm tabular-nums text-foreground-muted"> |
| 1014 | {entry.label} | 1002 | {entry.label} |
| ... | @@ -1016,7 +1004,7 @@ function renderFeedEntry( | ... | @@ -1016,7 +1004,7 @@ function renderFeedEntry( |
| 1016 | <SymbolView | 1004 | <SymbolView |
| 1017 | name={entry.expanded ? "chevron.down" : "chevron.right"} | 1005 | name={entry.expanded ? "chevron.down" : "chevron.right"} |
| 1018 | size={15} | 1006 | size={15} |
| 1019 | tintColor={iconSubtleColor} | 1007 | tintColorClassName={"accent-icon-subtle"} |
| 1020 | type="monochrome" | 1008 | type="monochrome" |
| 1021 | /> | 1009 | /> |
| 1022 | </Pressable> | 1010 | </Pressable> |
| ... | @@ -1102,7 +1090,7 @@ function renderFeedEntry( | ... | @@ -1102,7 +1090,7 @@ function renderFeedEntry( |
| 1102 | })} | 1090 | })} |
| 1103 | </View> | 1091 | </View> |
| 1104 | <View className="mt-1 flex-row items-center justify-end gap-1 pr-0.5"> | 1092 | <View className="mt-1 flex-row items-center justify-end gap-1 pr-0.5"> |
| 1105 | <Text className="font-t3-medium text-xs tabular-nums text-neutral-600 dark:text-neutral-400"> | 1093 | <Text className="font-t3-medium text-xs tabular-nums text-adaptive-neutral-600-400"> |
| 1106 | {timestampLabel} | 1094 | {timestampLabel} |
| 1107 | </Text> | 1095 | </Text> |
| 1108 | {message.text.trim().length > 0 ? ( | 1096 | {message.text.trim().length > 0 ? ( |
| ... | @@ -1157,7 +1145,7 @@ function renderFeedEntry( | ... | @@ -1157,7 +1145,7 @@ function renderFeedEntry( |
| 1157 | key={attachment.id} | 1145 | key={attachment.id} |
| 1158 | environmentId={props.environmentId} | 1146 | environmentId={props.environmentId} |
| 1159 | attachmentId={attachment.id} | 1147 | attachmentId={attachment.id} |
| 1160 | className="mt-1.5 aspect-[1.3] w-full rounded-[18px] bg-neutral-200 dark:bg-neutral-800" | 1148 | className="mt-1.5 aspect-[1.3] w-full rounded-[18px] bg-adaptive-neutral-200-800" |
| 1161 | onPressImage={props.onPressImage} | 1149 | onPressImage={props.onPressImage} |
| 1162 | /> | 1150 | /> |
| 1163 | ); | 1151 | ); |
| ... | @@ -1171,7 +1159,7 @@ function renderFeedEntry( | ... | @@ -1171,7 +1159,7 @@ function renderFeedEntry( |
| 1171 | buttonSize={28} | 1159 | buttonSize={28} |
| 1172 | iconSize={13} | 1160 | iconSize={13} |
| 1173 | /> | 1161 | /> |
| 1174 | <Text className="font-t3-medium text-xs tabular-nums text-neutral-600 dark:text-neutral-400"> | 1162 | <Text className="font-t3-medium text-xs tabular-nums text-adaptive-neutral-600-400"> |
| 1175 | {timestampLabel} | 1163 | {timestampLabel} |
| 1176 | </Text> | 1164 | </Text> |
| 1177 | </View> | 1165 | </View> |
| ... | @@ -1207,11 +1195,11 @@ const WorkingTimelineRow = memo(function WorkingTimelineRow(props: { readonly st | ... | @@ -1207,11 +1195,11 @@ const WorkingTimelineRow = memo(function WorkingTimelineRow(props: { readonly st |
| 1207 | return ( | 1195 | return ( |
| 1208 | <View className="mb-4 flex-row items-center gap-2 px-1.5 py-1"> | 1196 | <View className="mb-4 flex-row items-center gap-2 px-1.5 py-1"> |
| 1209 | <View className="flex-row items-center gap-1"> | 1197 | <View className="flex-row items-center gap-1"> |
| 1210 | <View className="h-1 w-1 rounded-full bg-neutral-400 dark:bg-neutral-500" /> | 1198 | <View className="h-1 w-1 rounded-full bg-adaptive-neutral-400-500" /> |
| 1211 | <View className="h-1 w-1 rounded-full bg-neutral-400/80 dark:bg-neutral-500/80" /> | 1199 | <View className="h-1 w-1 rounded-full bg-adaptive-neutral-400-a80-500-a80" /> |
| 1212 | <View className="h-1 w-1 rounded-full bg-neutral-400/60 dark:bg-neutral-500/60" /> | 1200 | <View className="h-1 w-1 rounded-full bg-adaptive-neutral-400-a60-500-a60" /> |
| 1213 | </View> | 1201 | </View> |
| 1214 | <Text className="font-t3-medium text-xs tabular-nums text-neutral-600 dark:text-neutral-400"> | 1202 | <Text className="font-t3-medium text-xs tabular-nums text-adaptive-neutral-600-400"> |
| 1215 | Working for {durationLabel} | 1203 | Working for {durationLabel} |
| 1216 | </Text> | 1204 | </Text> |
| 1217 | </View> | 1205 | </View> |
| ... | @@ -1303,6 +1291,7 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: { | ... | @@ -1303,6 +1291,7 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: { |
| 1303 | }) { | 1291 | }) { |
| 1304 | const { codeSurface, nativeReviewDiffStyle } = useAppearanceCodeSurface(); | 1292 | const { codeSurface, nativeReviewDiffStyle } = useAppearanceCodeSurface(); |
| 1305 | const { themeAppearance: appearanceScheme, themeId } = useAppearancePreferences(); | 1293 | const { themeAppearance: appearanceScheme, themeId } = useAppearancePreferences(); |
| 1294 | const appTheme = useUniwindTheme(); | ||
| 1306 | const NativeReviewDiffView = resolveNativeReviewDiffView(); | 1295 | const NativeReviewDiffView = resolveNativeReviewDiffView(); |
| 1307 | const patch = useMemo(() => buildReviewCommentPatch(props.comment), [props.comment]); | 1296 | const patch = useMemo(() => buildReviewCommentPatch(props.comment), [props.comment]); |
| 1308 | const parsedDiff = useMemo( | 1297 | const parsedDiff = useMemo( |
| ... | @@ -1315,8 +1304,8 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: { | ... | @@ -1315,8 +1304,8 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: { |
| 1315 | [nativeReviewDiffData.rows], | 1304 | [nativeReviewDiffData.rows], |
| 1316 | ); | 1305 | ); |
| 1317 | const nativeReviewDiffTheme = useMemo( | 1306 | const nativeReviewDiffTheme = useMemo( |
| 1318 | () => createNativeReviewDiffTheme(appearanceScheme, themeId), | 1307 | () => createNativeReviewDiffTheme(appearanceScheme, themeId, appTheme), |
| 1319 | [appearanceScheme, themeId], | 1308 | [appearanceScheme, appTheme, themeId], |
| 1320 | ); | 1309 | ); |
| 1321 | const nativeRowsJson = useMemo(() => JSON.stringify(compactNativeRows), [compactNativeRows]); | 1310 | const nativeRowsJson = useMemo(() => JSON.stringify(compactNativeRows), [compactNativeRows]); |
| 1322 | const nativeThemeJson = useMemo( | 1311 | const nativeThemeJson = useMemo( |
| ... | @@ -1578,8 +1567,9 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) { | ... | @@ -1578,8 +1567,9 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) { |
| 1578 | ? navigationHeaderHeight || insets.top + IOS_NAV_BAR_HEIGHT | 1567 | ? navigationHeaderHeight || insets.top + IOS_NAV_BAR_HEIGHT |
| 1579 | : topContentInset; | 1568 | : topContentInset; |
| 1580 | 1569 | ||
| 1581 | const iconSubtleColor = useThemeColor("--color-icon-subtle"); | 1570 | const theme = useUniwindTheme(); |
| 1582 | const userBubbleColor = useThemeColor("--color-user-bubble"); | 1571 | const iconSubtleColor = theme["--color-icon-subtle"]; |
| 1572 | const userBubbleColor = theme["--color-user-bubble"]; | ||
| 1583 | const onMarkdownLinkPress = useCallback( | 1573 | const onMarkdownLinkPress = useCallback( |
| 1584 | (href: string) => { | 1574 | (href: string) => { |
| 1585 | const presentation = resolveMarkdownLinkPresentation(href); | 1575 | const presentation = resolveMarkdownLinkPresentation(href); |
apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx+13-29| ... | @@ -27,7 +27,6 @@ import { SymbolView } from "../../components/AppSymbol"; | ... | @@ -27,7 +27,6 @@ import { SymbolView } from "../../components/AppSymbol"; |
| 27 | import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass"; | 27 | import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass"; |
| 28 | import { NativeStackScreenOptions } from "../../native/StackHeader"; | 28 | import { NativeStackScreenOptions } from "../../native/StackHeader"; |
| 29 | import { scopedProjectKey, scopedThreadKey } from "../../lib/scopedEntities"; | 29 | import { scopedProjectKey, scopedThreadKey } from "../../lib/scopedEntities"; |
| 30 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 31 | import { useProjects, useThreadShells } from "../../state/entities"; | 30 | import { useProjects, useThreadShells } from "../../state/entities"; |
| 32 | import { mobilePreferencesAtom } from "../../state/preferences"; | 31 | import { mobilePreferencesAtom } from "../../state/preferences"; |
| 33 | import { useThreadSearch } from "../../state/queries"; | 32 | import { useThreadSearch } from "../../state/queries"; |
| ... | @@ -127,19 +126,11 @@ export function ThreadNavigationSidebar(props: ThreadNavigationSidebarProps) { | ... | @@ -127,19 +126,11 @@ export function ThreadNavigationSidebar(props: ThreadNavigationSidebarProps) { |
| 127 | } | 126 | } |
| 128 | 127 | ||
| 129 | function NativeSidebarContainer(props: ThreadNavigationSidebarProps) { | 128 | function NativeSidebarContainer(props: ThreadNavigationSidebarProps) { |
| 130 | const backgroundColor = useThemeColor("--color-drawer"); | ||
| 131 | const borderColor = useThemeColor("--color-border"); | ||
| 132 | |||
| 133 | return ( | 129 | return ( |
| 134 | <View | 130 | <View |
| 135 | testID="thread-navigation-sidebar" | 131 | testID="thread-navigation-sidebar" |
| 136 | className="flex-1" | 132 | className="flex-1 border-border bg-drawer" |
| 137 | style={{ | 133 | style={{ borderRightWidth: StyleSheet.hairlineWidth, width: props.width }} |
| 138 | width: props.width, | ||
| 139 | backgroundColor, | ||
| 140 | borderRightColor: borderColor, | ||
| 141 | borderRightWidth: StyleSheet.hairlineWidth, | ||
| 142 | }} | ||
| 143 | > | 134 | > |
| 144 | <SidebarNavigationShell> | 135 | <SidebarNavigationShell> |
| 145 | <ThreadNavigationSidebarPane {...props} nativeChrome /> | 136 | <ThreadNavigationSidebarPane {...props} nativeChrome /> |
| ... | @@ -731,10 +722,6 @@ function ThreadNavigationSidebarPane( | ... | @@ -731,10 +722,6 @@ function ThreadNavigationSidebarPane( |
| 731 | ], | 722 | ], |
| 732 | ); | 723 | ); |
| 733 | 724 | ||
| 734 | const backgroundColor = useThemeColor("--color-drawer"); | ||
| 735 | const borderColor = useThemeColor("--color-border"); | ||
| 736 | const mutedColor = useThemeColor("--color-foreground-muted"); | ||
| 737 | const placeholderColor = useThemeColor("--color-placeholder"); | ||
| 738 | const [measuredHeaderHeight, setMeasuredHeaderHeight] = useState<number | null>(null); | 725 | const [measuredHeaderHeight, setMeasuredHeaderHeight] = useState<number | null>(null); |
| 739 | // The sticky header (title row, search field, optional connection status) | 726 | // The sticky header (title row, search field, optional connection status) |
| 740 | // is measured so the list inset always matches its real height — no | 727 | // is measured so the list inset always matches its real height — no |
| ... | @@ -1242,13 +1229,8 @@ function ThreadNavigationSidebarPane( | ... | @@ -1242,13 +1229,8 @@ function ThreadNavigationSidebarPane( |
| 1242 | return ( | 1229 | return ( |
| 1243 | <View | 1230 | <View |
| 1244 | testID="thread-navigation-sidebar" | 1231 | testID="thread-navigation-sidebar" |
| 1245 | className="flex-1" | 1232 | className="flex-1 border-r border-border bg-drawer" |
| 1246 | style={{ | 1233 | style={{ width: props.width }} |
| 1247 | width: props.width, | ||
| 1248 | backgroundColor, | ||
| 1249 | borderRightColor: borderColor, | ||
| 1250 | borderRightWidth: StyleSheet.hairlineWidth, | ||
| 1251 | }} | ||
| 1252 | > | 1234 | > |
| 1253 | <View className="flex-1" style={{ paddingBottom: insets.bottom }}> | 1235 | <View className="flex-1" style={{ paddingBottom: insets.bottom }}> |
| 1254 | <SwipeableScrollGateProvider enabled={swipeEnabled}> | 1236 | <SwipeableScrollGateProvider enabled={swipeEnabled}> |
| ... | @@ -1286,14 +1268,11 @@ function ThreadNavigationSidebarPane( | ... | @@ -1286,14 +1268,11 @@ function ThreadNavigationSidebarPane( |
| 1286 | </View> | 1268 | </View> |
| 1287 | 1269 | ||
| 1288 | <View | 1270 | <View |
| 1289 | className="absolute inset-x-0 top-0 z-[4]" | 1271 | className="absolute inset-x-0 top-0 z-[4] bg-drawer" |
| 1290 | collapsable={false} | 1272 | collapsable={false} |
| 1291 | onLayout={handleStickyHeaderLayout} | 1273 | onLayout={handleStickyHeaderLayout} |
| 1292 | pointerEvents="auto" | 1274 | pointerEvents="auto" |
| 1293 | style={{ | 1275 | style={{ paddingTop: insets.top }} |
| 1294 | paddingTop: insets.top, | ||
| 1295 | backgroundColor, | ||
| 1296 | }} | ||
| 1297 | > | 1276 | > |
| 1298 | <View className="h-[50px] flex-row items-end gap-0.5 pr-2 pl-5"> | 1277 | <View className="h-[50px] flex-row items-end gap-0.5 pr-2 pl-5"> |
| 1299 | {/* Title slot doubles as the connection status surface: while an | 1278 | {/* Title slot doubles as the connection status surface: while an |
| ... | @@ -1318,7 +1297,12 @@ function ThreadNavigationSidebarPane( | ... | @@ -1318,7 +1297,12 @@ function ThreadNavigationSidebarPane( |
| 1318 | </View> | 1297 | </View> |
| 1319 | 1298 | ||
| 1320 | <View className="mx-4 mt-[9px] h-[38px] flex-row items-center gap-1.5 rounded-xl bg-sidebar-search pr-2.5 pl-[11px]"> | 1299 | <View className="mx-4 mt-[9px] h-[38px] flex-row items-center gap-1.5 rounded-xl bg-sidebar-search pr-2.5 pl-[11px]"> |
| 1321 | <SymbolView name="magnifyingglass" size={15} tintColor={mutedColor} type="monochrome" /> | 1300 | <SymbolView |
| 1301 | name="magnifyingglass" | ||
| 1302 | size={15} | ||
| 1303 | tintColorClassName={"accent-foreground-muted"} | ||
| 1304 | type="monochrome" | ||
| 1305 | /> | ||
| 1322 | <TextInput | 1306 | <TextInput |
| 1323 | ref={searchInputRef} | 1307 | ref={searchInputRef} |
| 1324 | accessibilityLabel="Search threads" | 1308 | accessibilityLabel="Search threads" |
| ... | @@ -1327,7 +1311,7 @@ function ThreadNavigationSidebarPane( | ... | @@ -1327,7 +1311,7 @@ function ThreadNavigationSidebarPane( |
| 1327 | clearButtonMode="while-editing" | 1311 | clearButtonMode="while-editing" |
| 1328 | onChangeText={props.onSearchQueryChange} | 1312 | onChangeText={props.onSearchQueryChange} |
| 1329 | placeholder="Search" | 1313 | placeholder="Search" |
| 1330 | placeholderTextColor={placeholderColor} | 1314 | placeholderTextColorClassName={"accent-placeholder"} |
| 1331 | returnKeyType="search" | 1315 | returnKeyType="search" |
| 1332 | className="h-[34px] flex-1 px-0 py-0 font-sans text-base text-foreground" | 1316 | className="h-[34px] flex-1 px-0 py-0 font-sans text-base text-foreground" |
| 1333 | value={props.searchQuery} | 1317 | value={props.searchQuery} |
apps/mobile/src/features/threads/ThreadSettingsSheet.tsx+13-11| ... | @@ -40,7 +40,7 @@ import { cn } from "../../lib/cn"; | ... | @@ -40,7 +40,7 @@ import { cn } from "../../lib/cn"; |
| 40 | import type { ModelOption, ProviderGroup } from "../../lib/modelOptions"; | 40 | import type { ModelOption, ProviderGroup } from "../../lib/modelOptions"; |
| 41 | import { applyProviderOptionSelection } from "../../lib/providerOptions"; | 41 | import { applyProviderOptionSelection } from "../../lib/providerOptions"; |
| 42 | import { resolveProviderOptionDescriptors } from "../../lib/providerOptions"; | 42 | import { resolveProviderOptionDescriptors } from "../../lib/providerOptions"; |
| 43 | import { useThemeColor } from "../../lib/useThemeColor"; | 43 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 44 | import { | 44 | import { |
| 45 | NativeHeaderToolbar, | 45 | NativeHeaderToolbar, |
| 46 | NativeStackScreenOptions, | 46 | NativeStackScreenOptions, |
| ... | @@ -91,7 +91,6 @@ function ModelRow(props: { | ... | @@ -91,7 +91,6 @@ function ModelRow(props: { |
| 91 | readonly isFirst: boolean; | 91 | readonly isFirst: boolean; |
| 92 | readonly isLast: boolean; | 92 | readonly isLast: boolean; |
| 93 | }) { | 93 | }) { |
| 94 | const checkmarkColor = useThemeColor("--color-icon"); | ||
| 95 | return ( | 94 | return ( |
| 96 | <Pressable | 95 | <Pressable |
| 97 | accessibilityLabel={props.option.label} | 96 | accessibilityLabel={props.option.label} |
| ... | @@ -122,7 +121,7 @@ function ModelRow(props: { | ... | @@ -122,7 +121,7 @@ function ModelRow(props: { |
| 122 | <SymbolView | 121 | <SymbolView |
| 123 | name="checkmark" | 122 | name="checkmark" |
| 124 | size={16} | 123 | size={16} |
| 125 | tintColor={checkmarkColor} | 124 | tintColorClassName={"accent-icon"} |
| 126 | type="monochrome" | 125 | type="monochrome" |
| 127 | weight="semibold" | 126 | weight="semibold" |
| 128 | /> | 127 | /> |
| ... | @@ -140,7 +139,6 @@ function ProviderHeader(props: { | ... | @@ -140,7 +139,6 @@ function ProviderHeader(props: { |
| 140 | readonly modelCount: number; | 139 | readonly modelCount: number; |
| 141 | readonly onToggle: () => void; | 140 | readonly onToggle: () => void; |
| 142 | }) { | 141 | }) { |
| 143 | const iconSubtle = useThemeColor("--color-icon-subtle"); | ||
| 144 | const content = ( | 142 | const content = ( |
| 145 | <> | 143 | <> |
| 146 | <ProviderIcon provider={props.driver} size={15} /> | 144 | <ProviderIcon provider={props.driver} size={15} /> |
| ... | @@ -156,7 +154,7 @@ function ProviderHeader(props: { | ... | @@ -156,7 +154,7 @@ function ProviderHeader(props: { |
| 156 | <SymbolView | 154 | <SymbolView |
| 157 | name={props.collapsed ? "chevron.down" : "chevron.up"} | 155 | name={props.collapsed ? "chevron.down" : "chevron.up"} |
| 158 | size={12} | 156 | size={12} |
| 159 | tintColor={iconSubtle} | 157 | tintColorClassName={"accent-icon-subtle"} |
| 160 | type="monochrome" | 158 | type="monochrome" |
| 161 | /> | 159 | /> |
| 162 | </> | 160 | </> |
| ... | @@ -192,7 +190,6 @@ function DisclosureRow(props: { | ... | @@ -192,7 +190,6 @@ function DisclosureRow(props: { |
| 192 | readonly onPress: () => void; | 190 | readonly onPress: () => void; |
| 193 | readonly isLast?: boolean; | 191 | readonly isLast?: boolean; |
| 194 | }) { | 192 | }) { |
| 195 | const iconSubtle = useThemeColor("--color-icon-subtle"); | ||
| 196 | return ( | 193 | return ( |
| 197 | <Pressable | 194 | <Pressable |
| 198 | accessibilityRole="button" | 195 | accessibilityRole="button" |
| ... | @@ -209,7 +206,12 @@ function DisclosureRow(props: { | ... | @@ -209,7 +206,12 @@ function DisclosureRow(props: { |
| 209 | {props.value} | 206 | {props.value} |
| 210 | </Text> | 207 | </Text> |
| 211 | ) : null} | 208 | ) : null} |
| 212 | <SymbolView name="chevron.right" size={12} tintColor={iconSubtle} type="monochrome" /> | 209 | <SymbolView |
| 210 | name="chevron.right" | ||
| 211 | size={12} | ||
| 212 | tintColorClassName={"accent-icon-subtle"} | ||
| 213 | type="monochrome" | ||
| 214 | /> | ||
| 213 | </Pressable> | 215 | </Pressable> |
| 214 | ); | 216 | ); |
| 215 | } | 217 | } |
| ... | @@ -222,7 +224,6 @@ function ChoiceRow(props: { | ... | @@ -222,7 +224,6 @@ function ChoiceRow(props: { |
| 222 | readonly onPress: () => void; | 224 | readonly onPress: () => void; |
| 223 | readonly isLast: boolean; | 225 | readonly isLast: boolean; |
| 224 | }) { | 226 | }) { |
| 225 | const checkmarkColor = useThemeColor("--color-icon"); | ||
| 226 | return ( | 227 | return ( |
| 227 | <Pressable | 228 | <Pressable |
| 228 | accessibilityLabel={props.description ? `${props.label}. ${props.description}` : props.label} | 229 | accessibilityLabel={props.description ? `${props.label}. ${props.description}` : props.label} |
| ... | @@ -244,7 +245,7 @@ function ChoiceRow(props: { | ... | @@ -244,7 +245,7 @@ function ChoiceRow(props: { |
| 244 | <SymbolView | 245 | <SymbolView |
| 245 | name="checkmark" | 246 | name="checkmark" |
| 246 | size={16} | 247 | size={16} |
| 247 | tintColor={checkmarkColor} | 248 | tintColorClassName={"accent-icon"} |
| 248 | type="monochrome" | 249 | type="monochrome" |
| 249 | weight="semibold" | 250 | weight="semibold" |
| 250 | /> | 251 | /> |
| ... | @@ -1125,8 +1126,9 @@ function ThreadSettingsChoiceScreen() { | ... | @@ -1125,8 +1126,9 @@ function ThreadSettingsChoiceScreen() { |
| 1125 | } | 1126 | } |
| 1126 | 1127 | ||
| 1127 | function ThreadSettingsPickerNavigator(props: ThreadSettingsPickerPresentation) { | 1128 | function ThreadSettingsPickerNavigator(props: ThreadSettingsPickerPresentation) { |
| 1128 | const solidSheetBackground = String(useThemeColor("--color-sheet-solid")); | 1129 | const theme = useUniwindTheme(); |
| 1129 | const foreground = String(useThemeColor("--color-foreground")); | 1130 | const solidSheetBackground = theme["--color-sheet-solid"]; |
| 1131 | const foreground = theme["--color-foreground"]; | ||
| 1130 | const presentation = useMemo( | 1132 | const presentation = useMemo( |
| 1131 | () => ({ | 1133 | () => ({ |
| 1132 | onClose: props.onClose, | 1134 | onClose: props.onClose, |
apps/mobile/src/features/threads/git/GitCommitSheet.tsx+1-1| ... | @@ -85,7 +85,7 @@ export function GitCommitSheet(_props: GitCommitSheetProps) { | ... | @@ -85,7 +85,7 @@ export function GitCommitSheet(_props: GitCommitSheetProps) { |
| 85 | </Text> | 85 | </Text> |
| 86 | </View> | 86 | </View> |
| 87 | {isDefaultRef ? ( | 87 | {isDefaultRef ? ( |
| 88 | <Text className="text-xs leading-normal text-amber-700 dark:text-amber-400"> | 88 | <Text className="text-xs leading-normal text-adaptive-amber-700-400"> |
| 89 | Warning: this is the default branch. | 89 | Warning: this is the default branch. |
| 90 | </Text> | 90 | </Text> |
| 91 | ) : null} | 91 | ) : null} |
apps/mobile/src/features/threads/git/GitOverviewSheet.tsx+5-6| ... | @@ -17,7 +17,7 @@ import { Alert, Platform, Pressable, RefreshControl, ScrollView, View } from "re | ... | @@ -17,7 +17,7 @@ import { Alert, Platform, Pressable, RefreshControl, ScrollView, View } from "re |
| 17 | 17 | ||
| 18 | import { Screen, ScreenStack, ScreenStackHeaderConfig } from "react-native-screens"; | 18 | import { Screen, ScreenStack, ScreenStackHeaderConfig } from "react-native-screens"; |
| 19 | import { useSafeAreaInsets } from "react-native-safe-area-context"; | 19 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 20 | import { useThemeColor } from "../../../lib/useThemeColor"; | 20 | import { useUniwindTheme } from "../../../lib/useUniwindTheme"; |
| 21 | 21 | ||
| 22 | import { AndroidSheetHeader } from "../../../components/AndroidScreenHeader"; | 22 | import { AndroidSheetHeader } from "../../../components/AndroidScreenHeader"; |
| 23 | import { AppText as Text } from "../../../components/AppText"; | 23 | import { AppText as Text } from "../../../components/AppText"; |
| ... | @@ -53,10 +53,9 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) { | ... | @@ -53,10 +53,9 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) { |
| 53 | const { selectedThreadCwd, selectedThreadWorktreePath } = useSelectedThreadWorktree(); | 53 | const { selectedThreadCwd, selectedThreadWorktreePath } = useSelectedThreadWorktree(); |
| 54 | const gitState = useSelectedThreadGitState(); | 54 | const gitState = useSelectedThreadGitState(); |
| 55 | const gitActions = useSelectedThreadGitActions(); | 55 | const gitActions = useSelectedThreadGitActions(); |
| 56 | 56 | const theme = useUniwindTheme(); | |
| 57 | const iconColor = useThemeColor("--color-icon"); | 57 | const foregroundColor = theme["--color-foreground"]; |
| 58 | const foregroundColor = String(useThemeColor("--color-foreground")); | 58 | const sheetColor = theme["--color-sheet"]; |
| 59 | const sheetColor = String(useThemeColor("--color-sheet")); | ||
| 60 | 59 | ||
| 61 | const gitStatus = useEnvironmentQuery( | 60 | const gitStatus = useEnvironmentQuery( |
| 62 | selectedThread !== null && selectedThreadCwd !== null | 61 | selectedThread !== null && selectedThreadCwd !== null |
| ... | @@ -385,7 +384,7 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) { | ... | @@ -385,7 +384,7 @@ export function GitOverviewSheet(props: GitOverviewSheetProps) { |
| 385 | <SymbolView | 384 | <SymbolView |
| 386 | name="arrow.clockwise" | 385 | name="arrow.clockwise" |
| 387 | size={16} | 386 | size={16} |
| 388 | tintColor={iconColor} | 387 | tintColorClassName={"accent-icon"} |
| 389 | type="monochrome" | 388 | type="monochrome" |
| 390 | weight="medium" | 389 | weight="medium" |
| 391 | /> | 390 | /> |
apps/mobile/src/features/threads/git/gitSheetComponents.tsx+24-12| ... | @@ -1,7 +1,6 @@ | ... | @@ -1,7 +1,6 @@ |
| 1 | import { SymbolView } from "../../../components/AppSymbol"; | 1 | import { SymbolView } from "../../../components/AppSymbol"; |
| 2 | import type { ComponentProps } from "react"; | 2 | import type { ComponentProps } from "react"; |
| 3 | import { Pressable, View } from "react-native"; | 3 | import { Pressable, View } from "react-native"; |
| 4 | import { useThemeColor } from "../../../lib/useThemeColor"; | ||
| 5 | import { AppText as Text } from "../../../components/AppText"; | 4 | import { AppText as Text } from "../../../components/AppText"; |
| 6 | import { cn } from "../../../lib/cn"; | 5 | import { cn } from "../../../lib/cn"; |
| 7 | 6 | ||
| ... | @@ -14,12 +13,13 @@ export function SheetActionButton(props: { | ... | @@ -14,12 +13,13 @@ export function SheetActionButton(props: { |
| 14 | readonly tone?: "primary" | "secondary" | "danger"; | 13 | readonly tone?: "primary" | "secondary" | "danger"; |
| 15 | readonly onPress: () => void; | 14 | readonly onPress: () => void; |
| 16 | }) { | 15 | }) { |
| 17 | const primaryFg = useThemeColor("--color-primary-foreground"); | ||
| 18 | const dangerFg = useThemeColor("--color-danger-foreground"); | ||
| 19 | const secondaryFg = useThemeColor("--color-secondary-foreground"); | ||
| 20 | |||
| 21 | const tone = props.tone ?? "secondary"; | 16 | const tone = props.tone ?? "secondary"; |
| 22 | const textColor = tone === "primary" ? primaryFg : tone === "danger" ? dangerFg : secondaryFg; | 17 | const textColorClassName = |
| 18 | tone === "primary" | ||
| 19 | ? "accent-primary-foreground" | ||
| 20 | : tone === "danger" | ||
| 21 | ? "accent-danger-foreground" | ||
| 22 | : "accent-secondary-foreground"; | ||
| 23 | 23 | ||
| 24 | return ( | 24 | return ( |
| 25 | <Pressable | 25 | <Pressable |
| ... | @@ -34,7 +34,12 @@ export function SheetActionButton(props: { | ... | @@ -34,7 +34,12 @@ export function SheetActionButton(props: { |
| 34 | disabled={props.disabled} | 34 | disabled={props.disabled} |
| 35 | onPress={props.onPress} | 35 | onPress={props.onPress} |
| 36 | > | 36 | > |
| 37 | <SymbolView name={props.icon} size={16} tintColor={textColor} type="monochrome" /> | 37 | <SymbolView |
| 38 | name={props.icon} | ||
| 39 | size={16} | ||
| 40 | tintColorClassName={textColorClassName} | ||
| 41 | type="monochrome" | ||
| 42 | /> | ||
| 38 | <Text | 43 | <Text |
| 39 | className={cn( | 44 | className={cn( |
| 40 | "text-xs font-t3-bold tracking-[0.9px] uppercase", | 45 | "text-xs font-t3-bold tracking-[0.9px] uppercase", |
| ... | @@ -71,9 +76,6 @@ export function SheetListRow(props: { | ... | @@ -71,9 +76,6 @@ export function SheetListRow(props: { |
| 71 | readonly disabled?: boolean; | 76 | readonly disabled?: boolean; |
| 72 | readonly onPress: () => void; | 77 | readonly onPress: () => void; |
| 73 | }) { | 78 | }) { |
| 74 | const iconColor = useThemeColor("--color-icon"); | ||
| 75 | const iconSubtleColor = useThemeColor("--color-icon-subtle"); | ||
| 76 | |||
| 77 | return ( | 79 | return ( |
| 78 | <Pressable | 80 | <Pressable |
| 79 | className="flex-row items-center gap-3 px-1 py-3 disabled:opacity-[0.45]" | 81 | className="flex-row items-center gap-3 px-1 py-3 disabled:opacity-[0.45]" |
| ... | @@ -81,7 +83,12 @@ export function SheetListRow(props: { | ... | @@ -81,7 +83,12 @@ export function SheetListRow(props: { |
| 81 | onPress={props.onPress} | 83 | onPress={props.onPress} |
| 82 | > | 84 | > |
| 83 | <View className="bg-subtle h-9 w-9 items-center justify-center rounded-full"> | 85 | <View className="bg-subtle h-9 w-9 items-center justify-center rounded-full"> |
| 84 | <SymbolView name={props.icon} size={16} tintColor={iconColor} type="monochrome" /> | 86 | <SymbolView |
| 87 | name={props.icon} | ||
| 88 | size={16} | ||
| 89 | tintColorClassName={"accent-icon"} | ||
| 90 | type="monochrome" | ||
| 91 | /> | ||
| 85 | </View> | 92 | </View> |
| 86 | <View className="flex-1 gap-0.5"> | 93 | <View className="flex-1 gap-0.5"> |
| 87 | <Text className="text-foreground text-base font-t3-bold">{props.title}</Text> | 94 | <Text className="text-foreground text-base font-t3-bold">{props.title}</Text> |
| ... | @@ -89,7 +96,12 @@ export function SheetListRow(props: { | ... | @@ -89,7 +96,12 @@ export function SheetListRow(props: { |
| 89 | <Text className="text-foreground-muted text-xs leading-snug">{props.subtitle}</Text> | 96 | <Text className="text-foreground-muted text-xs leading-snug">{props.subtitle}</Text> |
| 90 | ) : null} | 97 | ) : null} |
| 91 | </View> | 98 | </View> |
| 92 | <SymbolView name="chevron.right" size={13} tintColor={iconSubtleColor} type="monochrome" /> | 99 | <SymbolView |
| 100 | name="chevron.right" | ||
| 101 | size={13} | ||
| 102 | tintColorClassName={"accent-icon-subtle"} | ||
| 103 | type="monochrome" | ||
| 104 | /> | ||
| 93 | </Pressable> | 105 | </Pressable> |
| 94 | ); | 106 | ); |
| 95 | } | 107 | } |
apps/mobile/src/features/threads/sidebar-filter-button.tsx+6-5| ... | @@ -1,8 +1,6 @@ | ... | @@ -1,8 +1,6 @@ |
| 1 | import { SymbolView } from "../../components/AppSymbol"; | 1 | import { SymbolView } from "../../components/AppSymbol"; |
| 2 | import { Pressable } from "react-native"; | 2 | import { Pressable } from "react-native"; |
| 3 | 3 | ||
| 4 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 5 | |||
| 6 | export type SidebarFilterButtonIcon = | 4 | export type SidebarFilterButtonIcon = |
| 7 | | "line.3.horizontal.decrease.circle" | 5 | | "line.3.horizontal.decrease.circle" |
| 8 | | "line.3.horizontal.decrease.circle.fill"; | 6 | | "line.3.horizontal.decrease.circle.fill"; |
| ... | @@ -11,8 +9,6 @@ export function SidebarFilterButton(props: { | ... | @@ -11,8 +9,6 @@ export function SidebarFilterButton(props: { |
| 11 | readonly accessibilityLabel: string; | 9 | readonly accessibilityLabel: string; |
| 12 | readonly icon: SidebarFilterButtonIcon; | 10 | readonly icon: SidebarFilterButtonIcon; |
| 13 | }) { | 11 | }) { |
| 14 | const iconColor = useThemeColor("--color-foreground"); | ||
| 15 | |||
| 16 | return ( | 12 | return ( |
| 17 | <Pressable | 13 | <Pressable |
| 18 | className="size-11 cursor-pointer items-center justify-center rounded-full bg-subtle active:opacity-70" | 14 | className="size-11 cursor-pointer items-center justify-center rounded-full bg-subtle active:opacity-70" |
| ... | @@ -20,7 +16,12 @@ export function SidebarFilterButton(props: { | ... | @@ -20,7 +16,12 @@ export function SidebarFilterButton(props: { |
| 20 | accessibilityRole="button" | 16 | accessibilityRole="button" |
| 21 | hitSlop={4} | 17 | hitSlop={4} |
| 22 | > | 18 | > |
| 23 | <SymbolView name={props.icon} size={16} tintColor={iconColor} type="monochrome" /> | 19 | <SymbolView |
| 20 | name={props.icon} | ||
| 21 | size={16} | ||
| 22 | tintColorClassName="accent-foreground" | ||
| 23 | type="monochrome" | ||
| 24 | /> | ||
| 24 | </Pressable> | 25 | </Pressable> |
| 25 | ); | 26 | ); |
| 26 | } | 27 | } |
apps/mobile/src/features/threads/sidebar-header-actions.tsx+6-5| ... | @@ -1,8 +1,6 @@ | ... | @@ -1,8 +1,6 @@ |
| 1 | import { SymbolView } from "../../components/AppSymbol"; | 1 | import { SymbolView } from "../../components/AppSymbol"; |
| 2 | import { Pressable, View } from "react-native"; | 2 | import { Pressable, View } from "react-native"; |
| 3 | 3 | ||
| 4 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 5 | |||
| 6 | export interface SidebarHeaderActionsProps { | 4 | export interface SidebarHeaderActionsProps { |
| 7 | readonly onOpenSettings: () => void; | 5 | readonly onOpenSettings: () => void; |
| 8 | } | 6 | } |
| ... | @@ -12,8 +10,6 @@ function FallbackHeaderButton(props: { | ... | @@ -12,8 +10,6 @@ function FallbackHeaderButton(props: { |
| 12 | readonly icon: "gearshape" | "square.and.pencil"; | 10 | readonly icon: "gearshape" | "square.and.pencil"; |
| 13 | readonly onPress: () => void; | 11 | readonly onPress: () => void; |
| 14 | }) { | 12 | }) { |
| 15 | const iconColor = useThemeColor("--color-foreground"); | ||
| 16 | |||
| 17 | return ( | 13 | return ( |
| 18 | <Pressable | 14 | <Pressable |
| 19 | className="size-11 items-center justify-center rounded-full bg-subtle active:opacity-70" | 15 | className="size-11 items-center justify-center rounded-full bg-subtle active:opacity-70" |
| ... | @@ -22,7 +18,12 @@ function FallbackHeaderButton(props: { | ... | @@ -22,7 +18,12 @@ function FallbackHeaderButton(props: { |
| 22 | hitSlop={4} | 18 | hitSlop={4} |
| 23 | onPress={props.onPress} | 19 | onPress={props.onPress} |
| 24 | > | 20 | > |
| 25 | <SymbolView name={props.icon} size={18} tintColor={iconColor} type="monochrome" /> | 21 | <SymbolView |
| 22 | name={props.icon} | ||
| 23 | size={18} | ||
| 24 | tintColorClassName="accent-foreground" | ||
| 25 | type="monochrome" | ||
| 26 | /> | ||
| 26 | </Pressable> | 27 | </Pressable> |
| 27 | ); | 28 | ); |
| 28 | } | 29 | } |
apps/mobile/src/features/threads/sidebar-navigation-shell.tsx+1-3| ... | @@ -10,7 +10,6 @@ import { getCompactBrandHeaderOptions } from "../../components/CompactBrandTitle | ... | @@ -10,7 +10,6 @@ import { getCompactBrandHeaderOptions } from "../../components/CompactBrandTitle |
| 10 | import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass"; | 10 | import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass"; |
| 11 | import { nativeHeaderScrollEdgeEffects } from "../../native/StackHeader"; | 11 | import { nativeHeaderScrollEdgeEffects } from "../../native/StackHeader"; |
| 12 | import { useMobileNavigationTheme } from "../../lib/useMobileNavigationTheme"; | 12 | import { useMobileNavigationTheme } from "../../lib/useMobileNavigationTheme"; |
| 13 | import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | ||
| 14 | 13 | ||
| 15 | const SCROLL_EDGE_EFFECTS = nativeHeaderScrollEdgeEffects(Platform.OS, Platform.Version); | 14 | const SCROLL_EDGE_EFFECTS = nativeHeaderScrollEdgeEffects(Platform.OS, Platform.Version); |
| 16 | 15 | ||
| ... | @@ -53,8 +52,7 @@ const SidebarStack = createNativeStackNavigator(); | ... | @@ -53,8 +52,7 @@ const SidebarStack = createNativeStackNavigator(); |
| 53 | * navigation hooks used for header configuration inside the pane. | 52 | * navigation hooks used for header configuration inside the pane. |
| 54 | */ | 53 | */ |
| 55 | export function SidebarNavigationShell(props: { readonly children: ReactNode }) { | 54 | export function SidebarNavigationShell(props: { readonly children: ReactNode }) { |
| 56 | const { themeAppearance } = useAppearancePreferences(); | 55 | const navigationTheme = useMobileNavigationTheme(); |
| 57 | const navigationTheme = useMobileNavigationTheme(themeAppearance); | ||
| 58 | 56 | ||
| 59 | return ( | 57 | return ( |
| 60 | <NavigationIndependentTree> | 58 | <NavigationIndependentTree> |
apps/mobile/src/features/threads/thread-list-items.tsx+18-21| ... | @@ -19,7 +19,7 @@ import { cn } from "../../lib/cn"; | ... | @@ -19,7 +19,7 @@ import { cn } from "../../lib/cn"; |
| 19 | import { HOME_HORIZONTAL_INSET } from "../../lib/layoutMetrics"; | 19 | import { HOME_HORIZONTAL_INSET } from "../../lib/layoutMetrics"; |
| 20 | import { relativeTime } from "../../lib/time"; | 20 | import { relativeTime } from "../../lib/time"; |
| 21 | import { themeColorWithAlpha } from "../../lib/mobileTheme"; | 21 | import { themeColorWithAlpha } from "../../lib/mobileTheme"; |
| 22 | import { useThemeColor } from "../../lib/useThemeColor"; | 22 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 23 | import type { PendingNewTask } from "../../state/use-pending-new-tasks"; | 23 | import type { PendingNewTask } from "../../state/use-pending-new-tasks"; |
| 24 | import { useThreadPr, type ThreadPr } from "../../state/use-thread-pr"; | 24 | import { useThreadPr, type ThreadPr } from "../../state/use-thread-pr"; |
| 25 | import type { HomeGroupDisplayAction } from "../home/homeListItems"; | 25 | import type { HomeGroupDisplayAction } from "../home/homeListItems"; |
| ... | @@ -87,7 +87,6 @@ export const ThreadListGroupHeader = memo(function ThreadListGroupHeader(props: | ... | @@ -87,7 +87,6 @@ export const ThreadListGroupHeader = memo(function ThreadListGroupHeader(props: |
| 87 | readonly newThreadTarget?: EnvironmentProject | null; | 87 | readonly newThreadTarget?: EnvironmentProject | null; |
| 88 | readonly onNewThread?: (project: EnvironmentProject) => void; | 88 | readonly onNewThread?: (project: EnvironmentProject) => void; |
| 89 | }) { | 89 | }) { |
| 90 | const iconMutedColor = useThemeColor("--color-icon-muted"); | ||
| 91 | const { groupKey, onGroupAction, onNewThread } = props; | 90 | const { groupKey, onGroupAction, onNewThread } = props; |
| 92 | const newThreadTarget = props.newThreadTarget ?? null; | 91 | const newThreadTarget = props.newThreadTarget ?? null; |
| 93 | const compact = props.variant === "compact"; | 92 | const compact = props.variant === "compact"; |
| ... | @@ -171,7 +170,7 @@ export const ThreadListGroupHeader = memo(function ThreadListGroupHeader(props: | ... | @@ -171,7 +170,7 @@ export const ThreadListGroupHeader = memo(function ThreadListGroupHeader(props: |
| 171 | <SymbolView | 170 | <SymbolView |
| 172 | name="plus" | 171 | name="plus" |
| 173 | size={compact ? 20 : 16} | 172 | size={compact ? 20 : 16} |
| 174 | tintColor={iconMutedColor} | 173 | tintColorClassName={"accent-icon-muted"} |
| 175 | type="monochrome" | 174 | type="monochrome" |
| 176 | weight="medium" | 175 | weight="medium" |
| 177 | /> | 176 | /> |
| ... | @@ -190,7 +189,6 @@ export const ThreadListShowMoreRow = memo(function ThreadListShowMoreRow(props: | ... | @@ -190,7 +189,6 @@ export const ThreadListShowMoreRow = memo(function ThreadListShowMoreRow(props: |
| 190 | readonly groupKey: string; | 189 | readonly groupKey: string; |
| 191 | readonly onGroupAction: (key: string, action: HomeGroupDisplayAction) => void; | 190 | readonly onGroupAction: (key: string, action: HomeGroupDisplayAction) => void; |
| 192 | }) { | 191 | }) { |
| 193 | const iconSubtleColor = useThemeColor("--color-icon-subtle"); | ||
| 194 | const showsMore = props.hiddenCount > 0; | 192 | const showsMore = props.hiddenCount > 0; |
| 195 | const compact = props.variant === "compact"; | 193 | const compact = props.variant === "compact"; |
| 196 | const { groupKey, onGroupAction } = props; | 194 | const { groupKey, onGroupAction } = props; |
| ... | @@ -221,7 +219,7 @@ export const ThreadListShowMoreRow = memo(function ThreadListShowMoreRow(props: | ... | @@ -221,7 +219,7 @@ export const ThreadListShowMoreRow = memo(function ThreadListShowMoreRow(props: |
| 221 | <SymbolView | 219 | <SymbolView |
| 222 | name={icon} | 220 | name={icon} |
| 223 | size={10} | 221 | size={10} |
| 224 | tintColor={iconSubtleColor} | 222 | tintColorClassName={"accent-icon-subtle"} |
| 225 | type="monochrome" | 223 | type="monochrome" |
| 226 | weight="semibold" | 224 | weight="semibold" |
| 227 | /> | 225 | /> |
| ... | @@ -275,10 +273,9 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: { | ... | @@ -275,10 +273,9 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: { |
| 275 | readonly onDeletePendingTask: (pendingTask: PendingNewTask) => void; | 273 | readonly onDeletePendingTask: (pendingTask: PendingNewTask) => void; |
| 276 | }) { | 274 | }) { |
| 277 | const compact = props.variant === "compact"; | 275 | const compact = props.variant === "compact"; |
| 278 | const separatorColor = useThemeColor("--color-separator"); | 276 | const theme = useUniwindTheme(); |
| 279 | const iconSubtleColor = useThemeColor("--color-icon-subtle"); | 277 | const separatorColor = theme["--color-separator"]; |
| 280 | const mutedColor = useThemeColor("--color-foreground-muted"); | 278 | const pressedBackgroundColor = theme["--color-subtle"]; |
| 281 | const pressedBackgroundColor = useThemeColor("--color-subtle"); | ||
| 282 | 279 | ||
| 283 | const { pendingTask, onSelectPendingTask, onDeletePendingTask } = props; | 280 | const { pendingTask, onSelectPendingTask, onDeletePendingTask } = props; |
| 284 | const timestamp = relativeTime(pendingTask.message.createdAt); | 281 | const timestamp = relativeTime(pendingTask.message.createdAt); |
| ... | @@ -294,8 +291,8 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: { | ... | @@ -294,8 +291,8 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: { |
| 294 | ); | 291 | ); |
| 295 | 292 | ||
| 296 | const statusPill = ( | 293 | const statusPill = ( |
| 297 | <View className="rounded-full bg-zinc-500/12 px-1.5 py-0.5 dark:bg-zinc-500/16"> | 294 | <View className="rounded-full bg-adaptive-zinc-500-a12-a16 px-1.5 py-0.5"> |
| 298 | <Text className="text-3xs font-t3-bold text-zinc-600 dark:text-zinc-300">Pending</Text> | 295 | <Text className="text-3xs font-t3-bold text-adaptive-zinc-600-300">Pending</Text> |
| 299 | </View> | 296 | </View> |
| 300 | ); | 297 | ); |
| 301 | 298 | ||
| ... | @@ -305,7 +302,7 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: { | ... | @@ -305,7 +302,7 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: { |
| 305 | <SymbolView | 302 | <SymbolView |
| 306 | name="tray.and.arrow.up" | 303 | name="tray.and.arrow.up" |
| 307 | size={10} | 304 | size={10} |
| 308 | tintColor={compact ? iconSubtleColor : mutedColor} | 305 | tintColorClassName={compact ? "accent-icon-subtle" : "accent-foreground-muted"} |
| 309 | type="monochrome" | 306 | type="monochrome" |
| 310 | /> | 307 | /> |
| 311 | <Text | 308 | <Text |
| ... | @@ -355,7 +352,7 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: { | ... | @@ -355,7 +352,7 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: { |
| 355 | <SymbolView | 352 | <SymbolView |
| 356 | name="chevron.right" | 353 | name="chevron.right" |
| 357 | size={13} | 354 | size={13} |
| 358 | tintColor={iconSubtleColor} | 355 | tintColorClassName={"accent-icon-subtle"} |
| 359 | type="monochrome" | 356 | type="monochrome" |
| 360 | /> | 357 | /> |
| 361 | </View> | 358 | </View> |
| ... | @@ -446,13 +443,13 @@ export const ThreadListRow = memo(function ThreadListRow(props: { | ... | @@ -446,13 +443,13 @@ export const ThreadListRow = memo(function ThreadListRow(props: { |
| 446 | // thread, so a hover highlight can't leak across rows. | 443 | // thread, so a hover highlight can't leak across rows. |
| 447 | const [hovered, setHovered] = useRecyclingState(false); | 444 | const [hovered, setHovered] = useRecyclingState(false); |
| 448 | 445 | ||
| 449 | const separatorColor = useThemeColor("--color-separator"); | 446 | const theme = useUniwindTheme(); |
| 450 | const iconSubtleColor = useThemeColor("--color-icon-subtle"); | 447 | const separatorColor = theme["--color-separator"]; |
| 451 | const screenColor = useThemeColor("--color-screen"); | 448 | const screenColor = theme["--color-screen"]; |
| 452 | const drawerColor = useThemeColor("--color-drawer"); | 449 | const drawerColor = theme["--color-drawer"]; |
| 453 | const pressedBackgroundColor = useThemeColor("--color-subtle"); | 450 | const pressedBackgroundColor = theme["--color-subtle"]; |
| 454 | const selectedBackgroundColor = useThemeColor("--color-user-bubble"); | 451 | const selectedBackgroundColor = theme["--color-user-bubble"]; |
| 455 | const selectedForegroundColor = useThemeColor("--color-user-bubble-foreground"); | 452 | const selectedForegroundColor = theme["--color-user-bubble-foreground"]; |
| 456 | 453 | ||
| 457 | const { thread, onSelectThread, onArchiveThread, onDeleteThread, onRegenerateThreadTitle } = | 454 | const { thread, onSelectThread, onArchiveThread, onDeleteThread, onRegenerateThreadTitle } = |
| 458 | props; | 455 | props; |
| ... | @@ -600,7 +597,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: { | ... | @@ -600,7 +597,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: { |
| 600 | <SymbolView | 597 | <SymbolView |
| 601 | name="chevron.right" | 598 | name="chevron.right" |
| 602 | size={13} | 599 | size={13} |
| 603 | tintColor={iconSubtleColor} | 600 | tintColorClassName={"accent-icon-subtle"} |
| 604 | type="monochrome" | 601 | type="monochrome" |
| 605 | /> | 602 | /> |
| 606 | </View> | 603 | </View> |
apps/mobile/src/features/threads/thread-list-v2-items.tsx+25-23| ... | @@ -16,7 +16,7 @@ import { ProjectFavicon } from "../../components/ProjectFavicon"; | ... | @@ -16,7 +16,7 @@ import { ProjectFavicon } from "../../components/ProjectFavicon"; |
| 16 | import { ProviderIcon } from "../../components/ProviderIcon"; | 16 | import { ProviderIcon } from "../../components/ProviderIcon"; |
| 17 | import { cn } from "../../lib/cn"; | 17 | import { cn } from "../../lib/cn"; |
| 18 | import { relativeTime } from "../../lib/time"; | 18 | import { relativeTime } from "../../lib/time"; |
| 19 | import { useThemeColor } from "../../lib/useThemeColor"; | 19 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 20 | import type { PendingNewTask } from "../../state/use-pending-new-tasks"; | 20 | import type { PendingNewTask } from "../../state/use-pending-new-tasks"; |
| 21 | import { useThreadPr } from "../../state/use-thread-pr"; | 21 | import { useThreadPr } from "../../state/use-thread-pr"; |
| 22 | import { ThreadSwipeable } from "../home/thread-swipe-actions"; | 22 | import { ThreadSwipeable } from "../home/thread-swipe-actions"; |
| ... | @@ -52,10 +52,10 @@ const MONO_FONT = Platform.select({ | ... | @@ -52,10 +52,10 @@ const MONO_FONT = Platform.select({ |
| 52 | const STATUS_LABEL_BY_STATUS: Partial< | 52 | const STATUS_LABEL_BY_STATUS: Partial< |
| 53 | Record<ThreadListV2Status, { label: string; className: string }> | 53 | Record<ThreadListV2Status, { label: string; className: string }> |
| 54 | > = { | 54 | > = { |
| 55 | approval: { label: "Approval", className: "text-amber-700 dark:text-amber-300" }, | 55 | approval: { label: "Approval", className: "text-adaptive-amber-700-300" }, |
| 56 | input: { label: "Input", className: "text-indigo-600 dark:text-indigo-300" }, | 56 | input: { label: "Input", className: "text-adaptive-indigo-600-300" }, |
| 57 | working: { label: "Working", className: "text-sky-600 dark:text-sky-400" }, | 57 | working: { label: "Working", className: "text-adaptive-sky-600-400" }, |
| 58 | failed: { label: "Failed", className: "text-red-700 dark:text-red-300" }, | 58 | failed: { label: "Failed", className: "text-adaptive-red-700-300" }, |
| 59 | }; | 59 | }; |
| 60 | 60 | ||
| 61 | function threadTimeLabel(thread: EnvironmentThreadShell): string { | 61 | function threadTimeLabel(thread: EnvironmentThreadShell): string { |
| ... | @@ -93,7 +93,6 @@ export const ThreadListV2SectionDivider = memo(function ThreadListV2SectionDivid | ... | @@ -93,7 +93,6 @@ export const ThreadListV2SectionDivider = memo(function ThreadListV2SectionDivid |
| 93 | readonly label: string; | 93 | readonly label: string; |
| 94 | readonly pane?: "screen" | "sidebar"; | 94 | readonly pane?: "screen" | "sidebar"; |
| 95 | }) { | 95 | }) { |
| 96 | const borderColor = useThemeColor("--color-border"); | ||
| 97 | return ( | 96 | return ( |
| 98 | <View | 97 | <View |
| 99 | className={cn( | 98 | className={cn( |
| ... | @@ -102,7 +101,7 @@ export const ThreadListV2SectionDivider = memo(function ThreadListV2SectionDivid | ... | @@ -102,7 +101,7 @@ export const ThreadListV2SectionDivider = memo(function ThreadListV2SectionDivid |
| 102 | )} | 101 | )} |
| 103 | > | 102 | > |
| 104 | <Text className="text-xs font-t3-medium text-foreground-tertiary">{props.label}</Text> | 103 | <Text className="text-xs font-t3-medium text-foreground-tertiary">{props.label}</Text> |
| 105 | <View className="h-px flex-1" style={{ backgroundColor: borderColor }} /> | 104 | <View className="h-px flex-1 bg-border" /> |
| 106 | </View> | 105 | </View> |
| 107 | ); | 106 | ); |
| 108 | }); | 107 | }); |
| ... | @@ -134,10 +133,10 @@ export const ThreadListV2SnoozedShelfHeader = memo(function ThreadListV2SnoozedS | ... | @@ -134,10 +133,10 @@ export const ThreadListV2SnoozedShelfHeader = memo(function ThreadListV2SnoozedS |
| 134 | onPress={props.onToggle} | 133 | onPress={props.onToggle} |
| 135 | style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })} | 134 | style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })} |
| 136 | > | 135 | > |
| 137 | <Text className="text-xs font-t3-medium text-blue-600 dark:text-blue-400"> | 136 | <Text className="text-xs font-t3-medium text-adaptive-blue-600-400"> |
| 138 | {props.expanded ? "Snoozed" : `Snoozed (${props.count})`} | 137 | {props.expanded ? "Snoozed" : `Snoozed (${props.count})`} |
| 139 | </Text> | 138 | </Text> |
| 140 | <View className="h-px flex-1 bg-blue-500/20 dark:bg-blue-400/15" /> | 139 | <View className="h-px flex-1 bg-adaptive-blue-500-a20-blue-400-a15" /> |
| 141 | <SymbolView | 140 | <SymbolView |
| 142 | name="chevron.down" | 141 | name="chevron.down" |
| 143 | size={10} | 142 | size={10} |
| ... | @@ -156,7 +155,6 @@ export const ThreadListV2SettledShelfHeader = memo(function ThreadListV2SettledS | ... | @@ -156,7 +155,6 @@ export const ThreadListV2SettledShelfHeader = memo(function ThreadListV2SettledS |
| 156 | readonly onToggle: () => void; | 155 | readonly onToggle: () => void; |
| 157 | readonly pane?: "screen" | "sidebar"; | 156 | readonly pane?: "screen" | "sidebar"; |
| 158 | }) { | 157 | }) { |
| 159 | const mutedColor = useThemeColor("--color-foreground-muted"); | ||
| 160 | return ( | 158 | return ( |
| 161 | <Pressable | 159 | <Pressable |
| 162 | accessibilityHint={ | 160 | accessibilityHint={ |
| ... | @@ -180,7 +178,7 @@ export const ThreadListV2SettledShelfHeader = memo(function ThreadListV2SettledS | ... | @@ -180,7 +178,7 @@ export const ThreadListV2SettledShelfHeader = memo(function ThreadListV2SettledS |
| 180 | <SymbolView | 178 | <SymbolView |
| 181 | name="chevron.down" | 179 | name="chevron.down" |
| 182 | size={10} | 180 | size={10} |
| 183 | tintColor={mutedColor} | 181 | tintColorClassName={"accent-foreground-muted"} |
| 184 | type="monochrome" | 182 | type="monochrome" |
| 185 | style={{ transform: [{ rotate: props.expanded ? "180deg" : "0deg" }] }} | 183 | style={{ transform: [{ rotate: props.expanded ? "180deg" : "0deg" }] }} |
| 186 | /> | 184 | /> |
| ... | @@ -213,8 +211,9 @@ export const ThreadListV2PendingRow = memo(function ThreadListV2PendingRow(props | ... | @@ -213,8 +211,9 @@ export const ThreadListV2PendingRow = memo(function ThreadListV2PendingRow(props |
| 213 | readonly onDeletePendingTask: (pendingTask: PendingNewTask) => void; | 211 | readonly onDeletePendingTask: (pendingTask: PendingNewTask) => void; |
| 214 | }) { | 212 | }) { |
| 215 | const { pendingTask, onSelectPendingTask, onDeletePendingTask } = props; | 213 | const { pendingTask, onSelectPendingTask, onDeletePendingTask } = props; |
| 216 | const drawerColor = useThemeColor("--color-drawer"); | 214 | const theme = useUniwindTheme(); |
| 217 | const pressedBackgroundColor = useThemeColor("--color-subtle"); | 215 | const drawerColor = theme["--color-drawer"]; |
| 216 | const pressedBackgroundColor = theme["--color-subtle"]; | ||
| 218 | const sidebarPane = props.pane === "sidebar"; | 217 | const sidebarPane = props.pane === "sidebar"; |
| 219 | const projectTitle = | 218 | const projectTitle = |
| 220 | props.projectTitle ?? props.project?.title ?? pendingTask.creation.projectTitle ?? ""; | 219 | props.projectTitle ?? props.project?.title ?? pendingTask.creation.projectTitle ?? ""; |
| ... | @@ -418,11 +417,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -418,11 +417,11 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 418 | onChangeRequestState?.(threadKey, changeRequest); | 417 | onChangeRequestState?.(threadKey, changeRequest); |
| 419 | }, [onChangeRequestState, prState, prUpdatedAt, thread.linkedPullRequest, threadKey]); | 418 | }, [onChangeRequestState, prState, prUpdatedAt, thread.linkedPullRequest, threadKey]); |
| 420 | 419 | ||
| 421 | const screenColor = useThemeColor("--color-screen"); | 420 | const theme = useUniwindTheme(); |
| 422 | const drawerColor = useThemeColor("--color-drawer"); | 421 | const screenColor = theme["--color-screen"]; |
| 423 | const pressedBackgroundColor = useThemeColor("--color-subtle"); | 422 | const drawerColor = theme["--color-drawer"]; |
| 424 | const selectedBackgroundColor = useThemeColor("--color-user-bubble"); | 423 | const pressedBackgroundColor = theme["--color-subtle"]; |
| 425 | const pinTintColor = useThemeColor("--color-foreground-muted"); | 424 | const selectedBackgroundColor = theme["--color-user-bubble"]; |
| 426 | const sidebarPane = props.pane === "sidebar"; | 425 | const sidebarPane = props.pane === "sidebar"; |
| 427 | const selected = props.selected === true; | 426 | const selected = props.selected === true; |
| 428 | 427 | ||
| ... | @@ -699,7 +698,12 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -699,7 +698,12 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 699 | {props.projectTitle ?? props.project?.title ?? ""} | 698 | {props.projectTitle ?? props.project?.title ?? ""} |
| 700 | </Text> | 699 | </Text> |
| 701 | {pinnedRow ? ( | 700 | {pinnedRow ? ( |
| 702 | <SymbolView name="pin" size={11} tintColor={pinTintColor} type="monochrome" /> | 701 | <SymbolView |
| 702 | name="pin" | ||
| 703 | size={11} | ||
| 704 | tintColorClassName={"accent-foreground-muted"} | ||
| 705 | type="monochrome" | ||
| 706 | /> | ||
| 703 | ) : null} | 707 | ) : null} |
| 704 | <Text | 708 | <Text |
| 705 | className={cn( | 709 | className={cn( |
| ... | @@ -735,9 +739,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -735,9 +739,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 735 | <Text | 739 | <Text |
| 736 | className={cn( | 740 | className={cn( |
| 737 | "flex-1 text-xs", | 741 | "flex-1 text-xs", |
| 738 | selected | 742 | selected ? "text-user-bubble-foreground-muted" : "text-adaptive-red-600-a80-400-a80", |
| 739 | ? "text-user-bubble-foreground-muted" | ||
| 740 | : "text-red-600/80 dark:text-red-400/80", | ||
| 741 | )} | 743 | )} |
| 742 | numberOfLines={1} | 744 | numberOfLines={1} |
| 743 | > | 745 | > |
| ... | @@ -905,7 +907,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { | ... | @@ -905,7 +907,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { |
| 905 | selected | 907 | selected |
| 906 | ? "text-user-bubble-foreground-muted" | 908 | ? "text-user-bubble-foreground-muted" |
| 907 | : snoozedRow | 909 | : snoozedRow |
| 908 | ? "text-blue-600 dark:text-blue-400" | 910 | ? "text-adaptive-blue-600-400" |
| 909 | : "text-foreground-tertiary", | 911 | : "text-foreground-tertiary", |
| 910 | )} | 912 | )} |
| 911 | style={{ fontFamily: MONO_FONT }} | 913 | style={{ fontFamily: MONO_FONT }} |
apps/mobile/src/features/threads/thread-search-match.tsx+2-2| ... | @@ -65,8 +65,8 @@ export function ThreadSearchMatchExcerpt(props: { | ... | @@ -65,8 +65,8 @@ export function ThreadSearchMatchExcerpt(props: { |
| 65 | props.selected | 65 | props.selected |
| 66 | ? "text-user-bubble-foreground" | 66 | ? "text-user-bubble-foreground" |
| 67 | : isUser | 67 | : isUser |
| 68 | ? "text-blue-500 dark:text-blue-400" | 68 | ? "text-adaptive-blue-500-400" |
| 69 | : "text-emerald-600 dark:text-emerald-400", | 69 | : "text-adaptive-emerald-600-400", |
| 70 | )} | 70 | )} |
| 71 | > | 71 | > |
| 72 | {isUser ? "You:" : "Agent:"}{" "} | 72 | {isUser ? "You:" : "Agent:"}{" "} |
apps/mobile/src/features/threads/thread-work-log.tsx+5-14| ... | @@ -7,7 +7,6 @@ import { scaledTypographyLineHeight } from "../../lib/appearancePreferences"; | ... | @@ -7,7 +7,6 @@ import { scaledTypographyLineHeight } from "../../lib/appearancePreferences"; |
| 7 | import { cn } from "../../lib/cn"; | 7 | import { cn } from "../../lib/cn"; |
| 8 | import type { ThreadFeedActivity } from "../../lib/threadActivity"; | 8 | import type { ThreadFeedActivity } from "../../lib/threadActivity"; |
| 9 | import { MOBILE_TYPOGRAPHY } from "../../lib/typography"; | 9 | import { MOBILE_TYPOGRAPHY } from "../../lib/typography"; |
| 10 | import { useThemeColor } from "../../lib/useThemeColor"; | ||
| 11 | import Animated, { FadeIn } from "react-native-reanimated"; | 10 | import Animated, { FadeIn } from "react-native-reanimated"; |
| 12 | 11 | ||
| 13 | const WORK_LOG_LAYOUT_ANIMATION = { | 12 | const WORK_LOG_LAYOUT_ANIMATION = { |
| ... | @@ -128,7 +127,6 @@ export function ThreadWorkLog(props: { | ... | @@ -128,7 +127,6 @@ export function ThreadWorkLog(props: { |
| 128 | readonly onCopyRow: (rowId: string, value: string) => void; | 127 | readonly onCopyRow: (rowId: string, value: string) => void; |
| 129 | readonly onToggleRow: (rowId: string) => void; | 128 | readonly onToggleRow: (rowId: string) => void; |
| 130 | }) { | 129 | }) { |
| 131 | const pressedBackground = useThemeColor("--color-subtle"); | ||
| 132 | const rows = visibleWorkLogActivities(props.activities).map((activity) => ({ | 130 | const rows = visibleWorkLogActivities(props.activities).map((activity) => ({ |
| 133 | ...activity, | 131 | ...activity, |
| 134 | detail: compactActivityDetail(activity.detail), | 132 | detail: compactActivityDetail(activity.detail), |
| ... | @@ -178,10 +176,7 @@ export function ThreadWorkLog(props: { | ... | @@ -178,10 +176,7 @@ export function ThreadWorkLog(props: { |
| 178 | } | 176 | } |
| 179 | }} | 177 | }} |
| 180 | onLongPress={() => props.onCopyRow(row.id, row.getCopyText())} | 178 | onLongPress={() => props.onCopyRow(row.id, row.getCopyText())} |
| 181 | style={({ pressed }) => ({ | 179 | className="rounded-md px-0.5 py-0 active:bg-subtle" |
| 182 | backgroundColor: pressed ? pressedBackground : "transparent", | ||
| 183 | })} | ||
| 184 | className="rounded-md px-0.5 py-0" | ||
| 185 | > | 180 | > |
| 186 | <View className="min-h-8 flex-row items-center gap-1.5"> | 181 | <View className="min-h-8 flex-row items-center gap-1.5"> |
| 187 | <View className="h-[18px] w-5 shrink-0 items-center justify-center"> | 182 | <View className="h-[18px] w-5 shrink-0 items-center justify-center"> |
| ... | @@ -198,7 +193,7 @@ export function ThreadWorkLog(props: { | ... | @@ -198,7 +193,7 @@ export function ThreadWorkLog(props: { |
| 198 | <Text | 193 | <Text |
| 199 | className={cn( | 194 | className={cn( |
| 200 | "font-t3-medium text-foreground", | 195 | "font-t3-medium text-foreground", |
| 201 | iconIsDestructive && "text-rose-600 dark:text-rose-400", | 196 | iconIsDestructive && "text-adaptive-rose-600-400", |
| 202 | )} | 197 | )} |
| 203 | > | 198 | > |
| 204 | {row.summary} | 199 | {row.summary} |
| ... | @@ -210,7 +205,7 @@ export function ThreadWorkLog(props: { | ... | @@ -210,7 +205,7 @@ export function ThreadWorkLog(props: { |
| 210 | 205 | ||
| 211 | <View className="shrink-0 flex-row items-center gap-px"> | 206 | <View className="shrink-0 flex-row items-center gap-px"> |
| 212 | {props.copiedRowId === row.id ? ( | 207 | {props.copiedRowId === row.id ? ( |
| 213 | <Text className="pr-1 font-t3-medium text-3xs text-emerald-600 dark:text-emerald-400"> | 208 | <Text className="pr-1 font-t3-medium text-3xs text-adaptive-emerald-600-400"> |
| 214 | Copied | 209 | Copied |
| 215 | </Text> | 210 | </Text> |
| 216 | ) : null} | 211 | ) : null} |
| ... | @@ -249,7 +244,7 @@ export function ThreadWorkLog(props: { | ... | @@ -249,7 +244,7 @@ export function ThreadWorkLog(props: { |
| 249 | </Pressable> | 244 | </Pressable> |
| 250 | 245 | ||
| 251 | {fullDetail ? ( | 246 | {fullDetail ? ( |
| 252 | <View className="ml-7 border-l border-neutral-300/60 pb-1 pl-3 pt-0.5 dark:border-white/[0.12]"> | 247 | <View className="ml-7 border-l border-adaptive-neutral-300-a60-white-a12 pb-1 pl-3 pt-0.5"> |
| 253 | <ScrollView | 248 | <ScrollView |
| 254 | nestedScrollEnabled | 249 | nestedScrollEnabled |
| 255 | directionalLockEnabled | 250 | directionalLockEnabled |
| ... | @@ -281,7 +276,6 @@ export function ThreadWorkGroupToggle(props: { | ... | @@ -281,7 +276,6 @@ export function ThreadWorkGroupToggle(props: { |
| 281 | readonly onlyToolActivities: boolean; | 276 | readonly onlyToolActivities: boolean; |
| 282 | readonly onToggle: () => void; | 277 | readonly onToggle: () => void; |
| 283 | }) { | 278 | }) { |
| 284 | const pressedBackground = useThemeColor("--color-subtle"); | ||
| 285 | const noun = props.onlyToolActivities | 279 | const noun = props.onlyToolActivities |
| 286 | ? props.hiddenCount === 1 | 280 | ? props.hiddenCount === 1 |
| 287 | ? "tool call" | 281 | ? "tool call" |
| ... | @@ -305,10 +299,7 @@ export function ThreadWorkGroupToggle(props: { | ... | @@ -305,10 +299,7 @@ export function ThreadWorkGroupToggle(props: { |
| 305 | void Haptics.selectionAsync(); | 299 | void Haptics.selectionAsync(); |
| 306 | props.onToggle(); | 300 | props.onToggle(); |
| 307 | }} | 301 | }} |
| 308 | style={({ pressed }) => ({ | 302 | className="min-h-8 flex-row items-center gap-1.5 rounded-md px-0.5 py-0 active:bg-subtle" |
| 309 | backgroundColor: pressed ? pressedBackground : "transparent", | ||
| 310 | })} | ||
| 311 | className="min-h-8 flex-row items-center gap-1.5 rounded-md px-0.5 py-0" | ||
| 312 | > | 303 | > |
| 313 | <View className="h-[18px] w-5 items-center justify-center"> | 304 | <View className="h-[18px] w-5 items-center justify-center"> |
| 314 | <SymbolView | 305 | <SymbolView |
apps/mobile/src/features/threads/threadPresentation.ts+12-12| ... | @@ -53,8 +53,8 @@ export function resolveThreadStatus( | ... | @@ -53,8 +53,8 @@ export function resolveThreadStatus( |
| 53 | return { | 53 | return { |
| 54 | kind: "pending-approval", | 54 | kind: "pending-approval", |
| 55 | label: "Needs Approval", | 55 | label: "Needs Approval", |
| 56 | pillClassName: "bg-amber-500/12 dark:bg-amber-500/16", | 56 | pillClassName: "bg-adaptive-amber-500-a12-a16", |
| 57 | textClassName: "text-amber-700 dark:text-amber-300", | 57 | textClassName: "text-adaptive-amber-700-300", |
| 58 | iconColor: "#ff9f0a", | 58 | iconColor: "#ff9f0a", |
| 59 | iconBackground: "rgba(255,159,10,0.22)", | 59 | iconBackground: "rgba(255,159,10,0.22)", |
| 60 | pulse: false, | 60 | pulse: false, |
| ... | @@ -65,8 +65,8 @@ export function resolveThreadStatus( | ... | @@ -65,8 +65,8 @@ export function resolveThreadStatus( |
| 65 | return { | 65 | return { |
| 66 | kind: "awaiting-input", | 66 | kind: "awaiting-input", |
| 67 | label: "Awaiting Input", | 67 | label: "Awaiting Input", |
| 68 | pillClassName: "bg-indigo-500/12 dark:bg-indigo-500/16", | 68 | pillClassName: "bg-adaptive-indigo-500-a12-a16", |
| 69 | textClassName: "text-indigo-700 dark:text-indigo-300", | 69 | textClassName: "text-adaptive-indigo-700-300", |
| 70 | iconColor: "#5e5ce6", | 70 | iconColor: "#5e5ce6", |
| 71 | iconBackground: "rgba(94,92,230,0.22)", | 71 | iconBackground: "rgba(94,92,230,0.22)", |
| 72 | pulse: false, | 72 | pulse: false, |
| ... | @@ -77,8 +77,8 @@ export function resolveThreadStatus( | ... | @@ -77,8 +77,8 @@ export function resolveThreadStatus( |
| 77 | return { | 77 | return { |
| 78 | kind: "working", | 78 | kind: "working", |
| 79 | label: "Working", | 79 | label: "Working", |
| 80 | pillClassName: "bg-sky-500/12 dark:bg-sky-500/16", | 80 | pillClassName: "bg-adaptive-sky-500-a12-a16", |
| 81 | textClassName: "text-sky-700 dark:text-sky-300", | 81 | textClassName: "text-adaptive-sky-700-300", |
| 82 | iconColor: "#0a84ff", | 82 | iconColor: "#0a84ff", |
| 83 | iconBackground: "rgba(10,132,255,0.22)", | 83 | iconBackground: "rgba(10,132,255,0.22)", |
| 84 | pulse: true, | 84 | pulse: true, |
| ... | @@ -89,8 +89,8 @@ export function resolveThreadStatus( | ... | @@ -89,8 +89,8 @@ export function resolveThreadStatus( |
| 89 | return { | 89 | return { |
| 90 | kind: "connecting", | 90 | kind: "connecting", |
| 91 | label: "Connecting", | 91 | label: "Connecting", |
| 92 | pillClassName: "bg-sky-500/12 dark:bg-sky-500/16", | 92 | pillClassName: "bg-adaptive-sky-500-a12-a16", |
| 93 | textClassName: "text-sky-700 dark:text-sky-300", | 93 | textClassName: "text-adaptive-sky-700-300", |
| 94 | iconColor: "#0a84ff", | 94 | iconColor: "#0a84ff", |
| 95 | iconBackground: "rgba(10,132,255,0.22)", | 95 | iconBackground: "rgba(10,132,255,0.22)", |
| 96 | pulse: true, | 96 | pulse: true, |
| ... | @@ -101,8 +101,8 @@ export function resolveThreadStatus( | ... | @@ -101,8 +101,8 @@ export function resolveThreadStatus( |
| 101 | return { | 101 | return { |
| 102 | kind: "error", | 102 | kind: "error", |
| 103 | label: "Error", | 103 | label: "Error", |
| 104 | pillClassName: "bg-rose-500/12 dark:bg-rose-500/16", | 104 | pillClassName: "bg-adaptive-rose-500-a12-a16", |
| 105 | textClassName: "text-rose-700 dark:text-rose-300", | 105 | textClassName: "text-adaptive-rose-700-300", |
| 106 | iconColor: "#ff453a", | 106 | iconColor: "#ff453a", |
| 107 | iconBackground: "rgba(255,69,58,0.22)", | 107 | iconBackground: "rgba(255,69,58,0.22)", |
| 108 | pulse: false, | 108 | pulse: false, |
| ... | @@ -117,8 +117,8 @@ export function resolveThreadStatus( | ... | @@ -117,8 +117,8 @@ export function resolveThreadStatus( |
| 117 | return { | 117 | return { |
| 118 | kind: "plan-ready", | 118 | kind: "plan-ready", |
| 119 | label: "Plan Ready", | 119 | label: "Plan Ready", |
| 120 | pillClassName: "bg-violet-500/12 dark:bg-violet-500/16", | 120 | pillClassName: "bg-adaptive-violet-500-a12-a16", |
| 121 | textClassName: "text-violet-700 dark:text-violet-300", | 121 | textClassName: "text-adaptive-violet-700-300", |
| 122 | iconColor: "#bf5af2", | 122 | iconColor: "#bf5af2", |
| 123 | iconBackground: "rgba(191,90,242,0.22)", | 123 | iconBackground: "rgba(191,90,242,0.22)", |
| 124 | pulse: false, | 124 | pulse: false, |
apps/mobile/src/lib/mobileDefaultTheme.ts deleted-139| ... | @@ -1,139 +0,0 @@ | ||
| 1 | import type { MobileThemeVariables } from "./mobileTheme"; | ||
| 2 | |||
| 3 | /** The existing T3 Code mobile palette, retained as the upgrade-safe default. */ | ||
| 4 | export const DEFAULT_MOBILE_THEME_VARIABLES = { | ||
| 5 | light: { | ||
| 6 | "--color-screen": "#f2f2f7", | ||
| 7 | "--color-sheet": "rgba(242, 242, 247, 0.98)", | ||
| 8 | "--color-sheet-solid": "#f2f2f7", | ||
| 9 | "--color-card": "#ffffff", | ||
| 10 | "--color-card-alt": "#f5f5f5", | ||
| 11 | "--color-card-translucent": "rgba(255, 255, 255, 0.8)", | ||
| 12 | "--color-foreground": "#262626", | ||
| 13 | "--color-foreground-secondary": "#525252", | ||
| 14 | "--color-foreground-muted": "#737373", | ||
| 15 | "--color-foreground-tertiary": "#8e8e93", | ||
| 16 | "--color-border": "rgba(0, 0, 0, 0.08)", | ||
| 17 | "--color-border-subtle": "rgba(0, 0, 0, 0.06)", | ||
| 18 | "--color-separator": "rgba(0, 0, 0, 0.04)", | ||
| 19 | "--color-subtle": "rgba(0, 0, 0, 0.04)", | ||
| 20 | "--color-subtle-strong": "rgba(0, 0, 0, 0.08)", | ||
| 21 | "--color-inline-skill-background": "rgba(217, 70, 239, 0.12)", | ||
| 22 | "--color-inline-skill-border": "rgba(217, 70, 239, 0.25)", | ||
| 23 | "--color-inline-skill-foreground": "#a21caf", | ||
| 24 | "--color-primary": "#262626", | ||
| 25 | "--color-primary-foreground": "#ffffff", | ||
| 26 | "--color-primary-shadow": "#000000", | ||
| 27 | "--color-secondary": "#ffffff", | ||
| 28 | "--color-secondary-foreground": "#262626", | ||
| 29 | "--color-secondary-border": "rgba(0, 0, 0, 0.08)", | ||
| 30 | "--color-switch-active-track": "#34c759", | ||
| 31 | "--color-switch-active-thumb": "#ffffff", | ||
| 32 | "--color-switch-inactive-track": "rgba(0, 0, 0, 0.08)", | ||
| 33 | "--color-switch-inactive-thumb": "#8e8e93", | ||
| 34 | "--color-danger": "#fef2f2", | ||
| 35 | "--color-danger-border": "rgba(239, 68, 68, 0.12)", | ||
| 36 | "--color-danger-foreground": "#dc2626", | ||
| 37 | "--color-input": "#ffffff", | ||
| 38 | "--color-input-border": "rgba(0, 0, 0, 0.1)", | ||
| 39 | "--color-sidebar-search": "rgba(118, 118, 128, 0.12)", | ||
| 40 | "--color-placeholder": "#737373", | ||
| 41 | "--color-icon": "#262626", | ||
| 42 | "--color-icon-muted": "#525252", | ||
| 43 | "--color-icon-subtle": "#a3a3a3", | ||
| 44 | "--color-header": "rgba(255, 255, 255, 0.97)", | ||
| 45 | "--color-header-border": "rgba(0, 0, 0, 0.06)", | ||
| 46 | "--color-glass-surface": "rgba(255, 255, 255, 0.72)", | ||
| 47 | "--color-glass-tint": "rgba(255, 255, 255, 0.18)", | ||
| 48 | "--color-status-bar": "#f2f2f7", | ||
| 49 | "--color-md-body": "#111111", | ||
| 50 | "--color-md-strong": "#000000", | ||
| 51 | "--color-md-link": "#2563eb", | ||
| 52 | "--color-md-blockquote-border": "rgba(0, 0, 0, 0.08)", | ||
| 53 | "--color-md-blockquote-bg": "rgba(0, 0, 0, 0.02)", | ||
| 54 | "--color-md-code-bg": "rgba(0, 0, 0, 0.04)", | ||
| 55 | "--color-md-code-text": "#262626", | ||
| 56 | "--color-md-user-code-bg": "rgba(255, 255, 255, 0.22)", | ||
| 57 | "--color-md-user-code-text": "#ffffff", | ||
| 58 | "--color-md-user-fence-bg": "rgba(0, 0, 0, 0.16)", | ||
| 59 | "--color-md-user-fence-text": "#ffffff", | ||
| 60 | "--color-md-hr": "rgba(0, 0, 0, 0.08)", | ||
| 61 | "--color-user-bubble": "#007aff", | ||
| 62 | "--color-user-bubble-foreground": "#ffffff", | ||
| 63 | "--color-user-bubble-foreground-muted": "rgba(255, 255, 255, 0.78)", | ||
| 64 | "--color-user-bubble-skill-foreground": "#f0abfc", | ||
| 65 | "--color-backdrop": "rgba(0, 0, 0, 0.22)", | ||
| 66 | "--color-drawer": "rgba(255, 255, 255, 0.99)", | ||
| 67 | "--color-drawer-shadow": "rgba(0, 0, 0, 0.12)", | ||
| 68 | "--color-dot-separator": "rgba(0, 0, 0, 0.2)", | ||
| 69 | "--color-wordmark": "#262626", | ||
| 70 | "--color-chevron": "rgba(0, 0, 0, 0.2)", | ||
| 71 | }, | ||
| 72 | dark: { | ||
| 73 | "--color-screen": "#0a0a0a", | ||
| 74 | "--color-sheet": "rgba(14, 14, 14, 0.98)", | ||
| 75 | "--color-sheet-solid": "#0e0e0e", | ||
| 76 | "--color-card": "#171717", | ||
| 77 | "--color-card-alt": "#1c1c1c", | ||
| 78 | "--color-card-translucent": "rgba(17, 17, 17, 0.8)", | ||
| 79 | "--color-foreground": "#f5f5f5", | ||
| 80 | "--color-foreground-secondary": "#a3a3a3", | ||
| 81 | "--color-foreground-muted": "#8e8e93", | ||
| 82 | "--color-foreground-tertiary": "#636366", | ||
| 83 | "--color-border": "rgba(255, 255, 255, 0.06)", | ||
| 84 | "--color-border-subtle": "rgba(255, 255, 255, 0.04)", | ||
| 85 | "--color-separator": "rgba(255, 255, 255, 0.03)", | ||
| 86 | "--color-subtle": "rgba(255, 255, 255, 0.04)", | ||
| 87 | "--color-subtle-strong": "rgba(255, 255, 255, 0.08)", | ||
| 88 | "--color-inline-skill-background": "rgba(217, 70, 239, 0.12)", | ||
| 89 | "--color-inline-skill-border": "rgba(217, 70, 239, 0.25)", | ||
| 90 | "--color-inline-skill-foreground": "#f0abfc", | ||
| 91 | "--color-primary": "#f5f5f5", | ||
| 92 | "--color-primary-foreground": "#0a0a0a", | ||
| 93 | "--color-primary-shadow": "#000000", | ||
| 94 | "--color-secondary": "rgba(255, 255, 255, 0.04)", | ||
| 95 | "--color-secondary-foreground": "#f5f5f5", | ||
| 96 | "--color-secondary-border": "rgba(255, 255, 255, 0.06)", | ||
| 97 | "--color-switch-active-track": "#30d158", | ||
| 98 | "--color-switch-active-thumb": "#ffffff", | ||
| 99 | "--color-switch-inactive-track": "rgba(255, 255, 255, 0.06)", | ||
| 100 | "--color-switch-inactive-thumb": "#8e8e93", | ||
| 101 | "--color-danger": "rgba(239, 68, 68, 0.14)", | ||
| 102 | "--color-danger-border": "rgba(248, 113, 113, 0.18)", | ||
| 103 | "--color-danger-foreground": "#fca5a5", | ||
| 104 | "--color-input": "#141414", | ||
| 105 | "--color-input-border": "rgba(255, 255, 255, 0.08)", | ||
| 106 | "--color-sidebar-search": "rgba(118, 118, 128, 0.24)", | ||
| 107 | "--color-placeholder": "#8e8e93", | ||
| 108 | "--color-icon": "#f5f5f5", | ||
| 109 | "--color-icon-muted": "#a3a3a3", | ||
| 110 | "--color-icon-subtle": "#8e8e93", | ||
| 111 | "--color-header": "rgba(10, 10, 10, 0.97)", | ||
| 112 | "--color-header-border": "rgba(255, 255, 255, 0.06)", | ||
| 113 | "--color-glass-surface": "rgba(23, 23, 23, 0.78)", | ||
| 114 | "--color-glass-tint": "rgba(23, 23, 23, 0.24)", | ||
| 115 | "--color-status-bar": "#0a0a0a", | ||
| 116 | "--color-md-body": "#e5e5e5", | ||
| 117 | "--color-md-strong": "#f5f5f5", | ||
| 118 | "--color-md-link": "#60a5fa", | ||
| 119 | "--color-md-blockquote-border": "rgba(255, 255, 255, 0.1)", | ||
| 120 | "--color-md-blockquote-bg": "rgba(255, 255, 255, 0.03)", | ||
| 121 | "--color-md-code-bg": "rgba(255, 255, 255, 0.06)", | ||
| 122 | "--color-md-code-text": "#e5e5e5", | ||
| 123 | "--color-md-user-code-bg": "rgba(255, 255, 255, 0.18)", | ||
| 124 | "--color-md-user-code-text": "#ffffff", | ||
| 125 | "--color-md-user-fence-bg": "rgba(0, 0, 0, 0.28)", | ||
| 126 | "--color-md-user-fence-text": "#ffffff", | ||
| 127 | "--color-md-hr": "rgba(255, 255, 255, 0.08)", | ||
| 128 | "--color-user-bubble": "#0a84ff", | ||
| 129 | "--color-user-bubble-foreground": "#ffffff", | ||
| 130 | "--color-user-bubble-foreground-muted": "rgba(255, 255, 255, 0.78)", | ||
| 131 | "--color-user-bubble-skill-foreground": "#f0abfc", | ||
| 132 | "--color-backdrop": "rgba(0, 0, 0, 0.48)", | ||
| 133 | "--color-drawer": "rgba(14, 14, 14, 0.99)", | ||
| 134 | "--color-drawer-shadow": "rgba(0, 0, 0, 0.32)", | ||
| 135 | "--color-dot-separator": "rgba(255, 255, 255, 0.2)", | ||
| 136 | "--color-wordmark": "#f5f5f5", | ||
| 137 | "--color-chevron": "rgba(255, 255, 255, 0.2)", | ||
| 138 | }, | ||
| 139 | } as const satisfies Readonly<Record<"light" | "dark", MobileThemeVariables>>; | ||
apps/mobile/src/lib/mobileTheme.test-support.ts created+20| ... | @@ -0,0 +1,20 @@ | ||
| 1 | import * as NodeFS from "node:fs"; | ||
| 2 | |||
| 3 | import type { MobileThemeAppearance, MobileThemeVariables } from "./mobileTheme"; | ||
| 4 | |||
| 5 | export function readDefaultMobileThemeVariables( | ||
| 6 | appearance: MobileThemeAppearance, | ||
| 7 | ): MobileThemeVariables { | ||
| 8 | const stylesheet = NodeFS.readFileSync(new URL("../../global.css", import.meta.url), "utf8"); | ||
| 9 | const variant = new RegExp(`@variant ${appearance} \\{([\\s\\S]*?)\\n \\}`, "u").exec( | ||
| 10 | stylesheet, | ||
| 11 | )?.[1]; | ||
| 12 | if (variant === undefined) throw new Error(`Missing default ${appearance} theme in global.css.`); | ||
| 13 | |||
| 14 | return Object.fromEntries( | ||
| 15 | Array.from(variant.matchAll(/(--color-[a-z0-9-]+):\s*([^;]+);/gu), ([, name, value]) => [ | ||
| 16 | name, | ||
| 17 | value.trim(), | ||
| 18 | ]), | ||
| 19 | ) as MobileThemeVariables; | ||
| 20 | } | ||
apps/mobile/src/lib/mobileTheme.test.ts+13-29| ... | @@ -1,8 +1,6 @@ | ... | @@ -1,8 +1,6 @@ |
| 1 | import { describe, expect, it } from "vite-plus/test"; | 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | import * as NodeFS from "node:fs"; | ||
| 3 | |||
| 4 | import { BUILT_IN_THEME_IDS, BUILT_IN_THEMES } from "@t3tools/shared/themePalettes"; | 2 | import { BUILT_IN_THEME_IDS, BUILT_IN_THEMES } from "@t3tools/shared/themePalettes"; |
| 5 | import { DEFAULT_MOBILE_THEME_VARIABLES } from "./mobileDefaultTheme"; | 3 | import { readDefaultMobileThemeVariables } from "./mobileTheme.test-support"; |
| 6 | 4 | ||
| 7 | import { | 5 | import { |
| 8 | createMobileThemePairPatch, | 6 | createMobileThemePairPatch, |
| ... | @@ -11,7 +9,6 @@ import { | ... | @@ -11,7 +9,6 @@ import { |
| 11 | DEFAULT_MOBILE_THEME_ID, | 9 | DEFAULT_MOBILE_THEME_ID, |
| 12 | getMobileThemePreviewColors, | 10 | getMobileThemePreviewColors, |
| 13 | getMobileThemeVariables, | 11 | getMobileThemeVariables, |
| 14 | MOBILE_THEME_IDS, | ||
| 15 | normalizeMobileThemeId, | 12 | normalizeMobileThemeId, |
| 16 | normalizeMobileThemeMode, | 13 | normalizeMobileThemeMode, |
| 17 | resolveMobileThemeIds, | 14 | resolveMobileThemeIds, |
| ... | @@ -52,13 +49,12 @@ function compositeOver(overlay: string, background: string): string { | ... | @@ -52,13 +49,12 @@ function compositeOver(overlay: string, background: string): string { |
| 52 | 49 | ||
| 53 | describe("mobile themes", () => { | 50 | describe("mobile themes", () => { |
| 54 | it("declares every runtime theme variable in the static stylesheet", () => { | 51 | it("declares every runtime theme variable in the static stylesheet", () => { |
| 55 | const stylesheet = NodeFS.readFileSync(new URL("../../global.css", import.meta.url), "utf8"); | 52 | const generatedVariables = createMobileThemeVariables(BUILT_IN_THEMES[0].colors, "light"); |
| 56 | const stylesheetVariables = new Set( | 53 | expect(Object.keys(readDefaultMobileThemeVariables("light")).sort()).toEqual( |
| 57 | Array.from(stylesheet.matchAll(/--color-[a-z0-9-]+/g), ([variable]) => variable), | 54 | Object.keys(generatedVariables).sort(), |
| 58 | ); | 55 | ); |
| 59 | 56 | expect(Object.keys(readDefaultMobileThemeVariables("dark")).sort()).toEqual( | |
| 60 | expect(Array.from(stylesheetVariables).sort()).toEqual( | 57 | Object.keys(generatedVariables).sort(), |
| 61 | Object.keys(DEFAULT_MOBILE_THEME_VARIABLES.light).sort(), | ||
| 62 | ); | 58 | ); |
| 63 | }); | 59 | }); |
| 64 | 60 | ||
| ... | @@ -71,17 +67,11 @@ describe("mobile themes", () => { | ... | @@ -71,17 +67,11 @@ describe("mobile themes", () => { |
| 71 | }); | 67 | }); |
| 72 | 68 | ||
| 73 | it("preserves the existing mobile palette as the default", () => { | 69 | it("preserves the existing mobile palette as the default", () => { |
| 74 | expect(getMobileThemeVariables(DEFAULT_MOBILE_THEME_ID, "light")["--color-screen"]).toBe( | 70 | expect(readDefaultMobileThemeVariables("light")["--color-screen"]).toBe("#f2f2f7"); |
| 75 | "#f2f2f7", | 71 | expect(readDefaultMobileThemeVariables("dark")["--color-screen"]).toBe("#0a0a0a"); |
| 72 | expect(readDefaultMobileThemeVariables("light")["--color-user-bubble-skill-foreground"]).toBe( | ||
| 73 | "#f0abfc", | ||
| 76 | ); | 74 | ); |
| 77 | expect(getMobileThemeVariables(DEFAULT_MOBILE_THEME_ID, "dark")["--color-screen"]).toBe( | ||
| 78 | "#0a0a0a", | ||
| 79 | ); | ||
| 80 | expect( | ||
| 81 | getMobileThemeVariables(DEFAULT_MOBILE_THEME_ID, "light")[ | ||
| 82 | "--color-user-bubble-skill-foreground" | ||
| 83 | ], | ||
| 84 | ).toBe("#f0abfc"); | ||
| 85 | }); | 75 | }); |
| 86 | 76 | ||
| 87 | it("applies palette overrides on top of the selected built-in theme", () => { | 77 | it("applies palette overrides on top of the selected built-in theme", () => { |
| ... | @@ -172,17 +162,11 @@ describe("mobile themes", () => { | ... | @@ -172,17 +162,11 @@ describe("mobile themes", () => { |
| 172 | expect(variables["--color-backdrop"]).toBe("rgba(0, 0, 0, 0.22)"); | 162 | expect(variables["--color-backdrop"]).toBe("rgba(0, 0, 0, 0.22)"); |
| 173 | expect(variables["--color-drawer-shadow"]).toBe("rgba(0, 0, 0, 0.12)"); | 163 | expect(variables["--color-drawer-shadow"]).toBe("rgba(0, 0, 0, 0.12)"); |
| 174 | expect(variables["--color-user-bubble-foreground"]).toMatch(/^#/); | 164 | expect(variables["--color-user-bubble-foreground"]).toMatch(/^#/); |
| 175 | expect(Object.keys(DEFAULT_MOBILE_THEME_VARIABLES.light).sort()).toEqual( | ||
| 176 | Object.keys(variables).sort(), | ||
| 177 | ); | ||
| 178 | expect(Object.keys(DEFAULT_MOBILE_THEME_VARIABLES.dark).sort()).toEqual( | ||
| 179 | Object.keys(variables).sort(), | ||
| 180 | ); | ||
| 181 | }); | 165 | }); |
| 182 | 166 | ||
| 183 | it("keeps every built-in shadow and backdrop black-based in dark mode", () => { | 167 | it("keeps every built-in shadow and backdrop black-based in dark mode", () => { |
| 184 | for (const theme of BUILT_IN_THEMES) { | 168 | for (const themeId of BUILT_IN_THEME_IDS) { |
| 185 | const variables = getMobileThemeVariables(normalizeMobileThemeId(theme.id), "dark"); | 169 | const variables = getMobileThemeVariables(themeId, "dark"); |
| 186 | expect(variables["--color-primary-shadow"]).toBe("#000000"); | 170 | expect(variables["--color-primary-shadow"]).toBe("#000000"); |
| 187 | expect(variables["--color-backdrop"]).toBe("rgba(0, 0, 0, 0.48)"); | 171 | expect(variables["--color-backdrop"]).toBe("rgba(0, 0, 0, 0.48)"); |
| 188 | expect(variables["--color-drawer-shadow"]).toBe("rgba(0, 0, 0, 0.32)"); | 172 | expect(variables["--color-drawer-shadow"]).toBe("rgba(0, 0, 0, 0.32)"); |
| ... | @@ -190,7 +174,7 @@ describe("mobile themes", () => { | ... | @@ -190,7 +174,7 @@ describe("mobile themes", () => { |
| 190 | }); | 174 | }); |
| 191 | 175 | ||
| 192 | it("keeps placeholders and selected-row labels readable on their mobile surfaces", () => { | 176 | it("keeps placeholders and selected-row labels readable on their mobile surfaces", () => { |
| 193 | for (const themeId of MOBILE_THEME_IDS) { | 177 | for (const themeId of BUILT_IN_THEME_IDS) { |
| 194 | for (const appearance of ["light", "dark"] as const) { | 178 | for (const appearance of ["light", "dark"] as const) { |
| 195 | const variables = getMobileThemeVariables(themeId, appearance); | 179 | const variables = getMobileThemeVariables(themeId, appearance); |
| 196 | expect( | 180 | expect( |
apps/mobile/src/lib/mobileTheme.ts+10-10| ... | @@ -3,6 +3,7 @@ import { | ... | @@ -3,6 +3,7 @@ import { |
| 3 | getThemeColorsForAppearance, | 3 | getThemeColorsForAppearance, |
| 4 | MOBILE_DEFAULT_THEME_ID, | 4 | MOBILE_DEFAULT_THEME_ID, |
| 5 | MOBILE_THEME_IDS as SHARED_MOBILE_THEME_IDS, | 5 | MOBILE_THEME_IDS as SHARED_MOBILE_THEME_IDS, |
| 6 | type BuiltInThemeId, | ||
| 6 | type MobileThemeId as SharedMobileThemeId, | 7 | type MobileThemeId as SharedMobileThemeId, |
| 7 | type ThemeAppearance, | 8 | type ThemeAppearance, |
| 8 | type ThemeColors, | 9 | type ThemeColors, |
| ... | @@ -11,7 +12,6 @@ import { | ... | @@ -11,7 +12,6 @@ import { |
| 11 | STANDARD_THEME_PREVIEW_COLORS, | 12 | STANDARD_THEME_PREVIEW_COLORS, |
| 12 | type ThemePreviewColors, | 13 | type ThemePreviewColors, |
| 13 | } from "@t3tools/shared/themePreview"; | 14 | } from "@t3tools/shared/themePreview"; |
| 14 | import { DEFAULT_MOBILE_THEME_VARIABLES } from "./mobileDefaultTheme"; | ||
| 15 | 15 | ||
| 16 | export const DEFAULT_MOBILE_THEME_ID = MOBILE_DEFAULT_THEME_ID; | 16 | export const DEFAULT_MOBILE_THEME_ID = MOBILE_DEFAULT_THEME_ID; |
| 17 | export const MOBILE_THEME_IDS = SHARED_MOBILE_THEME_IDS; | 17 | export const MOBILE_THEME_IDS = SHARED_MOBILE_THEME_IDS; |
| ... | @@ -28,7 +28,7 @@ export const MOBILE_THEME_OPTIONS: ReadonlyArray<{ | ... | @@ -28,7 +28,7 @@ export const MOBILE_THEME_OPTIONS: ReadonlyArray<{ |
| 28 | ...BUILT_IN_THEMES.map((theme) => ({ id: theme.id as MobileThemeId, label: theme.label })), | 28 | ...BUILT_IN_THEMES.map((theme) => ({ id: theme.id as MobileThemeId, label: theme.label })), |
| 29 | ]; | 29 | ]; |
| 30 | 30 | ||
| 31 | type MobileThemeVariable = `--color-${string}`; | 31 | export type MobileThemeVariable = `--color-${string}`; |
| 32 | export type MobileThemeVariables = Readonly<Record<MobileThemeVariable, string>>; | 32 | export type MobileThemeVariables = Readonly<Record<MobileThemeVariable, string>>; |
| 33 | 33 | ||
| 34 | export function normalizeMobileThemeId(value: unknown): MobileThemeId { | 34 | export function normalizeMobileThemeId(value: unknown): MobileThemeId { |
| ... | @@ -282,18 +282,18 @@ export function createMobileThemeVariables( | ... | @@ -282,18 +282,18 @@ export function createMobileThemeVariables( |
| 282 | }; | 282 | }; |
| 283 | } | 283 | } |
| 284 | 284 | ||
| 285 | export const MOBILE_THEME_VARIABLE_NAMES = Object.keys( | ||
| 286 | createMobileThemeVariables(BUILT_IN_THEMES[0].colors, "light"), | ||
| 287 | ) as ReadonlyArray<MobileThemeVariable>; | ||
| 288 | |||
| 285 | export function getMobileThemeVariables( | 289 | export function getMobileThemeVariables( |
| 286 | themeId: MobileThemeId, | 290 | themeId: BuiltInThemeId, |
| 287 | appearance: MobileThemeAppearance, | 291 | appearance: MobileThemeAppearance, |
| 288 | overrides: Partial<MobileThemeVariables> | null = null, | 292 | overrides: Partial<MobileThemeVariables> | null = null, |
| 289 | ): MobileThemeVariables { | 293 | ): MobileThemeVariables { |
| 290 | const baseVariables = (() => { | 294 | const theme = BUILT_IN_THEMES.find((candidate) => candidate.id === themeId) ?? BUILT_IN_THEMES[0]; |
| 291 | if (themeId === DEFAULT_MOBILE_THEME_ID) return DEFAULT_MOBILE_THEME_VARIABLES[appearance]; | 295 | const colors = getThemeColorsForAppearance(theme, appearance) ?? theme.colors; |
| 292 | const theme = | 296 | const baseVariables = createMobileThemeVariables(colors, appearance); |
| 293 | BUILT_IN_THEMES.find((candidate) => candidate.id === themeId) ?? BUILT_IN_THEMES[0]; | ||
| 294 | const colors = getThemeColorsForAppearance(theme, appearance) ?? theme.colors; | ||
| 295 | return createMobileThemeVariables(colors, appearance); | ||
| 296 | })(); | ||
| 297 | 297 | ||
| 298 | // The complete base record guarantees that optional overrides cannot leave a token undefined. | 298 | // The complete base record guarantees that optional overrides cannot leave a token undefined. |
| 299 | return overrides ? ({ ...baseVariables, ...overrides } as MobileThemeVariables) : baseVariables; | 299 | return overrides ? ({ ...baseVariables, ...overrides } as MobileThemeVariables) : baseVariables; |
apps/mobile/src/lib/mobileThemeRuntime.test.ts created+82| ... | @@ -0,0 +1,82 @@ | ||
| 1 | import { describe, expect, it } from "vite-plus/test"; | ||
| 2 | |||
| 3 | import { | ||
| 4 | createMobileThemeRuntimeOperations, | ||
| 5 | getMobileUniwindThemeName, | ||
| 6 | type MobileThemeRuntimeState, | ||
| 7 | } from "./mobileThemeRuntime"; | ||
| 8 | |||
| 9 | const initialState: MobileThemeRuntimeState = { | ||
| 10 | baseFontSize: 16, | ||
| 11 | themeAppearance: "light", | ||
| 12 | themeMode: "system", | ||
| 13 | }; | ||
| 14 | |||
| 15 | describe("mobileThemeRuntime", () => { | ||
| 16 | it("keeps the default palette on Uniwind's built-in appearance themes", () => { | ||
| 17 | expect(getMobileUniwindThemeName("t3-code", "light")).toBe("light"); | ||
| 18 | expect(getMobileUniwindThemeName("t3-code", "dark")).toBe("dark"); | ||
| 19 | }); | ||
| 20 | |||
| 21 | it("maps custom palettes and appearances to registered themes", () => { | ||
| 22 | expect(getMobileUniwindThemeName("t3-chat", "dark")).toBe("t3-chat-dark"); | ||
| 23 | }); | ||
| 24 | |||
| 25 | it("hydrates text variables and clears the native appearance override", () => { | ||
| 26 | const operations = createMobileThemeRuntimeOperations(null, initialState); | ||
| 27 | const variableOperations = operations.filter( | ||
| 28 | (operation) => operation.kind === "update-text-variables", | ||
| 29 | ); | ||
| 30 | |||
| 31 | expect(variableOperations).toHaveLength(12); | ||
| 32 | expect(variableOperations.at(-1)?.themeName).toBe("iris-dark"); | ||
| 33 | expect(operations.at(-1)).toEqual({ | ||
| 34 | kind: "set-appearance-mode", | ||
| 35 | appearance: "light", | ||
| 36 | themeMode: "system", | ||
| 37 | }); | ||
| 38 | }); | ||
| 39 | |||
| 40 | it("lets system appearance changes flow through the root ScopedTheme only", () => { | ||
| 41 | const operations = createMobileThemeRuntimeOperations(initialState, { | ||
| 42 | ...initialState, | ||
| 43 | themeAppearance: "dark", | ||
| 44 | }); | ||
| 45 | |||
| 46 | expect(operations).toEqual([]); | ||
| 47 | }); | ||
| 48 | |||
| 49 | it("updates native appearance once when the selected mode changes", () => { | ||
| 50 | const operations = createMobileThemeRuntimeOperations(initialState, { | ||
| 51 | ...initialState, | ||
| 52 | themeAppearance: "dark", | ||
| 53 | themeMode: "dark", | ||
| 54 | }); | ||
| 55 | |||
| 56 | expect(operations).toEqual([ | ||
| 57 | { | ||
| 58 | kind: "set-appearance-mode", | ||
| 59 | appearance: "dark", | ||
| 60 | themeMode: "dark", | ||
| 61 | }, | ||
| 62 | ]); | ||
| 63 | }); | ||
| 64 | |||
| 65 | it("updates text variables for every theme without switching palettes", () => { | ||
| 66 | const operations = createMobileThemeRuntimeOperations(initialState, { | ||
| 67 | ...initialState, | ||
| 68 | baseFontSize: 18, | ||
| 69 | }); | ||
| 70 | |||
| 71 | expect(operations).toHaveLength(12); | ||
| 72 | expect(operations.every((operation) => operation.kind === "update-text-variables")).toBe(true); | ||
| 73 | expect(operations.at(-1)).toMatchObject({ | ||
| 74 | kind: "update-text-variables", | ||
| 75 | themeName: "iris-dark", | ||
| 76 | }); | ||
| 77 | }); | ||
| 78 | |||
| 79 | it("does no native work when persistence echoes an already-applied state", () => { | ||
| 80 | expect(createMobileThemeRuntimeOperations(initialState, initialState)).toEqual([]); | ||
| 81 | }); | ||
| 82 | }); | ||
apps/mobile/src/lib/mobileThemeRuntime.ts created+75| ... | @@ -0,0 +1,75 @@ | ||
| 1 | import { resolveTextScaleVariables } from "./appearancePreferences"; | ||
| 2 | import { BUILT_IN_THEME_IDS, type BuiltInThemeId } from "@t3tools/shared/themePalettes"; | ||
| 3 | import { | ||
| 4 | DEFAULT_MOBILE_THEME_ID, | ||
| 5 | type MobileThemeAppearance, | ||
| 6 | type MobileThemeId, | ||
| 7 | type MobileThemeMode, | ||
| 8 | } from "./mobileTheme"; | ||
| 9 | |||
| 10 | export type MobileUniwindThemeName = | ||
| 11 | | MobileThemeAppearance | ||
| 12 | | `${BuiltInThemeId}-${MobileThemeAppearance}`; | ||
| 13 | |||
| 14 | export interface MobileThemeRuntimeState { | ||
| 15 | readonly baseFontSize: number; | ||
| 16 | readonly themeAppearance: MobileThemeAppearance; | ||
| 17 | readonly themeMode: MobileThemeMode; | ||
| 18 | } | ||
| 19 | |||
| 20 | export type MobileThemeRuntimeOperation = | ||
| 21 | | { | ||
| 22 | readonly kind: "update-text-variables"; | ||
| 23 | readonly themeName: "light" | "dark" | MobileUniwindThemeName; | ||
| 24 | readonly variables: Readonly<Record<string, number>>; | ||
| 25 | } | ||
| 26 | | { | ||
| 27 | readonly kind: "set-appearance-mode"; | ||
| 28 | readonly appearance: MobileThemeAppearance; | ||
| 29 | readonly themeMode: MobileThemeMode; | ||
| 30 | }; | ||
| 31 | |||
| 32 | const UNIWIND_THEME_NAMES: ReadonlyArray<"light" | "dark" | MobileUniwindThemeName> = [ | ||
| 33 | "light", | ||
| 34 | "dark", | ||
| 35 | ...BUILT_IN_THEME_IDS.flatMap((themeId) => [ | ||
| 36 | `${themeId}-light` as const, | ||
| 37 | `${themeId}-dark` as const, | ||
| 38 | ]), | ||
| 39 | ]; | ||
| 40 | |||
| 41 | export function getMobileUniwindThemeName( | ||
| 42 | themeId: MobileThemeId, | ||
| 43 | appearance: MobileThemeAppearance, | ||
| 44 | ): MobileUniwindThemeName { | ||
| 45 | return themeId === DEFAULT_MOBILE_THEME_ID ? appearance : `${themeId}-${appearance}`; | ||
| 46 | } | ||
| 47 | |||
| 48 | /** | ||
| 49 | * Plans imperative runtime work separately from theme selection. Palette | ||
| 50 | * changes are handled by one root ScopedTheme render; only typography and the | ||
| 51 | * native appearance override need imperative Uniwind/React Native updates. | ||
| 52 | */ | ||
| 53 | export function createMobileThemeRuntimeOperations( | ||
| 54 | previous: MobileThemeRuntimeState | null, | ||
| 55 | next: MobileThemeRuntimeState, | ||
| 56 | ): ReadonlyArray<MobileThemeRuntimeOperation> { | ||
| 57 | const operations: MobileThemeRuntimeOperation[] = []; | ||
| 58 | |||
| 59 | if (previous === null || previous.baseFontSize !== next.baseFontSize) { | ||
| 60 | const variables = resolveTextScaleVariables(next.baseFontSize); | ||
| 61 | for (const themeName of UNIWIND_THEME_NAMES) { | ||
| 62 | operations.push({ kind: "update-text-variables", themeName, variables }); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | if (previous === null || previous.themeMode !== next.themeMode) { | ||
| 67 | operations.push({ | ||
| 68 | kind: "set-appearance-mode", | ||
| 69 | appearance: next.themeAppearance, | ||
| 70 | themeMode: next.themeMode, | ||
| 71 | }); | ||
| 72 | } | ||
| 73 | |||
| 74 | return operations; | ||
| 75 | } | ||
apps/mobile/src/lib/mobileThemeVariables.test.ts created+25| ... | @@ -0,0 +1,25 @@ | ||
| 1 | import { describe, expect, it } from "vite-plus/test"; | ||
| 2 | |||
| 3 | import { readDefaultMobileThemeVariables } from "./mobileTheme.test-support"; | ||
| 4 | import { getMobileThemeVariables } from "./mobileTheme"; | ||
| 5 | import { getMobileThemeRuntimeVariables } from "./mobileThemeVariables"; | ||
| 6 | |||
| 7 | describe("mobile theme runtime variables", () => { | ||
| 8 | it("derives the standard runtime palette from global.css", () => { | ||
| 9 | expect(getMobileThemeRuntimeVariables("t3-code", "light")).toEqual( | ||
| 10 | readDefaultMobileThemeVariables("light"), | ||
| 11 | ); | ||
| 12 | expect(getMobileThemeRuntimeVariables("t3-code", "dark")).toEqual( | ||
| 13 | readDefaultMobileThemeVariables("dark"), | ||
| 14 | ); | ||
| 15 | }); | ||
| 16 | |||
| 17 | it("uses the same shared palette source as generated custom themes", () => { | ||
| 18 | expect(getMobileThemeRuntimeVariables("ocean", "light")).toEqual( | ||
| 19 | getMobileThemeVariables("ocean", "light"), | ||
| 20 | ); | ||
| 21 | expect(getMobileThemeRuntimeVariables("iris", "dark")).toEqual( | ||
| 22 | getMobileThemeVariables("iris", "dark"), | ||
| 23 | ); | ||
| 24 | }); | ||
| 25 | }); | ||
apps/mobile/src/lib/mobileThemeVariables.ts created+27| ... | @@ -0,0 +1,27 @@ | ||
| 1 | import defaultThemeVariables from "../../generated-uniwind-default-theme-variables.json"; | ||
| 2 | |||
| 3 | import { | ||
| 4 | DEFAULT_MOBILE_THEME_ID, | ||
| 5 | getMobileThemeVariables, | ||
| 6 | type MobileThemeAppearance, | ||
| 7 | type MobileThemeId, | ||
| 8 | type MobileThemeVariables, | ||
| 9 | } from "./mobileTheme"; | ||
| 10 | |||
| 11 | const defaults = defaultThemeVariables as Readonly< | ||
| 12 | Record<MobileThemeAppearance, MobileThemeVariables> | ||
| 13 | >; | ||
| 14 | |||
| 15 | /** | ||
| 16 | * Complete palette for native and third-party APIs that cannot consume a | ||
| 17 | * Uniwind className. The standard palette is generated from global.css; custom | ||
| 18 | * palettes share the same source that generates their registered CSS themes. | ||
| 19 | */ | ||
| 20 | export function getMobileThemeRuntimeVariables( | ||
| 21 | themeId: MobileThemeId, | ||
| 22 | appearance: MobileThemeAppearance, | ||
| 23 | ): MobileThemeVariables { | ||
| 24 | return themeId === DEFAULT_MOBILE_THEME_ID | ||
| 25 | ? defaults[appearance] | ||
| 26 | : getMobileThemeVariables(themeId, appearance); | ||
| 27 | } | ||
apps/mobile/src/lib/storage.test.ts+6| ... | @@ -196,6 +196,12 @@ describe("mobile connection storage", () => { | ... | @@ -196,6 +196,12 @@ describe("mobile connection storage", () => { |
| 196 | }); | 196 | }); |
| 197 | }); | 197 | }); |
| 198 | 198 | ||
| 199 | it("drops the removed theme transition preference", async () => { | ||
| 200 | mocks.setPreferencesJson(JSON.stringify({ themeTransition: "circle-bottom-left" }), 10); | ||
| 201 | |||
| 202 | await expect(loadPreferences()).resolves.toEqual({}); | ||
| 203 | }); | ||
| 204 | |||
| 199 | it("falls back to secure storage when SQLite cannot save preferences", async () => { | 205 | it("falls back to secure storage when SQLite cannot save preferences", async () => { |
| 200 | mocks.setDatabaseFailures(true, true); | 206 | mocks.setDatabaseFailures(true, true); |
| 201 | await expect(savePreferencesPatch({ baseFontSize: 19 })).resolves.toEqual({ baseFontSize: 19 }); | 207 | await expect(savePreferencesPatch({ baseFontSize: 19 })).resolves.toEqual({ baseFontSize: 19 }); |
apps/mobile/src/lib/useFontFamily.ts+6-8| ... | @@ -1,15 +1,13 @@ | ... | @@ -1,15 +1,13 @@ |
| 1 | import { useCSSVariable } from "uniwind"; | 1 | const FONT_FAMILIES = { |
| 2 | 2 | regular: "DMSans-Regular", | |
| 3 | const FONT_FAMILY_VARIABLES = { | 3 | medium: "DMSans-Medium", |
| 4 | regular: "--font-sans", | 4 | bold: "DMSans-Bold", |
| 5 | medium: "--font-medium", | ||
| 6 | bold: "--font-bold", | ||
| 7 | } as const; | 5 | } as const; |
| 8 | 6 | ||
| 9 | /** | 7 | /** |
| 10 | * Resolves a font family for APIs that require a style object or native prop. | 8 | * Resolves a font family for APIs that require a style object or native prop. |
| 11 | * Prefer Uniwind font classes when the target component accepts `className`. | 9 | * Prefer Uniwind font classes when the target component accepts `className`. |
| 12 | */ | 10 | */ |
| 13 | export function useFontFamily(weight: keyof typeof FONT_FAMILY_VARIABLES): string { | 11 | export function useFontFamily(weight: keyof typeof FONT_FAMILIES): string { |
| 14 | return useCSSVariable(FONT_FAMILY_VARIABLES[weight]) as string; | 12 | return FONT_FAMILIES[weight]; |
| 15 | } | 13 | } |
apps/mobile/src/lib/useMobileNavigationTheme.ts+21-12| ... | @@ -1,22 +1,31 @@ | ... | @@ -1,22 +1,31 @@ |
| 1 | import { DarkTheme, DefaultTheme, type Theme } from "@react-navigation/native"; | 1 | import { DarkTheme, DefaultTheme, type Theme } from "@react-navigation/native"; |
| 2 | import { useMemo } from "react"; | 2 | import { useMemo } from "react"; |
| 3 | 3 | ||
| 4 | import type { MobileThemeAppearance } from "./mobileTheme"; | 4 | import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; |
| 5 | import { useThemeColor } from "./useThemeColor"; | 5 | import { useUniwindTheme } from "./useUniwindTheme"; |
| 6 | |||
| 7 | export function useMobileNavigationTheme(appearance: MobileThemeAppearance): Theme { | ||
| 8 | const primary = String(useThemeColor("--color-primary")); | ||
| 9 | const background = String(useThemeColor("--color-screen")); | ||
| 10 | const card = String(useThemeColor("--color-sheet-solid")); | ||
| 11 | const text = String(useThemeColor("--color-foreground")); | ||
| 12 | const border = String(useThemeColor("--color-header-border")); | ||
| 13 | const notification = String(useThemeColor("--color-danger-foreground")); | ||
| 14 | 6 | ||
| 7 | /** | ||
| 8 | * React Navigation requires a JS theme object. Derive it from the same palette | ||
| 9 | * source as Uniwind instead of subscribing the app root to CSS variables. The | ||
| 10 | * preferences provider applies the registered Uniwind theme first, then | ||
| 11 | * publishes this matching navigation palette through React. | ||
| 12 | */ | ||
| 13 | export function useMobileNavigationTheme(): Theme { | ||
| 14 | const { themeAppearance: appearance } = useAppearancePreferences(); | ||
| 15 | const variables = useUniwindTheme(); | ||
| 15 | return useMemo(() => { | 16 | return useMemo(() => { |
| 16 | const base = appearance === "dark" ? DarkTheme : DefaultTheme; | 17 | const base = appearance === "dark" ? DarkTheme : DefaultTheme; |
| 17 | return { | 18 | return { |
| 18 | ...base, | 19 | ...base, |
| 19 | colors: { ...base.colors, primary, background, card, text, border, notification }, | 20 | colors: { |
| 21 | ...base.colors, | ||
| 22 | primary: variables["--color-primary"], | ||
| 23 | background: variables["--color-screen"], | ||
| 24 | card: variables["--color-sheet-solid"], | ||
| 25 | text: variables["--color-foreground"], | ||
| 26 | border: variables["--color-header-border"], | ||
| 27 | notification: variables["--color-danger-foreground"], | ||
| 28 | }, | ||
| 20 | }; | 29 | }; |
| 21 | }, [appearance, background, border, card, notification, primary, text]); | 30 | }, [appearance, variables]); |
| 22 | } | 31 | } |
apps/mobile/src/lib/useThemeColor.ts deleted-12| ... | @@ -1,12 +0,0 @@ | ||
| 1 | import type { ColorValue } from "react-native"; | ||
| 2 | import { useCSSVariable } from "uniwind"; | ||
| 3 | |||
| 4 | /** | ||
| 5 | * Typed wrapper around `useCSSVariable` that returns a `ColorValue` for use | ||
| 6 | * in React Native style props (backgroundColor, tintColor, etc.). | ||
| 7 | * | ||
| 8 | * Usage: `const color = useThemeColor("--color-icon");` | ||
| 9 | */ | ||
| 10 | export function useThemeColor(variable: `--color-${string}`): ColorValue { | ||
| 11 | return useCSSVariable(variable) as string as ColorValue; | ||
| 12 | } | ||
apps/mobile/src/lib/useUniwindTheme.ts created+21| ... | @@ -0,0 +1,21 @@ | ||
| 1 | import { useMemo } from "react"; | ||
| 2 | |||
| 3 | import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; | ||
| 4 | import type { MobileThemeVariables } from "./mobileTheme"; | ||
| 5 | import { getMobileThemeRuntimeVariables } from "./mobileThemeVariables"; | ||
| 6 | |||
| 7 | /** | ||
| 8 | * Complete JS palette for native and third-party APIs that cannot consume a | ||
| 9 | * Uniwind className (React Navigation, native editors, Markdown, SVG gradients, | ||
| 10 | * Reanimated worklets). Ordinary React Native rendering must use className. | ||
| 11 | * | ||
| 12 | * This bridge follows the same single React theme commit as the root | ||
| 13 | * ScopedTheme instead of subscribing every consumer to CSS-variable updates. | ||
| 14 | */ | ||
| 15 | export function useUniwindTheme(): MobileThemeVariables { | ||
| 16 | const { themeAppearance, themeId } = useAppearancePreferences(); | ||
| 17 | return useMemo( | ||
| 18 | () => getMobileThemeRuntimeVariables(themeId, themeAppearance), | ||
| 19 | [themeAppearance, themeId], | ||
| 20 | ); | ||
| 21 | } | ||
apps/mobile/src/native/T3ComposerEditor.ios.tsx+11-19| ... | @@ -14,7 +14,7 @@ import { Image, StyleSheet } from "react-native"; | ... | @@ -14,7 +14,7 @@ import { Image, StyleSheet } from "react-native"; |
| 14 | 14 | ||
| 15 | import { markdownFileIconSource } from "@t3tools/mobile-markdown-text/file-icons"; | 15 | import { markdownFileIconSource } from "@t3tools/mobile-markdown-text/file-icons"; |
| 16 | import { resolveMarkdownFileIcon } from "@t3tools/mobile-markdown-text/links"; | 16 | import { resolveMarkdownFileIcon } from "@t3tools/mobile-markdown-text/links"; |
| 17 | import { useThemeColor } from "../lib/useThemeColor"; | 17 | import { useUniwindTheme } from "../lib/useUniwindTheme"; |
| 18 | import { useFontFamily } from "../lib/useFontFamily"; | 18 | import { useFontFamily } from "../lib/useFontFamily"; |
| 19 | import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole"; | 19 | import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole"; |
| 20 | import { | 20 | import { |
| ... | @@ -110,15 +110,7 @@ export function ComposerEditor({ | ... | @@ -110,15 +110,7 @@ export function ComposerEditor({ |
| 110 | const nativeEventSnapshotsRef = useRef<ComposerNativeEventSnapshot[]>([]); | 110 | const nativeEventSnapshotsRef = useRef<ComposerNativeEventSnapshot[]>([]); |
| 111 | const confirmedTokensRef = useRef(collectComposerInlineTokens(props.value)); | 111 | const confirmedTokensRef = useRef(collectComposerInlineTokens(props.value)); |
| 112 | const bodyText = useScaledTextRole("body"); | 112 | const bodyText = useScaledTextRole("body"); |
| 113 | const textColor = useThemeColor("--color-foreground"); | 113 | const theme = useUniwindTheme(); |
| 114 | const placeholderColor = useThemeColor("--color-placeholder"); | ||
| 115 | const chipBackground = useThemeColor("--color-subtle"); | ||
| 116 | const chipBorder = useThemeColor("--color-border"); | ||
| 117 | const chipText = useThemeColor("--color-foreground"); | ||
| 118 | const skillBackground = useThemeColor("--color-inline-skill-background"); | ||
| 119 | const skillBorder = useThemeColor("--color-inline-skill-border"); | ||
| 120 | const skillText = useThemeColor("--color-inline-skill-foreground"); | ||
| 121 | const fileTint = useThemeColor("--color-icon-muted"); | ||
| 122 | const fontFamily = useFontFamily("regular"); | 114 | const fontFamily = useFontFamily("regular"); |
| 123 | 115 | ||
| 124 | useImperativeHandle( | 116 | useImperativeHandle( |
| ... | @@ -219,15 +211,15 @@ export function ComposerEditor({ | ... | @@ -219,15 +211,15 @@ export function ComposerEditor({ |
| 219 | [], | 211 | [], |
| 220 | ); | 212 | ); |
| 221 | const themeJson = JSON.stringify({ | 213 | const themeJson = JSON.stringify({ |
| 222 | text: String(textColor), | 214 | text: theme["--color-foreground"], |
| 223 | placeholder: String(placeholderColor), | 215 | placeholder: theme["--color-placeholder"], |
| 224 | chipBackground: String(chipBackground), | 216 | chipBackground: theme["--color-subtle"], |
| 225 | chipBorder: String(chipBorder), | 217 | chipBorder: theme["--color-border"], |
| 226 | chipText: String(chipText), | 218 | chipText: theme["--color-foreground"], |
| 227 | skillBackground: String(skillBackground), | 219 | skillBackground: theme["--color-inline-skill-background"], |
| 228 | skillBorder: String(skillBorder), | 220 | skillBorder: theme["--color-inline-skill-border"], |
| 229 | skillText: String(skillText), | 221 | skillText: theme["--color-inline-skill-foreground"], |
| 230 | fileTint: String(fileTint), | 222 | fileTint: theme["--color-icon-muted"], |
| 231 | }); | 223 | }); |
| 232 | const resolvedTextStyle = StyleSheet.flatten(textStyle) ?? {}; | 224 | const resolvedTextStyle = StyleSheet.flatten(textStyle) ?? {}; |
| 233 | return ( | 225 | return ( |
apps/mobile/src/native/T3ComposerEditor.native.tsx+11-19| ... | @@ -18,7 +18,7 @@ import { resolveMarkdownFileIcon } from "@t3tools/mobile-markdown-text/links"; | ... | @@ -18,7 +18,7 @@ import { resolveMarkdownFileIcon } from "@t3tools/mobile-markdown-text/links"; |
| 18 | import { MOBILE_TYPOGRAPHY } from "../lib/typography"; | 18 | import { MOBILE_TYPOGRAPHY } from "../lib/typography"; |
| 19 | import { useNativePaste } from "../lib/useNativePaste"; | 19 | import { useNativePaste } from "../lib/useNativePaste"; |
| 20 | import { useFontFamily } from "../lib/useFontFamily"; | 20 | import { useFontFamily } from "../lib/useFontFamily"; |
| 21 | import { useThemeColor } from "../lib/useThemeColor"; | 21 | import { useUniwindTheme } from "../lib/useUniwindTheme"; |
| 22 | import { | 22 | import { |
| 23 | acknowledgeComposerNativeEvent, | 23 | acknowledgeComposerNativeEvent, |
| 24 | assumeComposerControlledState, | 24 | assumeComposerControlledState, |
| ... | @@ -111,15 +111,7 @@ export function ComposerEditor({ | ... | @@ -111,15 +111,7 @@ export function ComposerEditor({ |
| 111 | const nativeEventSnapshotsRef = useRef<ComposerNativeEventSnapshot[]>([]); | 111 | const nativeEventSnapshotsRef = useRef<ComposerNativeEventSnapshot[]>([]); |
| 112 | const [initialConfirmedTokens] = useState(() => collectComposerInlineTokens(props.value)); | 112 | const [initialConfirmedTokens] = useState(() => collectComposerInlineTokens(props.value)); |
| 113 | const confirmedTokensRef = useRef(initialConfirmedTokens); | 113 | const confirmedTokensRef = useRef(initialConfirmedTokens); |
| 114 | const textColor = useThemeColor("--color-foreground"); | 114 | const theme = useUniwindTheme(); |
| 115 | const placeholderColor = useThemeColor("--color-placeholder"); | ||
| 116 | const chipBackground = useThemeColor("--color-subtle"); | ||
| 117 | const chipBorder = useThemeColor("--color-border"); | ||
| 118 | const chipText = useThemeColor("--color-foreground"); | ||
| 119 | const skillBackground = useThemeColor("--color-inline-skill-background"); | ||
| 120 | const skillBorder = useThemeColor("--color-inline-skill-border"); | ||
| 121 | const skillText = useThemeColor("--color-inline-skill-foreground"); | ||
| 122 | const fileTint = useThemeColor("--color-icon-muted"); | ||
| 123 | const handlePaste = useNativePaste((uris) => onPasteImages?.(uris)); | 115 | const handlePaste = useNativePaste((uris) => onPasteImages?.(uris)); |
| 124 | 116 | ||
| 125 | useImperativeHandle( | 117 | useImperativeHandle( |
| ... | @@ -220,15 +212,15 @@ export function ComposerEditor({ | ... | @@ -220,15 +212,15 @@ export function ComposerEditor({ |
| 220 | [], | 212 | [], |
| 221 | ); | 213 | ); |
| 222 | const themeJson = JSON.stringify({ | 214 | const themeJson = JSON.stringify({ |
| 223 | text: String(textColor), | 215 | text: theme["--color-foreground"], |
| 224 | placeholder: String(placeholderColor), | 216 | placeholder: theme["--color-placeholder"], |
| 225 | chipBackground: String(chipBackground), | 217 | chipBackground: theme["--color-subtle"], |
| 226 | chipBorder: String(chipBorder), | 218 | chipBorder: theme["--color-border"], |
| 227 | chipText: String(chipText), | 219 | chipText: theme["--color-foreground"], |
| 228 | skillBackground: String(skillBackground), | 220 | skillBackground: theme["--color-inline-skill-background"], |
| 229 | skillBorder: String(skillBorder), | 221 | skillBorder: theme["--color-inline-skill-border"], |
| 230 | skillText: String(skillText), | 222 | skillText: theme["--color-inline-skill-foreground"], |
| 231 | fileTint: String(fileTint), | 223 | fileTint: theme["--color-icon-muted"], |
| 232 | }); | 224 | }); |
| 233 | const resolvedTextStyle = StyleSheet.flatten(textStyle) ?? {}; | 225 | const resolvedTextStyle = StyleSheet.flatten(textStyle) ?? {}; |
| 234 | const regularFontFamily = useFontFamily("regular"); | 226 | const regularFontFamily = useFontFamily("regular"); |
apps/mobile/src/native/T3ComposerEditor.tsx+2-5| ... | @@ -2,7 +2,6 @@ import { TextInputWrapper } from "expo-paste-input"; | ... | @@ -2,7 +2,6 @@ import { TextInputWrapper } from "expo-paste-input"; |
| 2 | import { useImperativeHandle, useRef } from "react"; | 2 | import { useImperativeHandle, useRef } from "react"; |
| 3 | import { TextInput, type TextInput as RNTextInput } from "react-native"; | 3 | import { TextInput, type TextInput as RNTextInput } from "react-native"; |
| 4 | 4 | ||
| 5 | import { useThemeColor } from "../lib/useThemeColor"; | ||
| 6 | import { useFontFamily } from "../lib/useFontFamily"; | 5 | import { useFontFamily } from "../lib/useFontFamily"; |
| 7 | import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole"; | 6 | import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole"; |
| 8 | import { useNativePaste } from "../lib/useNativePaste"; | 7 | import { useNativePaste } from "../lib/useNativePaste"; |
| ... | @@ -21,8 +20,6 @@ export function ComposerEditor({ | ... | @@ -21,8 +20,6 @@ export function ComposerEditor({ |
| 21 | }: ComposerEditorProps) { | 20 | }: ComposerEditorProps) { |
| 22 | const inputRef = useRef<RNTextInput>(null); | 21 | const inputRef = useRef<RNTextInput>(null); |
| 23 | const bodyText = useScaledTextRole("body"); | 22 | const bodyText = useScaledTextRole("body"); |
| 24 | const foregroundColor = useThemeColor("--color-foreground"); | ||
| 25 | const placeholderColor = useThemeColor("--color-placeholder"); | ||
| 26 | const fontFamily = useFontFamily("regular"); | 23 | const fontFamily = useFontFamily("regular"); |
| 27 | const handlePaste = useNativePaste((uris) => onPasteImages?.(uris)); | 24 | const handlePaste = useNativePaste((uris) => onPasteImages?.(uris)); |
| 28 | 25 | ||
| ... | @@ -45,12 +42,12 @@ export function ComposerEditor({ | ... | @@ -45,12 +42,12 @@ export function ComposerEditor({ |
| 45 | selection={selection} | 42 | selection={selection} |
| 46 | onSelectionChange={(event) => props.onSelectionChange?.(event.nativeEvent.selection)} | 43 | onSelectionChange={(event) => props.onSelectionChange?.(event.nativeEvent.selection)} |
| 47 | multiline={props.multiline ?? true} | 44 | multiline={props.multiline ?? true} |
| 48 | placeholderTextColor={placeholderColor} | 45 | placeholderTextColorClassName={"accent-placeholder"} |
| 46 | className="text-foreground" | ||
| 49 | style={[ | 47 | style={[ |
| 50 | { | 48 | { |
| 51 | flex: 1, | 49 | flex: 1, |
| 52 | minHeight: 0, | 50 | minHeight: 0, |
| 53 | color: foregroundColor, | ||
| 54 | fontFamily, | 51 | fontFamily, |
| 55 | ...bodyText, | 52 | ...bodyText, |
| 56 | paddingVertical: contentInsetVertical, | 53 | paddingVertical: contentInsetVertical, |
apps/mobile/src/state/thread-pr-presentation.ts+3-3| ... | @@ -17,9 +17,9 @@ export interface ThreadPrPresentation { | ... | @@ -17,9 +17,9 @@ export interface ThreadPrPresentation { |
| 17 | } | 17 | } |
| 18 | 18 | ||
| 19 | const PR_STATE_TEXT_CLASS: Record<ThreadPr["state"], string> = { | 19 | const PR_STATE_TEXT_CLASS: Record<ThreadPr["state"], string> = { |
| 20 | open: "text-emerald-600 dark:text-emerald-400", | 20 | open: "text-adaptive-emerald-600-400", |
| 21 | merged: "text-violet-600 dark:text-violet-400", | 21 | merged: "text-adaptive-violet-600-400", |
| 22 | closed: "text-zinc-500 dark:text-zinc-400", | 22 | closed: "text-adaptive-zinc-500-400", |
| 23 | }; | 23 | }; |
| 24 | 24 | ||
| 25 | export function presentThreadPr( | 25 | export function presentThreadPr( |
apps/mobile/src/state/use-thread-pr.test.ts+1-1| ... | @@ -17,7 +17,7 @@ describe("presentThreadPr", () => { | ... | @@ -17,7 +17,7 @@ describe("presentThreadPr", () => { |
| 17 | expect(presentThreadPr(pullRequest, undefined)).toMatchObject({ | 17 | expect(presentThreadPr(pullRequest, undefined)).toMatchObject({ |
| 18 | label: "3774", | 18 | label: "3774", |
| 19 | accessibilityLabel: "#3774 pull request merged", | 19 | accessibilityLabel: "#3774 pull request merged", |
| 20 | textClassName: "text-violet-600 dark:text-violet-400", | 20 | textClassName: "text-adaptive-violet-600-400", |
| 21 | }); | 21 | }); |
| 22 | }); | 22 | }); |
| 23 | 23 |
apps/mobile/uniwind-types.d.ts+1-1| ... | @@ -3,7 +3,7 @@ | ... | @@ -3,7 +3,7 @@ |
| 3 | 3 | ||
| 4 | declare module 'uniwind' { | 4 | declare module 'uniwind' { |
| 5 | export interface UniwindConfig { | 5 | export interface UniwindConfig { |
| 6 | themes: readonly ['light', 'dark'] | 6 | themes: readonly ['light', 'dark', 't3-chat-light', 't3-chat-dark', 'grove-light', 'grove-dark', 'ocean-light', 'ocean-dark', 'ember-light', 'ember-dark', 'iris-light', 'iris-dark'] |
| 7 | } | 7 | } |
| 8 | } | 8 | } |
| 9 | 9 |
docs/user/mobile-appearance.md+3| ... | @@ -11,5 +11,8 @@ To change themes: | ... | @@ -11,5 +11,8 @@ To change themes: |
| 11 | 3. Choose a theme. | 11 | 3. Choose a theme. |
| 12 | 4. Select **System**, **Light**, or **Dark**. | 12 | 4. Select **System**, **Light**, or **Dark**. |
| 13 | 13 | ||
| 14 | Tap a theme card to use it for both light and dark appearance. To mix themes, tap the light or dark | ||
| 15 | preview circle inside a card to change only that appearance. | ||
| 16 | |||
| 14 | **System** follows the device appearance automatically. Theme, text, code, and terminal appearance | 17 | **System** follows the device appearance automatically. Theme, text, code, and terminal appearance |
| 15 | preferences are stored on the device. | 18 | preferences are stored on the device. |
oxlint-plugin-t3code/index.ts+2| ... | @@ -4,6 +4,7 @@ import namespaceNodeImports from "./rules/namespace-node-imports.ts"; | ... | @@ -4,6 +4,7 @@ import namespaceNodeImports from "./rules/namespace-node-imports.ts"; |
| 4 | import noGlobalProcessRuntime from "./rules/no-global-process-runtime.ts"; | 4 | import noGlobalProcessRuntime from "./rules/no-global-process-runtime.ts"; |
| 5 | import noInlineSchemaCompile from "./rules/no-inline-schema-compile.ts"; | 5 | import noInlineSchemaCompile from "./rules/no-inline-schema-compile.ts"; |
| 6 | import noManualEffectRuntimeInTests from "./rules/no-manual-effect-runtime-in-tests.ts"; | 6 | import noManualEffectRuntimeInTests from "./rules/no-manual-effect-runtime-in-tests.ts"; |
| 7 | import noMobileUniwindThemeEscapeHatches from "./rules/no-mobile-uniwind-theme-escape-hatches.ts"; | ||
| 7 | import noNativeTitleTooltip from "./rules/no-native-title-tooltip.ts"; | 8 | import noNativeTitleTooltip from "./rules/no-native-title-tooltip.ts"; |
| 8 | 9 | ||
| 9 | export default definePlugin({ | 10 | export default definePlugin({ |
| ... | @@ -15,6 +16,7 @@ export default definePlugin({ | ... | @@ -15,6 +16,7 @@ export default definePlugin({ |
| 15 | "no-global-process-runtime": noGlobalProcessRuntime, | 16 | "no-global-process-runtime": noGlobalProcessRuntime, |
| 16 | "no-inline-schema-compile": noInlineSchemaCompile, | 17 | "no-inline-schema-compile": noInlineSchemaCompile, |
| 17 | "no-manual-effect-runtime-in-tests": noManualEffectRuntimeInTests, | 18 | "no-manual-effect-runtime-in-tests": noManualEffectRuntimeInTests, |
| 19 | "no-mobile-uniwind-theme-escape-hatches": noMobileUniwindThemeEscapeHatches, | ||
| 18 | "no-native-title-tooltip": noNativeTitleTooltip, | 20 | "no-native-title-tooltip": noNativeTitleTooltip, |
| 19 | }, | 21 | }, |
| 20 | }); | 22 | }); |
oxlint-plugin-t3code/rules/no-mobile-uniwind-theme-escape-hatches.test.ts created+264| ... | @@ -0,0 +1,264 @@ | ||
| 1 | import { assert, describe, expect } from "@effect/vitest"; | ||
| 2 | |||
| 3 | import { createOxlintRuleHarness } from "../test/utils.ts"; | ||
| 4 | |||
| 5 | const guardedMobileFile = createOxlintRuleHarness("t3code/no-mobile-uniwind-theme-escape-hatches", { | ||
| 6 | filename: "apps/mobile/src/features/settings/NewThemeSurface.tsx", | ||
| 7 | }); | ||
| 8 | const reviewedInteropFile = createOxlintRuleHarness( | ||
| 9 | "t3code/no-mobile-uniwind-theme-escape-hatches", | ||
| 10 | { filename: "apps/mobile/src/features/home/HomeHeader.tsx" }, | ||
| 11 | ); | ||
| 12 | const gitOverlayInteropFile = createOxlintRuleHarness( | ||
| 13 | "t3code/no-mobile-uniwind-theme-escape-hatches", | ||
| 14 | { filename: "apps/mobile/src/features/threads/GitActionProgressOverlay.tsx" }, | ||
| 15 | ); | ||
| 16 | const webFile = createOxlintRuleHarness("t3code/no-mobile-uniwind-theme-escape-hatches", { | ||
| 17 | filename: "apps/web/src/ThemeSurface.tsx", | ||
| 18 | }); | ||
| 19 | |||
| 20 | describe("t3code/no-mobile-uniwind-theme-escape-hatches", () => { | ||
| 21 | guardedMobileFile.valid( | ||
| 22 | "allows semantic mobile theme classes", | ||
| 23 | `const surface = <View className="bg-surface text-foreground" />;`, | ||
| 24 | ); | ||
| 25 | |||
| 26 | guardedMobileFile.valid( | ||
| 27 | "allows unrelated functions with legacy hook names", | ||
| 28 | ` | ||
| 29 | const useCSSVariable = () => "local value"; | ||
| 30 | const useThemeColor = () => "local color"; | ||
| 31 | |||
| 32 | export const values = [useCSSVariable(), useThemeColor()]; | ||
| 33 | `, | ||
| 34 | ); | ||
| 35 | |||
| 36 | guardedMobileFile.valid( | ||
| 37 | "allows unrelated imports with legacy hook names", | ||
| 38 | ` | ||
| 39 | import { useThemeColor } from "./unrelated-library"; | ||
| 40 | |||
| 41 | export const foreground = useThemeColor(); | ||
| 42 | `, | ||
| 43 | ); | ||
| 44 | |||
| 45 | guardedMobileFile.valid( | ||
| 46 | "allows erased type-only theme imports", | ||
| 47 | ` | ||
| 48 | import type { useCSSVariable } from "uniwind"; | ||
| 49 | import { type useCSSVariable as CSSVariableHook } from "uniwind"; | ||
| 50 | import type { useThemeColor } from "../../../hooks/useThemeColor"; | ||
| 51 | import type { useUniwindTheme } from "../../../lib/useUniwindTheme"; | ||
| 52 | |||
| 53 | export type ThemeHooks = [ | ||
| 54 | typeof useCSSVariable, | ||
| 55 | typeof CSSVariableHook, | ||
| 56 | typeof useThemeColor, | ||
| 57 | typeof useUniwindTheme, | ||
| 58 | ]; | ||
| 59 | `, | ||
| 60 | ); | ||
| 61 | |||
| 62 | guardedMobileFile.valid( | ||
| 63 | "allows appearance words in ordinary copy", | ||
| 64 | ` | ||
| 65 | const message = "Set dark mode manually"; | ||
| 66 | const detail = "For dark: high contrast mode is available"; | ||
| 67 | const label = <Text accessibilityLabel="Light theme">Theme</Text>; | ||
| 68 | `, | ||
| 69 | ); | ||
| 70 | |||
| 71 | guardedMobileFile.valid( | ||
| 72 | "does not conflate shadowed Uniwind namespaces", | ||
| 73 | ` | ||
| 74 | import * as Uniwind from "uniwind"; | ||
| 75 | |||
| 76 | export function readDirect(Uniwind: { useCSSVariable: () => string }) { | ||
| 77 | return Uniwind.useCSSVariable(); | ||
| 78 | } | ||
| 79 | |||
| 80 | export function readDestructured(Uniwind: { useCSSVariable: () => string }) { | ||
| 81 | const { useCSSVariable } = Uniwind; | ||
| 82 | return useCSSVariable(); | ||
| 83 | } | ||
| 84 | `, | ||
| 85 | ); | ||
| 86 | |||
| 87 | reviewedInteropFile.valid( | ||
| 88 | "allows reviewed native interop boundaries", | ||
| 89 | ` | ||
| 90 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; | ||
| 91 | |||
| 92 | export const foreground = useUniwindTheme().colors.foreground; | ||
| 93 | `, | ||
| 94 | ); | ||
| 95 | |||
| 96 | gitOverlayInteropFile.valid( | ||
| 97 | "allows the native liquid-glass theme boundary", | ||
| 98 | ` | ||
| 99 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; | ||
| 100 | |||
| 101 | export const tint = useUniwindTheme()["--color-glass-surface"]; | ||
| 102 | `, | ||
| 103 | ); | ||
| 104 | |||
| 105 | webFile.valid( | ||
| 106 | "does not impose the mobile custom-theme policy on web code", | ||
| 107 | `const surface = <div className="bg-white dark:bg-black" />;`, | ||
| 108 | ); | ||
| 109 | |||
| 110 | guardedMobileFile.invalid( | ||
| 111 | "reports new React theme subscriptions", | ||
| 112 | ` | ||
| 113 | import { useCSSVariable } from "uniwind"; | ||
| 114 | |||
| 115 | export const foreground = useCSSVariable("--color-foreground"); | ||
| 116 | `, | ||
| 117 | (output) => { | ||
| 118 | assert.match(output, /semantic className/); | ||
| 119 | }, | ||
| 120 | ); | ||
| 121 | |||
| 122 | guardedMobileFile.invalid( | ||
| 123 | "reports the retired theme color hook", | ||
| 124 | ` | ||
| 125 | import { useThemeColor } from "../../../hooks/useThemeColor"; | ||
| 126 | |||
| 127 | export const foreground = useThemeColor({}, "foreground"); | ||
| 128 | `, | ||
| 129 | (output) => { | ||
| 130 | assert.match(output, /replaced by semantic Uniwind classes/); | ||
| 131 | }, | ||
| 132 | ); | ||
| 133 | |||
| 134 | guardedMobileFile.invalid( | ||
| 135 | "reports guarded hooks imported with TypeScript extensions", | ||
| 136 | ` | ||
| 137 | import { useThemeColor } from "../../../hooks/useThemeColor.ts"; | ||
| 138 | import { useUniwindTheme } from "../../../lib/useUniwindTheme.ts"; | ||
| 139 | |||
| 140 | export const foreground = [ | ||
| 141 | useThemeColor({}, "foreground"), | ||
| 142 | useUniwindTheme().colors.foreground, | ||
| 143 | ]; | ||
| 144 | `, | ||
| 145 | (output) => { | ||
| 146 | assert.match(output, /replaced by semantic Uniwind classes/); | ||
| 147 | assert.match(output, /native\/third-party interop boundary/); | ||
| 148 | }, | ||
| 149 | ); | ||
| 150 | |||
| 151 | guardedMobileFile.invalid( | ||
| 152 | "reports unreviewed native interop subscriptions", | ||
| 153 | ` | ||
| 154 | import { useUniwindTheme } from "../../../lib/useUniwindTheme"; | ||
| 155 | |||
| 156 | export const foreground = useUniwindTheme().colors.foreground; | ||
| 157 | `, | ||
| 158 | (output) => { | ||
| 159 | assert.match(output, /native\/third-party interop boundary/); | ||
| 160 | }, | ||
| 161 | ); | ||
| 162 | |||
| 163 | guardedMobileFile.invalid( | ||
| 164 | "reports appearance variants in string literals", | ||
| 165 | `const surface = <View className="bg-white dark:bg-black" />;`, | ||
| 166 | (output) => { | ||
| 167 | assert.match(output, /registered custom themes/); | ||
| 168 | }, | ||
| 169 | ); | ||
| 170 | |||
| 171 | guardedMobileFile.invalid( | ||
| 172 | "reports appearance variants in template literals", | ||
| 173 | "const className = `bg-black light:bg-white`;", | ||
| 174 | ); | ||
| 175 | |||
| 176 | guardedMobileFile.invalid( | ||
| 177 | "reports escaped appearance variants in template literals", | ||
| 178 | "const className = `dark\\u003abg-black`;", | ||
| 179 | ); | ||
| 180 | |||
| 181 | guardedMobileFile.invalid( | ||
| 182 | "reports appearance variants through nested class-map indirection", | ||
| 183 | ` | ||
| 184 | const styles = { | ||
| 185 | variants: { root: "bg-white dark:bg-black" }, | ||
| 186 | }; | ||
| 187 | |||
| 188 | const root = styles.variants.root; | ||
| 189 | export const surface = <View className={root} />; | ||
| 190 | `, | ||
| 191 | (output) => { | ||
| 192 | expect(output.match(/registered custom themes/g)).toHaveLength(1); | ||
| 193 | }, | ||
| 194 | ); | ||
| 195 | |||
| 196 | guardedMobileFile.invalid( | ||
| 197 | "reports appearance variants passed to class builders", | ||
| 198 | `const surface = cn("bg-white", enabled && "dark:bg-black");`, | ||
| 199 | ); | ||
| 200 | |||
| 201 | guardedMobileFile.invalid( | ||
| 202 | "reports negative and important appearance variants", | ||
| 203 | `const className = "dark:-mt-2 light:!bg-white";`, | ||
| 204 | ); | ||
| 205 | |||
| 206 | guardedMobileFile.invalid( | ||
| 207 | "reports namespace CSS variable subscriptions", | ||
| 208 | ` | ||
| 209 | import * as Uniwind from "uniwind"; | ||
| 210 | |||
| 211 | export const foreground = Uniwind.useCSSVariable("--color-foreground"); | ||
| 212 | `, | ||
| 213 | ); | ||
| 214 | |||
| 215 | guardedMobileFile.invalid( | ||
| 216 | "reports destructured namespace CSS variable subscriptions", | ||
| 217 | ` | ||
| 218 | import * as Uniwind from "uniwind"; | ||
| 219 | |||
| 220 | const { useCSSVariable: resolveVariable } = Uniwind; | ||
| 221 | export const foreground = resolveVariable("--color-foreground"); | ||
| 222 | `, | ||
| 223 | ); | ||
| 224 | |||
| 225 | guardedMobileFile.invalid( | ||
| 226 | "reports aliased namespace CSS variable subscriptions", | ||
| 227 | ` | ||
| 228 | import * as Uniwind from "uniwind"; | ||
| 229 | |||
| 230 | const Theme = Uniwind; | ||
| 231 | const NestedTheme = Theme; | ||
| 232 | export const foreground = NestedTheme.useCSSVariable("--color-foreground"); | ||
| 233 | `, | ||
| 234 | ); | ||
| 235 | |||
| 236 | guardedMobileFile.invalid( | ||
| 237 | "reports object-rest namespace CSS variable subscriptions", | ||
| 238 | ` | ||
| 239 | import * as Uniwind from "uniwind"; | ||
| 240 | |||
| 241 | const { ...Theme } = Uniwind; | ||
| 242 | export const foreground = Theme.useCSSVariable("--color-foreground"); | ||
| 243 | `, | ||
| 244 | ); | ||
| 245 | |||
| 246 | guardedMobileFile.invalid( | ||
| 247 | "reports namespace access to the retired theme hook", | ||
| 248 | ` | ||
| 249 | import * as ThemeColor from "../../../hooks/useThemeColor"; | ||
| 250 | |||
| 251 | export const foreground = ThemeColor.useThemeColor({}, "foreground"); | ||
| 252 | `, | ||
| 253 | ); | ||
| 254 | |||
| 255 | guardedMobileFile.invalid( | ||
| 256 | "reports destructured namespace access to the retired theme hook", | ||
| 257 | ` | ||
| 258 | import * as ThemeColor from "../../../hooks/useThemeColor.ts"; | ||
| 259 | |||
| 260 | const { useThemeColor: resolveThemeColor } = ThemeColor; | ||
| 261 | export const foreground = resolveThemeColor({}, "foreground"); | ||
| 262 | `, | ||
| 263 | ); | ||
| 264 | }); | ||
oxlint-plugin-t3code/rules/no-mobile-uniwind-theme-escape-hatches.ts created+225| ... | @@ -0,0 +1,225 @@ | ||
| 1 | import { defineRule, type Variable } from "@oxlint/plugins"; | ||
| 2 | import * as Option from "effect/Option"; | ||
| 3 | |||
| 4 | import { getPropertyName, unwrapExpression } from "../utils.ts"; | ||
| 5 | |||
| 6 | const MOBILE_SOURCE_MARKER = "/apps/mobile/src/"; | ||
| 7 | const APPEARANCE_VARIANT_PATTERN = /\b(?:dark|light):(?=\S)/u; | ||
| 8 | const APPEARANCE_VARIANT_MESSAGE = | ||
| 9 | "dark:/light: utilities do not follow registered custom themes; use an adaptive semantic token."; | ||
| 10 | const THEME_INTEROP_ALLOWLIST = new Set([ | ||
| 11 | "features/archive/ArchivedThreadsScreen.tsx", | ||
| 12 | "features/connection/ConnectionsNewRouteScreen.tsx", | ||
| 13 | "features/files/FileMarkdownPreview.tsx", | ||
| 14 | "features/files/SourceFileSurface.tsx", | ||
| 15 | "features/files/ThreadFilesRouteScreen.tsx", | ||
| 16 | "features/files/thread-file-navigator-pane.tsx", | ||
| 17 | "features/home/HomeHeader.tsx", | ||
| 18 | "features/review/ReviewSheet.tsx", | ||
| 19 | "features/review/useNativeReviewDiffBridge.ts", | ||
| 20 | "features/settings/SettingsEnvironmentsRouteScreen.tsx", | ||
| 21 | "features/settings/appearance/components/AppearancePreviews.tsx", | ||
| 22 | "features/settings/appearance/components/FontSizeSliderRow.tsx", | ||
| 23 | "features/threads/GitActionProgressOverlay.tsx", | ||
| 24 | "features/threads/NewTaskContextPickerScreens.tsx", | ||
| 25 | "features/threads/NewTaskDraftScreen.tsx", | ||
| 26 | "features/threads/ThreadComposer.tsx", | ||
| 27 | "features/threads/ThreadFeed.tsx", | ||
| 28 | "features/threads/ThreadSettingsSheet.tsx", | ||
| 29 | "features/threads/git/GitOverviewSheet.tsx", | ||
| 30 | "features/threads/thread-list-items.tsx", | ||
| 31 | "features/threads/thread-list-v2-items.tsx", | ||
| 32 | "lib/useMobileNavigationTheme.ts", | ||
| 33 | "native/T3ComposerEditor.ios.tsx", | ||
| 34 | "native/T3ComposerEditor.native.tsx", | ||
| 35 | ]); | ||
| 36 | |||
| 37 | const mobileSourcePath = (filename: string): string | undefined => { | ||
| 38 | const normalized = `/${filename.replaceAll("\\", "/")}`; | ||
| 39 | const markerIndex = normalized.lastIndexOf(MOBILE_SOURCE_MARKER); | ||
| 40 | return markerIndex === -1 | ||
| 41 | ? undefined | ||
| 42 | : normalized.slice(markerIndex + MOBILE_SOURCE_MARKER.length); | ||
| 43 | }; | ||
| 44 | |||
| 45 | const literalStringValue = (node: unknown): Option.Option<string> => { | ||
| 46 | if (typeof node !== "object" || node === null) return Option.none(); | ||
| 47 | if (!("type" in node) || node.type !== "Literal") return Option.none(); | ||
| 48 | if (!("value" in node) || typeof node.value !== "string") return Option.none(); | ||
| 49 | return Option.some(node.value); | ||
| 50 | }; | ||
| 51 | |||
| 52 | const reportsAppearanceVariant = (value: string) => APPEARANCE_VARIANT_PATTERN.test(value); | ||
| 53 | |||
| 54 | const importsModule = (source: string, modulePath: string): boolean => | ||
| 55 | source.replace(/\.[cm]?[jt]sx?$/u, "").endsWith(modulePath); | ||
| 56 | |||
| 57 | export default defineRule({ | ||
| 58 | meta: { | ||
| 59 | type: "problem", | ||
| 60 | docs: { | ||
| 61 | description: | ||
| 62 | "Keep mobile theme styling on semantic Uniwind classes and reviewed native interop boundaries.", | ||
| 63 | }, | ||
| 64 | }, | ||
| 65 | create(context) { | ||
| 66 | const sourcePath = mobileSourcePath(context.filename); | ||
| 67 | if (sourcePath === undefined) return {}; | ||
| 68 | |||
| 69 | const uniwindNamespaces = new Set<Variable>(); | ||
| 70 | |||
| 71 | const resolveVariable = (node: unknown): Variable | undefined => { | ||
| 72 | const identifier = unwrapExpression(node); | ||
| 73 | if (Option.isNone(identifier) || identifier.value.type !== "Identifier") return undefined; | ||
| 74 | |||
| 75 | let scope = context.sourceCode.getScope(identifier.value); | ||
| 76 | while (true) { | ||
| 77 | const variable = scope.set.get(identifier.value.name); | ||
| 78 | if (variable !== undefined || scope.upper === null) return variable; | ||
| 79 | scope = scope.upper; | ||
| 80 | } | ||
| 81 | }; | ||
| 82 | |||
| 83 | return { | ||
| 84 | ImportDeclaration(node) { | ||
| 85 | const source = literalStringValue(node.source); | ||
| 86 | if (Option.isNone(source)) return; | ||
| 87 | const declaredVariables = context.sourceCode.getDeclaredVariables(node); | ||
| 88 | |||
| 89 | for (const specifier of node.specifiers) { | ||
| 90 | const local = unwrapExpression(specifier.local); | ||
| 91 | const importedName = | ||
| 92 | specifier.type === "ImportSpecifier" | ||
| 93 | ? getPropertyName(specifier.imported) | ||
| 94 | : Option.none(); | ||
| 95 | const isTypeOnly = | ||
| 96 | node.importKind === "type" || | ||
| 97 | (specifier.type === "ImportSpecifier" && specifier.importKind === "type"); | ||
| 98 | |||
| 99 | if ( | ||
| 100 | !isTypeOnly && | ||
| 101 | specifier.type === "ImportNamespaceSpecifier" && | ||
| 102 | Option.isSome(local) && | ||
| 103 | local.value.type === "Identifier" | ||
| 104 | ) { | ||
| 105 | const localName = local.value.name; | ||
| 106 | const variable = declaredVariables.find((candidate) => candidate.name === localName); | ||
| 107 | if (source.value === "uniwind" && variable !== undefined) { | ||
| 108 | uniwindNamespaces.add(variable); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | if ( | ||
| 113 | !isTypeOnly && | ||
| 114 | source.value === "uniwind" && | ||
| 115 | Option.isSome(importedName) && | ||
| 116 | importedName.value === "useCSSVariable" | ||
| 117 | ) { | ||
| 118 | context.report({ | ||
| 119 | node: specifier, | ||
| 120 | message: | ||
| 121 | "Use a semantic className instead of useCSSVariable; it adds a React theme subscription.", | ||
| 122 | }); | ||
| 123 | } | ||
| 124 | |||
| 125 | if (!isTypeOnly && importsModule(source.value, "/useThemeColor")) { | ||
| 126 | context.report({ | ||
| 127 | node: specifier, | ||
| 128 | message: "useThemeColor was replaced by semantic Uniwind classes.", | ||
| 129 | }); | ||
| 130 | } | ||
| 131 | |||
| 132 | if ( | ||
| 133 | !isTypeOnly && | ||
| 134 | importsModule(source.value, "/useUniwindTheme") && | ||
| 135 | !THEME_INTEROP_ALLOWLIST.has(sourcePath) | ||
| 136 | ) { | ||
| 137 | context.report({ | ||
| 138 | node: specifier, | ||
| 139 | message: | ||
| 140 | "Use className for theme styling, or review and add this native/third-party interop boundary to the lint allowlist.", | ||
| 141 | }); | ||
| 142 | } | ||
| 143 | } | ||
| 144 | }, | ||
| 145 | MemberExpression(node) { | ||
| 146 | const object = unwrapExpression(node.object); | ||
| 147 | if (Option.isNone(object) || object.value.type !== "Identifier") return; | ||
| 148 | |||
| 149 | const property = getPropertyName(node.property); | ||
| 150 | if (Option.isNone(property)) return; | ||
| 151 | |||
| 152 | const namespace = resolveVariable(object.value); | ||
| 153 | if ( | ||
| 154 | namespace !== undefined && | ||
| 155 | uniwindNamespaces.has(namespace) && | ||
| 156 | property.value === "useCSSVariable" | ||
| 157 | ) { | ||
| 158 | context.report({ | ||
| 159 | node, | ||
| 160 | message: | ||
| 161 | "Use a semantic className instead of useCSSVariable; it adds a React theme subscription.", | ||
| 162 | }); | ||
| 163 | } | ||
| 164 | }, | ||
| 165 | VariableDeclarator(node) { | ||
| 166 | const initializer = unwrapExpression(node.init); | ||
| 167 | const binding = unwrapExpression(node.id); | ||
| 168 | if ( | ||
| 169 | Option.isNone(initializer) || | ||
| 170 | initializer.value.type !== "Identifier" || | ||
| 171 | Option.isNone(binding) | ||
| 172 | ) { | ||
| 173 | return; | ||
| 174 | } | ||
| 175 | |||
| 176 | const namespace = resolveVariable(initializer.value); | ||
| 177 | if (namespace === undefined || !uniwindNamespaces.has(namespace)) return; | ||
| 178 | |||
| 179 | if (binding.value.type === "Identifier") { | ||
| 180 | const bindingName = binding.value.name; | ||
| 181 | const variable = context.sourceCode | ||
| 182 | .getDeclaredVariables(node) | ||
| 183 | .find((candidate) => candidate.name === bindingName); | ||
| 184 | if (variable !== undefined) uniwindNamespaces.add(variable); | ||
| 185 | return; | ||
| 186 | } | ||
| 187 | |||
| 188 | if (binding.value.type !== "ObjectPattern") return; | ||
| 189 | |||
| 190 | const declaredVariables = context.sourceCode.getDeclaredVariables(node); | ||
| 191 | for (const propertyNode of binding.value.properties) { | ||
| 192 | if (propertyNode.type === "RestElement") { | ||
| 193 | const restBinding = unwrapExpression(propertyNode.argument); | ||
| 194 | if (Option.isNone(restBinding) || restBinding.value.type !== "Identifier") continue; | ||
| 195 | |||
| 196 | const restName = restBinding.value.name; | ||
| 197 | const variable = declaredVariables.find((candidate) => candidate.name === restName); | ||
| 198 | if (variable !== undefined) uniwindNamespaces.add(variable); | ||
| 199 | continue; | ||
| 200 | } | ||
| 201 | |||
| 202 | if (propertyNode.type !== "Property") continue; | ||
| 203 | const property = getPropertyName(propertyNode.key); | ||
| 204 | if (Option.isNone(property)) continue; | ||
| 205 | |||
| 206 | if (property.value === "useCSSVariable") { | ||
| 207 | context.report({ | ||
| 208 | node: propertyNode, | ||
| 209 | message: | ||
| 210 | "Use a semantic className instead of useCSSVariable; it adds a React theme subscription.", | ||
| 211 | }); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | }, | ||
| 215 | Literal(node) { | ||
| 216 | if (typeof node.value !== "string" || !reportsAppearanceVariant(node.value)) return; | ||
| 217 | context.report({ node, message: APPEARANCE_VARIANT_MESSAGE }); | ||
| 218 | }, | ||
| 219 | TemplateElement(node) { | ||
| 220 | if (!reportsAppearanceVariant(node.value.cooked ?? "")) return; | ||
| 221 | context.report({ node, message: APPEARANCE_VARIANT_MESSAGE }); | ||
| 222 | }, | ||
| 223 | }; | ||
| 224 | }, | ||
| 225 | }); | ||
oxlint-plugin-t3code/test/utils.ts+1| ... | @@ -114,6 +114,7 @@ export const createOxlintRuleHarness = ( | ... | @@ -114,6 +114,7 @@ export const createOxlintRuleHarness = ( |
| 114 | rules: { [ruleName]: "error" }, | 114 | rules: { [ruleName]: "error" }, |
| 115 | }), | 115 | }), |
| 116 | ); | 116 | ); |
| 117 | yield* fs.makeDirectory(path.dirname(sourcePath), { recursive: true }); | ||
| 117 | yield* fs.writeFileString(sourcePath, source); | 118 | yield* fs.writeFileString(sourcePath, source); |
| 118 | 119 | ||
| 119 | // Run through the current Node binary: oxlint's bin is an extensionless | 120 | // Run through the current Node binary: oxlint's bin is an extensionless |
vite.config.ts+1| ... | @@ -120,6 +120,7 @@ export default defineConfig({ | ... | @@ -120,6 +120,7 @@ export default defineConfig({ |
| 120 | "t3code/no-global-process-runtime": "error", | 120 | "t3code/no-global-process-runtime": "error", |
| 121 | "t3code/no-inline-schema-compile": "warn", | 121 | "t3code/no-inline-schema-compile": "warn", |
| 122 | "t3code/no-manual-effect-runtime-in-tests": "error", | 122 | "t3code/no-manual-effect-runtime-in-tests": "error", |
| 123 | "t3code/no-mobile-uniwind-theme-escape-hatches": "error", | ||
| 123 | "t3code/no-native-title-tooltip": "error", | 124 | "t3code/no-native-title-tooltip": "error", |
| 124 | "t3code/namespace-node-imports": "error", | 125 | "t3code/namespace-node-imports": "error", |
| 125 | }, | 126 | }, |