Skip to content

refactor: inline Tailwind, remove SCSS style blocks, and unify class override API#489

Merged
kelsos merged 58 commits intorotki:mainfrom
kelsos:feat/tailwind-infrastructure
Mar 28, 2026
Merged

refactor: inline Tailwind, remove SCSS style blocks, and unify class override API#489
kelsos merged 58 commits intorotki:mainfrom
kelsos:feat/tailwind-infrastructure

Conversation

@kelsos
Copy link
Copy Markdown
Member

@kelsos kelsos commented Mar 24, 2026

Summary

Implements the infrastructure and first 3 batches of the SCSS → inline Tailwind migration (#465#469, part of #473):

Key patterns established

  • variant() object pattern with compoundVariants for complex state combinations
  • Hoisted variant definitions in .ts files for frequently-instantiated components
  • data-id, data-state, data-active, data-variant attributes replacing CSS module class selectors
  • Co-located as const object + type pattern for enum-like values
  • Composable extraction for complex component logic

Other changes

  • Switched test environment from jsdom to happy-dom
  • 90 files changed, ~3000 insertions, ~2000 deletions

Test plan

  • Unit tests pass (pnpm test:run)
  • TypeScript checks pass (pnpm typecheck)
  • Lint passes (pnpm lint)
  • E2E tests pass (pnpm test:e2e)
  • Visual review in Storybook for migrated components

@kelsos kelsos requested a review from a team as a code owner March 24, 2026 09:17
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Mar 24, 2026

Codecov Report

❌ Patch coverage is 89.98016% with 101 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.01%. Comparing base (a0395b6) to head (ca1ef7d).
⚠️ Report is 58 commits behind head on main.

Files with missing lines Patch % Lines
...es/ui-library/src/components/color-picker/utils.ts 60.78% 20 Missing ⚠️
...library/src/components/tabs/tabs/use-tab-scroll.ts 55.55% 20 Missing ⚠️
.../ui-library/src/components/steppers/RuiStepper.vue 65.00% 7 Missing ⚠️
...es/ui-library/src/composables/colors/text-input.ts 0.00% 7 Missing ⚠️
...ckages/ui-library/src/composables/colors/common.ts 0.00% 6 Missing ⚠️
...es/ui-library/src/components/tabs/tabs/RuiTabs.vue 89.74% 4 Missing ⚠️
packages/ui-library/src/composables/floating.ts 96.33% 4 Missing ⚠️
...-library/src/components/forms/slider/RuiSlider.vue 94.00% 3 Missing ⚠️
...ary/src/components/forms/text-area/RuiTextArea.vue 91.42% 3 Missing ⚠️
...ary/src/components/overlays/tooltip/RuiTooltip.vue 80.00% 3 Missing ⚠️
... and 18 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #489      +/-   ##
==========================================
+ Coverage   83.79%   85.01%   +1.21%     
==========================================
  Files         110      139      +29     
  Lines        4820     5024     +204     
  Branches     1448     1509      +61     
==========================================
+ Hits         4039     4271     +232     
+ Misses        781      753      -28     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kelsos kelsos force-pushed the feat/tailwind-infrastructure branch 2 times, most recently from 52da413 to 7179d84 Compare March 25, 2026 08:51
@kelsos kelsos force-pushed the feat/tailwind-infrastructure branch 2 times, most recently from 3568033 to cf0f8a0 Compare March 25, 2026 19:12
kelsos added 23 commits March 26, 2026 11:26
- Replace getIconColorClass() with tv() color variant
- Add ui computed wrapper for template binding
- Type components computed with SvgComponent tuple alias
- Delete composables/colors/icon.ts (inlined into tv())
kelsos added 26 commits March 27, 2026 14:49
…onfig

- Replace SCSS style block with tv({ slots }) for all progress elements
- Move keyframe animations (buffer-pulse, slide-rail, collapse-stroke) to Tailwind config
- Add svg/circle slots with variant dimension for circular animation classes
- Replace v-bind() CSS with computed :style bindings for dynamic values
- Extract ProgressVariant as const enum in progress-props.ts
- Use compoundVariants for circular vs linear label positioning
- Nest circular label inside circularContainer for proper centering
- Extract clampPercent helper, CIRCLE_RADIUS/CIRCLE_CIRCUMFERENCE constants
- Extend all 6 context colors (original only had primary/secondary)
- Add Colors and CircularColors stories
- Update e2e tests (progress, footer-stepper, data-table) for data attributes
… active

- Replace SCSS style block with tv({ slots }) for wrapper/inner/input/toggle/label
- Use group/switch for hover shadow on toggle, peer-active for ripple
- Use compoundVariants for checked × disabled × color combinations
- Handle dark mode track/toggle colors in compound variants
- Use !important for disabled and validation overrides
- Add instanceof guard replacing type cast in input handler
- Use useFormTextDetail validation computed
- Add data-disabled, data-checked, data-error attributes
- Delete composables/colors/switch.ts (inlined into tv())
- Update tests for inline Tailwind class assertions
- Replace SCSS style block with tv({ slots }) for root/prepend/label/close/closeIcon
- Use compoundVariants for color × variant × clickable × disabled combinations
- Use compoundSlots for shared dark text on filled context colors
- Extract ChipSize, ChipVariant, CHIP_CLOSE_ICON_SIZES to chip-props.ts
- Add data-variant, data-color, data-disabled attributes
- Delete composables/colors/chip.ts (inlined into tv())
- Update tests for inline Tailwind class and data attribute assertions
- Replace plain <style> block with tv() bordered variant
- Use dark: prefix instead of manual .dark class toggle
- Simplify watch([selectedDate]) to watch(selectedDate)
- Update tests to check Tailwind classes instead of .rui-calendar selector
… utilities

- Replace SCSS module with single tv() definition following Nuxt UI pattern
- Use ui.slot({ class: ... }) overrides for per-column alignment/sort classes
- Extract TableAlign, SortDirection as const enums to table-props.ts
- Extract getAlignClass(), getSortButtonAlignClass() shared utilities
- Use position variant (default/sticky/fixed) instead of conflicting absolute+!fixed
- Fix duplicate alignment classes on th elements (text-left appeared twice)
- Fix sticky header flicker: single-frame width sync instead of double-RAF clear+apply
- Remove unused clearColumnWidths, simplify syncRafId
- Add data-sorted, data-direction attributes on sort buttons
- Update e2e to check Tailwind classes instead of CSS module selectors
…component

- Replace SCSS module with tv() in data-table-styles.ts
- Extract RuiTableEmptyState component (SVGs, dark mode, label/description)
- Reduces RuiDataTable imports from 21 to 19 (under max-dependencies limit)
- Use rowVariant for selected/empty/expandable/group row states
- Use ui.td({ class: getAlignClass(...) }) for body cell alignment
- Fix loading placeholder height: wrap in flex container with min-h-56
- Reuse getAlignClass from table-props.ts (re-exported via data-table-styles)
- Update e2e to check Tailwind classes instead of CSS module selectors
- Replace SCSS module with tv({ slots }) in button-styles.ts
- Extract ButtonVariant, ButtonSize, getButtonSpinnerSize to button-props.ts
- List variant self-contained in tv() with shared outlined/text color compounds
- Use compoundSlots for dark mode text overrides
- Split elevation into defaultElevation + usedElevation with named constants
- Use data-spinner attribute for loading state child visibility
- Add data-variant, data-color, data-active, data-id="btn-label" attributes
- Improve List story to showcase stacked menu-style usage
- Update ButtonGroup tests to use data-active attribute checks
- Delete composables/colors/button.ts (inlined into tv())
- Update e2e to use data-color and data-id selectors
…ned variant

- Replace CSS modules with tv() (tailwind-variants) extending shared textInputBase
- Move variant-specific classes (pt-3, py-1.5, border-b, underline pseudo) into
  variant definitions to avoid tv() extend class conflicts (tw-merge limitation)
- Fix outlined variant: flush fieldset, proper label float with -translate-y-1/2,
  overflow-visible on inputWrapper, legend text content instead of broken ::after
- Fix focused+error: override color with validation state so fieldset border
  shows error/success color even when focused
- Fix clear button: instant show on focus, 500ms delay on blur via useTimeoutManager
- Add tv() slots for all template elements: inputWrapper, details, required,
  clearButton — zero static class attributes in template
- Add data-id attributes (wrapper, prepend, append, clear-btn) for robust testing
- Update test selectors from CSS module classes to data-id and semantic selectors
- Remove unused RuiTextInputOutlined component and useLabelWithQuote import
- Slim down textInputBase.label (remove block/truncate not universal across components)
- Legend padding (px-2) now compound-gated on showLabel+active (no notch when idle)
…le block

- Create text-area-styles.ts extending shared textInputBase
- Extract underlinePseudo and TextInputVariant to text-input-styles.ts for reuse
- Rewrite template: zero static classes, all through tv() slots
- Fix outlined variant: flush fieldset, label float with -translate-y-1/2
- Add data-id attributes (wrapper, prepend, append, clear-btn)
- Add defineExpose for focus() and element (parity with TextField)
- Performance: move CSS variables to wrapper (eliminate per-element computeds),
  debounce autoGrow watcher (50ms), remove redundant minHeight DOM write
- Fix clear button: instant show on focus, 500ms delay on blur via useTimeoutManager
- Update test selectors from CSS modules to data-id and semantic selectors
…riant, fix dropdown UX

Migrate MenuSelect from CSS modules to tv() with shared activatorStyles.
Add filled variant with Material Design spec (grey bg, underline, rounded top).
Fix default variant to use transparent bg with underline per spec.

Simplify dropdown-menu composable: replace DOM-query-based scroll with
virtual list's scrollTo(), remove autoFocus (was stealing keyboard focus),
remove @mousedown handler that caused virtual scroll shift on click.

Add keyboard support: Enter, Space, Home, End keys on activator.
Wire up applyHighlighted from composable instead of local duplicate.
Migrate AutoComplete from CSS modules to tv() with shared activatorStyles.
Remove @mousedown handler on menu items (same virtual scroll shift fix as
MenuSelect). Add keyboard Home/End support. Fix OutlinedDense story args.

Consolidate menu/highlighted/active slots into activatorStyles base so
both menuSelectStyles and autoCompleteStyles inherit them without
duplication. AutoComplete only overrides the value slot for flex-wrap
chip layout.

Update unit tests and e2e tests to use data-highlighted attribute and
data-id selectors instead of CSS module class patterns.
…lock

Migrate DateTimePicker from CSS modules to tv() with shared activatorStyles.
Add iconPrepend slot for the calendar icon positioning. Implement filled
variant (was declared in props but never styled). Simplify
combinedErrorMessages computed.

Replace useLabelWithQuote with legendText computed + useFormTextDetail.

Add missing stories: Filled, Dense, Disabled, Readonly, WithErrorMessage,
WithSuccessMessage, WithHint, HideDetails.
Migrate TimePicker from CSS modules to tv() with standalone
timePickerStyles (clock face, digits, hand, numbers).

Update e2e tests to use aria-label selectors instead of CSS module
class patterns for digit elements.
All component style blocks have been migrated to inline Tailwind with
tv(). The remaining SCSS files (style.scss, colors.scss, preview.scss)
were already pure CSS with one unused SCSS variable ($context-colors).

Convert to .css, update imports, remove sass from devDependencies.
…ple app

Replace all $style CSS module references with inline Tailwind classes
including dark: variants. No more <style> blocks in the example app.
Adjust padding, margins, gaps, and sizing across components to match
M3 specifications while preserving custom size variants and shapes.

- buttons: height md 2.25→2.5rem, h-padding 1→1.5rem, line-height 1.25rem
- text fields: h-padding px-3→px-4, filled label x-padding 0.75→1rem
- text area: same padding adjustments as text fields
- chips: height 2.26→2rem, tile shape rounded-sm→rounded-lg, label/root padding
- tabs: container height 2.625→3rem
- tooltip: vertical padding py-1→py-2
- badge: sizes shifted down (sm 20→16, md 24→20, lg 28→24px), dot overlap fix
- progress: circular default diameter 32→40px
- menu: add py-2 vertical padding to content container
37-62% faster due to optimized object creation and reduced function
overhead. Override peer dep to allow tailwind-merge v2 (v3 requires TW4).
Query the DOM directly via querySelector instead of caching chip
elements in a Map. The cache held stale DOM refs between value
changes and added complexity for minimal gain on small element sets.
Reduce MAX_CACHE_SIZE from 500 to 100 and replace full cache clear on
overflow with single-entry LRU eviction using Map insertion order.
Prevents cache thrashing while keeping memory bounded.
Break down onEnter into focused helpers: highlightedMatchesSearch,
applyCustomValue, and submitClosestForm. Use early returns to flatten
control flow and reduce cyclomatic complexity.
Replace pre-allocated arrays with length manipulation with simple
push() pattern. V8 handles dynamic array growth efficiently,
making the pre-allocation unnecessary complexity.
Lower the blur-to-close debounce timing for snappier UX when
focus leaves the autocomplete. MaxWait reduced from 400ms to 200ms.
- search.ts: remove unsafe non-null assertion on LRU eviction, inline
  default filter to avoid per-evaluation allocation, wrap internalSearch
  return with readonly(), add eslint-disable for intentional Ref params
- value.ts: merge duplicate filterValuesByOptions/filterValuesByIdentifier
  into single filterValues with getKey parameter
- keyboard-navigation.ts: extract handleOpenMenuEnter to reduce onEnter
  complexity, wrap focusedValueIndex return with readonly()
Replace withDefaults() with destructured prop defaults and pass
props explicitly to RuiTextField instead of v-bind spreading $props.
Use @floating-ui/dom directly with imperative computePosition calls,
avoiding the reactive loop issues of @floating-ui/vue. Position and
arrow styles applied via Object.assign instead of reactive bindings.

- New composables/floating.ts with useFloating(), own Placement/Strategy
  const enums, library-agnostic naming (popover, visible, onLeaveComplete)
- composables/popper.ts reduced to deprecated shim (PopperOptions +
  toFloatingOptions converter)
- Tooltip/Menu: new `options` prop (FloatingOptions), deprecated `popper`
  prop preserved for backwards compat
- Internal components migrated from :popper to :options
- Stories updated to use DEFAULT_FLOATING_OPTIONS
- Default offset changed from 8 to 2
- Arrow element positioned absolutely with inline styles
@kelsos kelsos force-pushed the feat/tailwind-infrastructure branch from f1d3502 to ca1ef7d Compare March 28, 2026 22:58
@kelsos kelsos merged commit ca1ef7d into rotki:main Mar 28, 2026
5 checks passed
@kelsos kelsos deleted the feat/tailwind-infrastructure branch March 28, 2026 23:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants