Skip to content

Commit c14c5bc

Browse files
committed
temp
1 parent 143cd4d commit c14c5bc

File tree

8 files changed

+215
-334
lines changed

8 files changed

+215
-334
lines changed

docs/develop/tailwind-css-migration.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,116 @@ Global foundation styling should remain even in a Tailwind-based hybrid model.
301301
4. Delete helper SCSS files only after they have no remaining imports or mixin usages.
302302
5. Keep editor, runner, markdown, and Naive UI wrapper styling outside the migration scope unless there is a separate reason to refactor them.
303303

304+
## Pilot Conventions And Preferences
305+
306+
The community and tutorials pilot clarified a few practical conventions that should guide follow-up migrations.
307+
308+
These are not abstract preferences. They reflect patterns that were already implemented and verified in the pilot components and pages.
309+
310+
### 1. Keep Tailwind As A Local Authoring Tool, Not A New Global Dumping Ground
311+
312+
The global entry file [spx-gui/src/app.css](../../spx-gui/src/app.css) should stay narrowly scoped.
313+
314+
It is the right place for:
315+
316+
- Tailwind entry setup such as `@import "tailwindcss"` and `@source`
317+
- the theme bridge from existing `--ui-*` variables to Tailwind tokens
318+
- rare project-wide utilities that are truly cross-feature and not page-specific
319+
320+
It is not the right place for:
321+
322+
- tutorials-only utilities
323+
- page-local dimensions, transforms, or background colors
324+
- feature-specific card or banner styles
325+
326+
Rule of thumb:
327+
328+
- if a class name would only make sense inside one page or one small feature area, keep it local to that component instead of promoting it into `app.css`
329+
330+
### 2. Bridge Global Tokens, Not Page-Specific Business Styling
331+
332+
The Tailwind theme bridge should cover stable design-system tokens from [spx-gui/src/components/ui/tokens/index.ts](../../spx-gui/src/components/ui/tokens/index.ts).
333+
334+
That includes:
335+
336+
- shared palette tokens
337+
- semantic tokens such as `primary`, `danger`, `success`, `sprite`, `sound`, and `stage`
338+
- typography, spacing, radius, and shadow tokens
339+
340+
It should not absorb page-specific visual decisions such as a tutorials-only banner color or one-off sizing tokens.
341+
342+
Rule of thumb:
343+
344+
- bridge values that belong to the whole product design language
345+
- keep values that exist only for one feature in the feature itself
346+
347+
### 3. Prefer Tailwind For Straightforward Layout And Surface Styling
348+
349+
The pilot confirmed that Tailwind works well for the following kinds of changes:
350+
351+
- flex and grid layout
352+
- spacing, width, height, alignment, and overflow
353+
- simple typography application through bridged tokens
354+
- simple hover transitions on authored template nodes
355+
356+
Representative examples from the pilot:
357+
358+
- [spx-gui/src/pages/tutorials/index.vue](../../spx-gui/src/pages/tutorials/index.vue)
359+
- [spx-gui/src/components/tutorials/TutorialsBanner.vue](../../spx-gui/src/components/tutorials/TutorialsBanner.vue)
360+
- [spx-gui/src/components/tutorials/CourseSeriesItem.vue](../../spx-gui/src/components/tutorials/CourseSeriesItem.vue)
361+
362+
Preferred style in this category:
363+
364+
- write the style directly in the template when the utilities remain readable
365+
- remove the local style block after the result is verified
366+
367+
### 4. Do Not Force Tailwind Where A Small Inline Style Is Clearer
368+
369+
Some values are technically expressible in Tailwind but are not a good fit for utility classes.
370+
371+
Examples from the pilot:
372+
373+
- special color expressions such as `rgb(from var(--ui-color-grey-1000) r g b / 0.2)`
374+
- asset URLs that are clearer when imported through TypeScript
375+
- one-off values that would otherwise require long arbitrary-value classes
376+
377+
Preferred style in this category:
378+
379+
- import the asset or compute the value in script when that makes the dependency explicit
380+
- bind it through `:style` when the inline form is shorter and clearer than a custom utility or arbitrary class
381+
382+
This is the current preferred pattern for one-off backgrounds in migrated components such as [spx-gui/src/components/tutorials/CourseSeriesItem.vue](../../spx-gui/src/components/tutorials/CourseSeriesItem.vue).
383+
384+
### 5. Do Not Introduce Setup Variables For Single-Use Style Values Without A Payoff
385+
386+
During the pilot, a temporary setup variable was introduced for a single background image style and then removed.
387+
388+
Preferred style:
389+
390+
- if a style value is used only once and is still readable inline, bind it directly in the template
391+
- only lift it into script when it is reused, computed, or meaningfully improves readability
392+
393+
### 6. Prefer Explicit Asset Imports Over Tailwind `url(...)` Classes When Readability Matters
394+
395+
Tailwind arbitrary `url(...)` classes do work correctly through Vite build and asset hashing.
396+
397+
However, the pilot showed that this form can still create review friction because the dependency path is less explicit in the template.
398+
399+
Preferred style:
400+
401+
- for important or non-obvious asset references, prefer `import stageBgUrl from '...'` plus `:style="{ backgroundImage: \`url(${stageBgUrl})\` }"`
402+
- reserve Tailwind `bg-[url(...)]` for cases where the shorthand is clearly readable and unlikely to cause doubt
403+
404+
### 7. Keep Migration Decisions At The Responsibility Level, Not The File-Extension Level
405+
406+
The pilot reinforced the original migration strategy:
407+
408+
- Tailwind is a good fit for local page-shell and card layout concerns
409+
- local CSS or SCSS should remain for deep selectors, generated content, third-party DOM overrides, and complex stateful widgets
410+
- a file does not need to become "100% Tailwind" to count as successfully migrated
411+
412+
The goal is clearer and more maintainable code, not maximizing utility-class usage.
413+
304414
## Recommendation
305415

306416
Tailwind CSS is worth evaluating in `spx-gui`, but only as a constrained utility layer.

spx-gui/AGENTS.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,43 @@ Keep import statements in order:
5858

5959
* Generate accessibility info for interactive elements using `v-radar` directive.
6060

61+
## Styling Preferences
62+
63+
This section records the current styling preferences established during the Tailwind migration pilot.
64+
65+
### Global vs Local Styling
66+
67+
* Keep [src/app.css](src/app.css) limited to Tailwind entry setup, the theme bridge, and rare project-wide utilities.
68+
* Do not move page-local or feature-local styles into `src/app.css` just because they are written with Tailwind.
69+
* Keep page-specific dimensions, transforms, colors, and card/banner styling local to the relevant page or component.
70+
71+
### Design Tokens
72+
73+
* Keep the existing `--ui-*` design token system as the source of truth.
74+
* Bridge stable product-wide tokens into Tailwind theme tokens in `src/app.css`.
75+
* Do not add page-specific or one-off business styling values to the global theme bridge.
76+
77+
### When to Prefer Tailwind
78+
79+
* Prefer Tailwind for straightforward layout and surface styling: flex, grid, spacing, sizing, alignment, overflow, simple typography, and simple hover states.
80+
* When Tailwind classes stay readable, write them directly in the template and remove the local style block after verification.
81+
82+
### When to Keep CSS or SCSS
83+
84+
* Keep local CSS or SCSS for deep selectors, generated content, third-party DOM overrides, and complex stateful widgets.
85+
* Do not force a file to become fully Tailwind if a small amount of CSS or SCSS remains the clearest expression.
86+
87+
### Inline Styles
88+
89+
* Use `:style` for one-off values that are clearer inline than as Tailwind arbitrary values or custom utilities.
90+
* This is preferred for special expressions such as `rgb(from ...)` values or asset-backed backgrounds when the inline form is easier to review.
91+
* Do not introduce setup variables for single-use style values unless they are reused, computed, or materially improve readability.
92+
93+
### Asset References
94+
95+
* For important or non-obvious background assets, prefer TypeScript imports plus inline `backgroundImage` binding over Tailwind `bg-[url(...)]` classes.
96+
* This makes the dependency explicit in code review while still going through Vite's normal asset build pipeline.
97+
6198
### Menu Item Text Guidelines
6299

63100
When creating or modifying menu items, follow these UI guidelines for ellipses:
Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
11
<!-- TODO: merge with UICard? -->
22

33
<template>
4-
<UICard class="community-card">
4+
<UICard class="shadow-[0px_2px_4px_0px_rgba(36,41,47,0.05)]">
55
<slot></slot>
66
</UICard>
77
</template>
88

99
<script setup lang="ts">
1010
import { UICard } from '@/components/ui'
1111
</script>
12-
13-
<style lang="scss" scoped>
14-
.community-card {
15-
box-shadow: 0px 2px 4px 0px rgba(36, 41, 47, 0.05);
16-
}
17-
</style>
Lines changed: 17 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
11
<!-- Project list as a section -->
22

33
<template>
4-
<section :class="`context-${context}`" :style="{ '--project-num-in-row': numInRow }">
5-
<header class="header">
6-
<h2 class="title">
4+
<section :style="{ '--project-num-in-row': numInRow }">
5+
<header class="flex items-center justify-between" :class="isUserContext ? 'h-15 pt-5 pb-2' : 'h-13'">
6+
<h2 class="text-title" :class="isUserContext ? 'text-base leading-1' : 'text-20 leading-7'">
77
<slot name="title"></slot>
88
</h2>
99
<RouterUILink
1010
v-if="linkTo != null"
1111
v-radar="{ name: 'Link to more', desc: 'Link to more similar projects' }"
12-
class="link"
12+
class="flex items-center text-15"
1313
:to="linkTo"
1414
>
1515
<slot name="link"></slot>
16-
<UIIcon class="link-icon" type="arrowRightSmall" />
16+
<UIIcon class="ml-2 h-5 w-5" type="arrowRightSmall" />
1717
</RouterUILink>
1818
</header>
19-
<div class="projects-wrapper">
19+
<div class="projects-wrapper relative mt-2" :class="isUserContext ? 'mb-4' : 'mb-8'">
2020
<ListResultWrapper content-type="project" :query-ret="queryRet" :height="254">
2121
<template v-if="!!slots.empty" #empty="emptyProps">
2222
<slot name="empty" v-bind="emptyProps"></slot>
2323
</template>
24-
<ul class="projects">
24+
<ul
25+
class="grid grid-cols-[repeat(var(--project-num-in-row),minmax(0,1fr))]"
26+
:class="isUserContext ? 'gap-4' : 'gap-5'"
27+
>
2528
<slot></slot>
2629
</ul>
2730
</ListResultWrapper>
@@ -30,7 +33,7 @@
3033
</template>
3134

