|
| 1 | +:root { |
| 2 | + --input-py: --spacing(1); |
| 3 | + --input-font-size: --spacing(3.5); |
| 4 | + --input-leading: --spacing(5.5); |
| 5 | + --input-border-w: 1px; |
| 6 | + |
| 7 | + --input-icon-offset: --spacing(2); |
| 8 | + --input-icon-size: --spacing(4.5); |
| 9 | + --input-icon-gap: --spacing(1); |
| 10 | +} |
| 11 | + |
| 12 | +/* ========================================================================== |
| 13 | + Base |
| 14 | + ========================================================================== */ |
| 15 | +@layer base { |
| 16 | + select, |
| 17 | + textarea, |
| 18 | + .textarea-field, |
| 19 | + input:not([type="range"]):not([type="checkbox"]):not([type="radio"]), |
| 20 | + .input-field, |
| 21 | + tags.tagify { |
| 22 | + @apply appearance-none inline-flex bg-primary text-content |
| 23 | + rounded-lg border border-tertiary ps-2 pe-2 font-normal overflow-hidden |
| 24 | + text-ellipsis whitespace-nowrap relative w-full; |
| 25 | + |
| 26 | + /* box-shadow: var(--box-shadow); */ |
| 27 | + padding-block: var(--input-py); |
| 28 | + font-size: var(--input-font-size); |
| 29 | + line-height: var(--input-leading); |
| 30 | + } |
| 31 | + |
| 32 | + tags.tagify { |
| 33 | + @apply overflow-visible whitespace-normal p-2 text-clip; |
| 34 | + } |
| 35 | + |
| 36 | + textarea, |
| 37 | + .textarea-field { |
| 38 | + @apply block text-ellipsis whitespace-normal wrap-break-word; |
| 39 | + } |
| 40 | + |
| 41 | + /* ========================================================================== |
| 42 | + Size Variants — Tailwind utilities + token overrides |
| 43 | + ========================================================================== */ |
| 44 | + |
| 45 | + .input--size-sm { |
| 46 | + --input-py: --spacing(1); |
| 47 | + --input-font-size: --spacing(3); |
| 48 | + --input-leading: --spacing(4); |
| 49 | + |
| 50 | + --input-icon-size: --spacing(4); |
| 51 | + } |
| 52 | + |
| 53 | + .input--size-md { |
| 54 | + --input-py: --spacing(1); |
| 55 | + --input-font-size: --spacing(3.5); |
| 56 | + --input-leading: --spacing(5.5); |
| 57 | + |
| 58 | + --input-icon-size: --spacing(4.5); |
| 59 | + } |
| 60 | + |
| 61 | + .input--size-lg { |
| 62 | + --input-py: --spacing(2); |
| 63 | + --input-font-size: --spacing(3.5); |
| 64 | + --input-leading: --spacing(5.5); |
| 65 | + |
| 66 | + --input-icon-size: --spacing(5); |
| 67 | + } |
| 68 | + |
| 69 | + /* For icons and with shortcut search command + k where the size depends on the parent size*/ |
| 70 | + :has(> .input--size-sm) { |
| 71 | + --input-icon-size: --spacing(4); |
| 72 | + --input-leading: --spacing(3.5); |
| 73 | + } |
| 74 | + :has(> .input--size-md) { |
| 75 | + --input-icon-size: --spacing(4.5); |
| 76 | + --input-leading: --spacing(4); |
| 77 | + } |
| 78 | + :has(> .input--size-lg) { |
| 79 | + --input-icon-size: --spacing(5); |
| 80 | + --input-leading: --spacing(4.5); |
| 81 | + } |
| 82 | + |
| 83 | + /* ========================================================================== |
| 84 | + Disabled State |
| 85 | + ========================================================================== */ |
| 86 | + |
| 87 | + /* Disabled input — cursor on parent (input has pointer-events-none) */ |
| 88 | + .field-wrapper__content-wrapper:has(input:disabled), |
| 89 | + .field-wrapper__content-wrapper:has(select:disabled), |
| 90 | + .field-wrapper__content-wrapper:has(textarea:disabled), |
| 91 | + .field-wrapper__content-wrapper:has(.input-field:disabled) { |
| 92 | + @apply cursor-not-allowed; |
| 93 | + } |
| 94 | + |
| 95 | + select:disabled, |
| 96 | + textarea:disabled, |
| 97 | + .textarea-field:disabled, |
| 98 | + input:not([type="range"]):not([type="checkbox"]):not([type="radio"]):disabled, |
| 99 | + .input-field:disabled { |
| 100 | + @apply pointer-events-none text-content-secondary; |
| 101 | + } |
| 102 | + /* ========================================================================== |
| 103 | + Placeholder |
| 104 | + ========================================================================== */ |
| 105 | + |
| 106 | + *::placeholder { |
| 107 | + @apply text-content-secondary; |
| 108 | + } |
| 109 | + |
| 110 | + /* ========================================================================== |
| 111 | + States: Focus |
| 112 | + ========================================================================== */ |
| 113 | + |
| 114 | + select:focus-visible, |
| 115 | + textarea:focus-visible, |
| 116 | + .textarea-field:focus-visible, |
| 117 | + input:not([type="range"]):not([type="checkbox"]):not([type="radio"]):focus-visible, |
| 118 | + .input-field:focus-visible |
| 119 | + { |
| 120 | + @apply outline-none; |
| 121 | + box-shadow: var(--box-shadow-focus); |
| 122 | + } |
| 123 | +} |
| 124 | + |
| 125 | +@layer component { |
| 126 | + /* ========================================================================== |
| 127 | + States: Error, Success |
| 128 | + ========================================================================== */ |
| 129 | + select.input-field--error, |
| 130 | + textarea.input-field--error, |
| 131 | + .textarea-field.input-field--error, |
| 132 | + input:not([type="range"]):not([type="checkbox"]):not([type="radio"]).input-field--error, |
| 133 | + .input-field.input-field--error |
| 134 | + { |
| 135 | + box-shadow: var(--box-shadow-error); |
| 136 | + } |
| 137 | + |
| 138 | + select.input-field--success, |
| 139 | + textarea.input-field--success, |
| 140 | + .textarea-field.input-field--success, |
| 141 | + input:not([type="range"]):not([type="checkbox"]):not([type="radio"]).input-field--success, |
| 142 | + .input-field.input-field--success { |
| 143 | + box-shadow: var(--box-shadow-success); |
| 144 | + } |
| 145 | + |
| 146 | + /* ========================================================================== |
| 147 | + Color Input |
| 148 | + ========================================================================== */ |
| 149 | + |
| 150 | + input[type="color"] { |
| 151 | + @apply cursor-pointer; |
| 152 | + |
| 153 | + height: calc(var(--input-leading) + var(--input-py) * 2 + var(--input-border-w) * 2); |
| 154 | + } |
| 155 | + |
| 156 | + /* ========================================================================== |
| 157 | + Password Visibility |
| 158 | + ========================================================================== */ |
| 159 | + |
| 160 | + .input-field__password-toggle { |
| 161 | + @apply absolute text-content-secondary inset-y-0 end-0 flex items-center cursor-pointer; |
| 162 | + |
| 163 | + padding-inline-end: var(--input-icon-offset); |
| 164 | + } |
| 165 | + |
| 166 | + /* Wrapper-based selector: reliably targets password inputs with visibility toggle |
| 167 | + (User create resource, password confirmation, etc.) */ |
| 168 | + .field-wrapper__input:has(.input-field__password-toggle) input { |
| 169 | + padding-inline-end: calc( |
| 170 | + var(--input-icon-size) + |
| 171 | + var(--input-icon-offset) + |
| 172 | + var(--input-icon-gap) |
| 173 | + ); |
| 174 | + } |
| 175 | + |
| 176 | + /* Unified icon sizing — reads from size token */ |
| 177 | + .input-field__password-toggle svg, |
| 178 | + .input-wrapper--loading::after, |
| 179 | + .search-input__prefix svg { |
| 180 | + @apply shrink-0; |
| 181 | + width: var(--input-icon-size); |
| 182 | + height: var(--input-icon-size); |
| 183 | + } |
| 184 | + |
| 185 | + /* Loading: show spinner only — hide visibility toggle */ |
| 186 | + .input-wrapper--loading .input-field__password-toggle { |
| 187 | + @apply hidden; |
| 188 | + } |
| 189 | + |
| 190 | + /* ========================================================================== |
| 191 | + Date Input |
| 192 | + ========================================================================== */ |
| 193 | + |
| 194 | + input[type="date"]::-webkit-calendar-picker-indicator, |
| 195 | + .input-field--date::-webkit-calendar-picker-indicator { |
| 196 | + @apply opacity-0 absolute end-0 top-0 h-full cursor-pointer; |
| 197 | + } |
| 198 | + |
| 199 | + input[type="date"], |
| 200 | + .input-field--date { |
| 201 | + @apply bg-no-repeat relative; |
| 202 | + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M4 7a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12'/%3E%3Cpath d='M16 3v4'/%3E%3Cpath d='M8 3v4'/%3E%3Cpath d='M4 11h16'/%3E%3Cpath d='M11 15h1'/%3E%3Cpath d='M12 15v3'/%3E%3C/svg%3E"); |
| 203 | + background-position: right var(--input-icon-offset) center; |
| 204 | + background-size: var(--input-icon-size); |
| 205 | + } |
| 206 | + |
| 207 | + input[type="date"]:disabled, |
| 208 | + .input-field--date:disabled { |
| 209 | + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M4 7a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12'/%3E%3Cpath d='M16 3v4'/%3E%3Cpath d='M8 3v4'/%3E%3Cpath d='M4 11h16'/%3E%3Cpath d='M11 15h1'/%3E%3Cpath d='M12 15v3'/%3E%3C/svg%3E"); |
| 210 | + } |
| 211 | + |
| 212 | + /* ========================================================================== |
| 213 | + Search Input |
| 214 | + ========================================================================== */ |
| 215 | + |
| 216 | + .search-input { |
| 217 | + @apply relative w-full; |
| 218 | + } |
| 219 | + |
| 220 | + .search-input__prefix { |
| 221 | + @apply absolute inset-y-0 start-0 flex items-center pointer-events-none text-content-secondary z-10 ps-[var(--input-icon-offset)]; |
| 222 | + } |
| 223 | + |
| 224 | + /* Input padding — prefix side, suffix side (no shortcut / with shortcut) */ |
| 225 | + input[type=search], |
| 226 | + .search-input__input { |
| 227 | + padding-inline-start: calc( |
| 228 | + var(--input-icon-offset) + |
| 229 | + var(--input-icon-size) + |
| 230 | + var(--input-icon-gap) |
| 231 | + ); |
| 232 | + padding-inline-end: calc( |
| 233 | + var(--input-icon-offset) + |
| 234 | + var(--input-icon-gap) |
| 235 | + ); |
| 236 | + } |
| 237 | + |
| 238 | + /* Mac: ⌘ + K — narrower */ |
| 239 | + .search-input:has(.mac_class) .search-input__input--with-shortcut { |
| 240 | + padding-inline-end: calc( |
| 241 | + var(--input-icon-offset) + |
| 242 | + var(--input-icon-size) * 2 + |
| 243 | + var(--input-icon-gap) * 3 |
| 244 | + ); |
| 245 | + } |
| 246 | + |
| 247 | + /* PC: CTRL + K — wider (only one abbr in DOM, so :has() matches exclusively) */ |
| 248 | + .search-input:has(.pc_class) .search-input__input--with-shortcut { |
| 249 | + padding-inline-end: calc( |
| 250 | + var(--input-icon-offset) + |
| 251 | + var(--input-icon-size) * 3 + |
| 252 | + var(--input-icon-gap) * 4 |
| 253 | + ); |
| 254 | + } |
| 255 | + /* Suffix (shortcut) */ |
| 256 | + .search-input__suffix { |
| 257 | + @apply absolute inset-y-0 end-0 flex items-center text-content-secondary pointer-events-none z-10 gap-1; |
| 258 | + padding-inline-end: var(--input-icon-offset); |
| 259 | + } |
| 260 | + |
| 261 | + .search-input__shortcut { |
| 262 | + @apply text-xs font-semibold py-px px-1 border border-secondary rounded-sm bg-secondary min-w-5 flex items-center justify-center; |
| 263 | + |
| 264 | + line-height: var(--input-leading); |
| 265 | + box-shadow: var(--box-shadow-search-input-shortcut); |
| 266 | + } |
| 267 | + |
| 268 | + /* ========================================================================== |
| 269 | + Range Input |
| 270 | + ========================================================================== */ |
| 271 | + |
| 272 | + input[type="range"] { |
| 273 | + @apply accent-accent ; |
| 274 | + } |
| 275 | + |
| 276 | + /* Thumb (WebKit + Firefox) */ |
| 277 | + input[type="range"]::-webkit-slider-thumb { |
| 278 | + @apply accent-accent-content bg-accent-content; |
| 279 | + } |
| 280 | +} |
0 commit comments