Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,58 @@ git push fork main

* Open a pull request. Thank you for your contribution!

## CSS Conventions

We follow the Cutestrap Popsicle approach for component class naming and modifiers.

### Resources

- [Cutestrap Popsicle Overview](https://www.cutestrap.com/features/popsicle)
- [Cutestrap Core Docs](https://docs.cutestrap.com/section-1.html)
- [Cutestrap Utilities Docs](https://docs.cutestrap.com/section-7.html)
- [stylelint selector-disallowed-list](https://stylelint.io/user-guide/rules/selector-disallowed-list/)

### Project Rules

- Use box plus modifier chaining on the same element for component variants.
- Do: `.button.-outline`
- Do: `.alert-container.-success`
- Do: `.line.-warning`
- Do not: `.button .-outline`
- Do not: `.alert-container .-success`
- Do not introduce bare modifier selectors in SCSS.
- Do not: `.-warning { ... }`
- Do not: `.-success { ... }`
- When styling child elements, prefer semantic child classes or element selectors over modifier-like child classes.
- Prefer: `.alert-container .alert-title`
- Prefer: `.recent-build-link svg`
- Avoid: `.alert-container .-title`
- Avoid: `.recent-build-link .-icon`
- Utility layers are allowed when they are explicit and namespaced.
- Use `icon-*` for icon utilities.
- Use `anim-*` for animation utilities.
- Use `state-*` for shared state utilities where appropriate.
- Keep valid component modifiers as modifiers.
- Example: `.button.-success` remains the preferred component variant style.
- Example: `.line.-warning` remains valid for line-row variant state.

### Elm Class Construction

- For static base + modifier combinations, prefer one class string.
- Preferred: `class "button -outline"`
- Preferred: `class "hook-status -success"`
- Splitting classes is still valid Elm and is allowed when composition is dynamic or improves readability.
- Allowed: `class "build-animation -not-running", statusToClass status`
- Allowed: `class "button", class dynamicModifier`
- Avoid splitting static base + modifier pairs by default.

### Review Checklist

- Verify there are no new bare modifier selectors in `src/scss`.
- Verify no descendant modifier misuse was introduced (for example `.box .-modifier`).
- Verify utility classes are namespaced (`icon-*`, `anim-*`, `state-*`) when used.
- Run `npm run lint:css` before opening a pull request.

## Tips

### Visual Studio Code Users
Expand Down
1 change: 1 addition & 0 deletions .stylelintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ module.exports = {
// since we're going for http://www.cutestrap.com/features/popsicle
'selector-class-pattern': '^((?!(-|_)\\2{1,})[a-z0-9\\-])*$',
'selector-max-compound-selectors': 3,
'selector-disallowed-list': ['/^\\.-/', '/\\s\\.-/'],
'selector-max-specificity': [
// setting for interim, try to lower especially last number (id,class,type)
'0,4,3',
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ test: format-validate ## Test the Elm source code
.PHONY: test-playwright
test-playwright: ## Run playwright tests
@echo -e "\n### Running playwright tests"
@npm run test:cy
@npm run playwright

# The `format-validate` target is intended to
# check the format of the Elm source code.
Expand Down
4 changes: 2 additions & 2 deletions playwright/dashboards.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ test.describe('Dashboards', () => {
const firstHeaderIcon = page
.getByTestId('dashboard-card')
.first()
.locator('header .-icon')
.locator('header .icon-state')
.first();
await expect(firstHeaderIcon).toHaveClass(/-success/);
});
Expand All @@ -88,7 +88,7 @@ test.describe('Dashboards', () => {
const secondHeaderIcon = page
.getByTestId('dashboard-card')
.nth(1)
.locator('header .-icon')
.locator('header .icon-state')
.first();
await expect(secondHeaderIcon).toHaveClass(/-failure/);
});
Expand Down
4 changes: 2 additions & 2 deletions playwright/logs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ test.describe('Logs', () => {
});

test('step duration should show sub-second runtime', async ({ page }) => {
const duration = page.getByTestId('step-header-1').locator('.-duration');
await expect(duration).toHaveText('< 00:01');
const stepHeader = page.getByTestId('step-header-1');
await expect(stepHeader).toContainText('< 00:01');
});
});

Expand Down
7 changes: 3 additions & 4 deletions src/elm/Components/Alerts.elm
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ wrapAlert variantClass title message link copy =
in
div
[ class "alert-container", class variantClass ]
[ h1 [ class "-title" ] [ text title, copyButton message copy ]
[ h1 [ class "alert-title" ] [ text title, copyButton message copy ]
, if String.isEmpty message then
text ""

else
p [ class "-message" ] [ text message, hyperlink ]
p [ class "alert-message" ] [ text message, hyperlink ]
]


Expand All @@ -93,8 +93,7 @@ copyButton copyContent copy =
button
[ class "copy-button"
, attribute "aria-label" <| "copy error message '" ++ copyContent ++ "' to clipboard "
, class "button"
, class "-icon"
, class "button -icon"
, onClick <| copyMsg copyContent
, attribute "data-clipboard-text" copyContent
]
Expand Down
14 changes: 7 additions & 7 deletions src/elm/Components/Build.elm
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ buildAnimation buildStatus build =
div [ class "build-animation" ] <| topParticles build ++ bottomParticles build

_ ->
div [ class "build-animation", class "-not-running", statusToClass buildStatus ] []
div [ class "build-animation -not-running", statusToClass buildStatus ] []


{-| topParticles : returns an svg frame to parallax scroll on a running build, set to the top of the build.
Expand Down Expand Up @@ -531,13 +531,13 @@ topBuildNumberDashes : Int -> String
topBuildNumberDashes build =
case modBy 3 build of
1 ->
"-animation-dashes-1"
"anim-dashes-1"

2 ->
"-animation-dashes-2"
"anim-dashes-2"

_ ->
"-animation-dashes-3"
"anim-dashes-3"


{-| bottomBuildNumberDashes : returns a different particle effect based on a module of the build number.
Expand All @@ -546,13 +546,13 @@ bottomBuildNumberDashes : Int -> String
bottomBuildNumberDashes build =
case modBy 3 build of
1 ->
"-animation-dashes-3"
"anim-dashes-3"

2 ->
"-animation-dashes-1"
"anim-dashes-1"

_ ->
"-animation-dashes-2"
"anim-dashes-2"



Expand Down
2 changes: 1 addition & 1 deletion src/elm/Components/Builds.elm
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ viewFilter maybeEvent filterByEventMsg =
-}
viewTimeToggle : Bool -> msg -> Html msg
viewTimeToggle showTimestamp showHideFullTimestampMsg =
div [ class "form-controls", class "-stack", class "time-toggle" ]
div [ class "form-controls -stack", class "time-toggle" ]
[ div [ class "form-control" ]
[ input [ type_ "checkbox", checked showTimestamp, onClick showHideFullTimestampMsg, id "checkbox-time-toggle", Util.testAttribute "time-toggle" ] []
, label [ class "form-label", for "checkbox-time-toggle" ] [ text "show full timestamps" ]
Expand Down
2 changes: 1 addition & 1 deletion src/elm/Components/DashboardRepoCard.elm
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ view shared props =
, age = dash
, sender = dash
, duration = "--:--"
, recentBuilds = div [ class "dashboard-recent-builds", class "-none" ] [ text "waiting for builds" ]
, recentBuilds = div [ class "dashboard-recent-builds -none" ] [ text "waiting for builds" ]
}
in
section [ class "card", Util.testAttribute "dashboard-card" ]
Expand Down
3 changes: 1 addition & 2 deletions src/elm/Components/Favorites.elm
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ viewStarToggle { msg, user, org, repo } =
[ Util.testAttribute <| "star-toggle-" ++ org ++ "-" ++ repo
, onClick <| msg org (Just repo)
, starToggleAriaLabel org repo <| Favorites.isFavorited org repo user
, class "button"
, class "-icon"
, class "button -icon"
]
[ star <| Favorites.isFavorited org repo user ]

Expand Down
30 changes: 12 additions & 18 deletions src/elm/Components/Form.elm
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ viewInputSection { id_, title, subtitle, val, placeholder_, classList_, rows_, w
String.join "-" [ "input", id_ ]
in
section
[ class "form-control"
, class "-stack"
[ class "form-control -stack"
]
[ Maybe.Extra.unwrap (text "")
(\l ->
Expand Down Expand Up @@ -207,8 +206,7 @@ viewTextareaSection { id_, title, subtitle, val, placeholder_, classList_, rows_
String.join "-" [ "textarea", id_ ]
in
section
[ class "form-control"
, class "-stack"
[ class "form-control -stack"
]
[ Maybe.Extra.unwrap (text "")
(\l ->
Expand Down Expand Up @@ -365,8 +363,7 @@ viewCopyButton { id_, msg, text_, classList_, disabled_, content } =
[ button
[ class "copy-button"
, attribute "aria-label" ("copy " ++ id_ ++ "content to clipboard")
, class "button"
, class "-icon"
, class "button -icon"
, disabled disabled_
, classList classList_
, onClick <| msg content
Expand Down Expand Up @@ -401,7 +398,7 @@ viewAllowEvents :
-> List (Html msg)
viewAllowEvents shared { msg, allowEvents } =
[ h3 [ class "settings-subtitle" ] [ text "Push" ]
, div [ class "form-controls", class "-two-col" ]
, div [ class "form-controls -two-col" ]
[ viewCheckbox
{ title = "Push"
, subtitle = Nothing
Expand All @@ -424,7 +421,7 @@ viewAllowEvents shared { msg, allowEvents } =
}
]
, h3 [ class "settings-subtitle" ] [ text "Pull Request" ]
, div [ class "form-controls", class "-two-col" ]
, div [ class "form-controls -two-col" ]
[ viewCheckbox
{ title = "Opened"
, subtitle = Nothing
Expand Down Expand Up @@ -487,7 +484,7 @@ viewAllowEvents shared { msg, allowEvents } =
}
]
, h3 [ class "settings-subtitle" ] [ text "Deployments" ]
, div [ class "form-controls", class "-two-col" ]
, div [ class "form-controls -two-col" ]
[ viewCheckbox
{ title = "Created"
, subtitle = Nothing
Expand All @@ -500,7 +497,7 @@ viewAllowEvents shared { msg, allowEvents } =
}
]
, h3 [ class "settings-subtitle" ] [ text "Comment" ]
, div [ class "form-controls", class "-two-col" ]
, div [ class "form-controls -two-col" ]
[ viewCheckbox
{ title = "Created"
, subtitle = Nothing
Expand All @@ -523,7 +520,7 @@ viewAllowEvents shared { msg, allowEvents } =
}
]
, h3 [ class "settings-subtitle" ] [ text "Delete" ]
, div [ class "form-controls", class "-two-col" ]
, div [ class "form-controls -two-col" ]
[ viewCheckbox
{ title = "Branch"
, subtitle = Nothing
Expand All @@ -546,7 +543,7 @@ viewAllowEvents shared { msg, allowEvents } =
}
]
, h3 [ class "settings-subtitle" ] [ text "Schedule" ]
, div [ class "form-controls", class "-two-col" ]
, div [ class "form-controls -two-col" ]
[ viewCheckbox
{ title = "Schedule"
, subtitle = Nothing
Expand Down Expand Up @@ -715,8 +712,7 @@ viewEditableListItem props item =
span []
[ button
[ class "remove-button"
, class "button"
, class "-icon"
, class "button -icon"
, attribute "aria-label" <| "remove list item " ++ itemId
, onClick <| props.itemRemoveOnClickMsg <| itemId
, Util.testAttribute <| target ++ "-remove"
Expand All @@ -728,8 +724,7 @@ viewEditableListItem props item =
]
, button
[ class "save-button"
, class "button"
, class "-icon"
, class "button -icon"
, attribute "aria-label" <| "save list item " ++ itemId
, onClick <| props.itemSaveOnClickMsg { id = itemId, val = val }
, Util.testAttribute <| target ++ "-save"
Expand All @@ -745,8 +740,7 @@ viewEditableListItem props item =
span []
[ button
[ class "edit-button"
, class "button"
, class "-icon"
, class "button -icon"
, attribute "aria-label" <| "edit list item " ++ itemId
, onClick <| props.itemEditOnClickMsg { id = itemId }
, Util.testAttribute <| target ++ "-edit"
Expand Down
5 changes: 2 additions & 3 deletions src/elm/Components/Help.elm
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ view shared props =
-}
viewCommand : Shared.Model -> Props msg -> Command -> Html msg
viewCommand shared props command =
div [ class "form-controls", class "-stack", Util.testAttribute "help-cmd-header" ]
div [ class "form-controls -stack", Util.testAttribute "help-cmd-header" ]
[ span []
[ label [ class "form-label", for <| "" ] [ text <| command.name ++ " " ]
, case command.docs of
Expand Down Expand Up @@ -109,8 +109,7 @@ viewCommand shared props command =
[ button
[ Util.testAttribute "help-copy"
, attribute "aria-label" <| "copy " ++ command.content ++ " to clipboard"
, class "button"
, class "-icon"
, class "button -icon"
, onClick <| props.showCopyAlert command.content
, class "copy-button"
, attribute "data-clipboard-text" command.content
Expand Down
6 changes: 3 additions & 3 deletions src/elm/Components/Loading.elm
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ import Html.Attributes exposing (class)
-}
viewSmallLoader : Html msg
viewSmallLoader =
div [ class "small-loader" ] [ div [ class "-spinner" ] [], div [ class "-label" ] [] ]
div [ class "small-loader" ] [ div [ class "loader-spinner" ] [], div [ class "loader-label" ] [] ]


{-| viewSmallLoaderWithText : renders a small loading spinner for better transitioning UX with additional loading text.
-}
viewSmallLoaderWithText : String -> Html msg
viewSmallLoaderWithText label =
div [ class "small-loader" ] [ div [ class "-spinner" ] [], div [ class "-label" ] [ text label ] ]
div [ class "small-loader" ] [ div [ class "loader-spinner" ] [], div [ class "loader-label" ] [ text label ] ]


{-| viewLargeLoader : renders a large loading spinner for better transitioning UX.
-}
viewLargeLoader : Html msg
viewLargeLoader =
div [ class "large-loader" ] [ div [ class "-spinner" ] [], div [ class "-label" ] [] ]
div [ class "large-loader" ] [ div [ class "loader-spinner" ] [], div [ class "loader-label" ] [] ]
Loading
Loading