3235
<script setup lang="ts">
33-
import { useSlots } from 'vue'
36+
import { computed, useSlots } from 'vue'
3437
import type { QueryRet } from '@/utils/query'
3538
import { UIIcon } from '@/components/ui'
3639
import ListResultWrapper from '../common/ListResultWrapper.vue'
@@ -44,72 +47,20 @@ import RouterUILink from '../common/RouterUILink.vue'
4447
*/
4548
type Context = 'home' | 'user' | 'project'
4649
47-
defineProps<{
50+
const props = defineProps<{
4851
linkTo?: string | null
4952
queryRet: QueryRet<unknown[]>
5053
context: Context
5154
numInRow: number
5255
}>()
5356
5457
const slots = useSlots()
58+
const isUserContext = computed(() => props.context === 'user')
5559
</script>
5660

57-
<style lang="scss" scoped>
58-
.header {
59-
height: 52px;
60-
display: flex;
61-
justify-content: space-between;
62-
align-items: center;
63-
64-
.title {
65-
line-height: 28px;
66-
font-size: 20px;
67-
color: var(--ui-color-title);
68-
}
69-
70-
.link {
71-
display: flex;
72-
align-items: center;
73-
font-size: 15px;
74-
}
75-
76-
.link-icon {
77-
margin-left: 8px;
78-
width: 20px;
79-
height: 20px;
80-
}
81-
}
82-
83-
.projects-wrapper {
84-
margin: 8px 0 32px;
85-
position: relative;
86-
87-
&:not(:has(.projects)) {
88-
background-color: var(--ui-color-grey-100);
89-
border-radius: var(--ui-border-radius-2);
90-
}
91-
}
92-
93-
.projects {
94-
display: grid;
95-
grid-template-columns: repeat(var(--project-num-in-row), 1fr);
96-
gap: 20px;
97-
}
98-
99-
.context-user {
100-
.header {
101-
height: 60px;
102-
padding: 20px 0 8px;
103-
}
104-
.title {
105-
font-size: 16px;
106-
line-height: 26px;
107-
}
108-
.projects-wrapper {
109-
margin: 8px 0 16px;
110-
}
111-
.projects {
112-
gap: 16px;
113-
}
61+
<style scoped>
62+
.projects-wrapper:not(:has(.projects)) {
63+
background-color: var(--ui-color-grey-100);
64+
border-radius: var(--ui-border-radius-2);
11465
}
11566
</style>

spx-gui/src/components/community/user/sidebar/UserSidebarItem.vue

Lines changed: 18 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -7,71 +7,23 @@ defineProps<{
77
</script>
88

99
<template>
10-
<RouterLink class="user-sidebar-item" exact-active-class="active" :to="to">
11-
<i class="icon-bg" />
12-
<span class="content">
13-
<slot></slot>
14-
</span>
15-
<UIIcon class="arrow" type="arrowAlt" />
10+
<RouterLink v-slot="{ href, navigate, isExactActive }" custom :to="to">
11+
<a
12+
:href="href"
13+
class="relative flex items-center rounded-2 p-3 no-underline transition-all duration-100"
14+
:class="isExactActive ? 'bg-primary-100 text-primary-main' : 'text-text'"
15+
@click="navigate"
16+
>
17+
<i
18+
class="absolute left-3 top-3.5 h-[17px] w-[17px] rounded-full transition-all duration-100"
19+
:class="isExactActive ? 'bg-primary-300' : 'bg-grey-500'"
20+
/>
21+
<span
22+
class="relative flex-1 pl-[26px] text-[13px] leading-5 [&_svg]:absolute [&_svg]:left-0 [&_svg]:top-1/2 [&_svg]:z-[1] [&_svg]:-translate-y-1/2"
23+
>
24+
<slot></slot>
25+
</span>
26+
<UIIcon class="h-3 w-3 flex-none rotate-90 text-grey-800" type="arrowAlt" />
27+
</a>
1628
</RouterLink>
1729
</template>
18-
19-
<style lang="scss" scoped>
20-
@import '@/utils/utils';
21-
22-
.user-sidebar-item {
23-
padding: 12px;
24-
position: relative;
25-
display: flex;
26-
align-items: center;
27-
28-
border-radius: 12px;
29-
color: var(--ui-color-text);
30-
text-decoration: none;
31-
transition: 0.1s;
32-
33-
.icon-bg {
34-
position: absolute;
35-
left: 12px;
36-
top: 14px;
37-
width: 17px;
38-
height: 17px;
39-
border-radius: 50%;
40-
background: var(--ui-color-grey-500);
41-
transition: 0.1s;
42-
}
43-
44-
&.active {
45-
color: var(--ui-color-primary-main);
46-
background: var(--ui-color-primary-100);
47-
48-
.icon-bg {
49-
background: var(--ui-color-primary-300);
50-
}
51-
}
52-
}
53-
54-
.content {
55-
position: relative;
56-
padding-left: 26px;
57-
flex: 1 1 0;
58-
font-size: 13px;
59-
line-height: 20px;
60-
61-
:deep(svg) {
62-
position: absolute;
63-
left: 0;
64-
top: 50%;
65-
transform: translateY(-50%);
66-
z-index: 1;
67-
}
68-
}
69-
70-
.arrow {
71-
flex: 0 0 auto;
72-
width: 12px;
73-
height: 12px;
74-
transform: rotate(90deg);
75-
color: var(--ui-color-grey-800);
76-
}
77-
</style>

0 commit comments

Comments
 (0)