Merge pull request #1698 from basecamp/mobile-app/scoped-stylesheets

Mobile app / Scoped stylesheets
This commit is contained in:
Adrien Maston
2025-12-16 15:34:37 +01:00
committed by GitHub
60 changed files with 195 additions and 9 deletions
@@ -0,0 +1,5 @@
@layer android {
.hide-in-android-app {
display: none;
}
}
+5
View File
@@ -0,0 +1,5 @@
@layer ios {
.hide-in-ios-app {
display: none;
}
}
@@ -0,0 +1,10 @@
@layer mobile_app {
/* Use custom insets to account for floating navigation elements.
The mobile apps can inject their own inset value based on native elements on screen. */
:root {
--custom-safe-inset-top: var(--injected-safe-inset-top, env(safe-area-inset-top, 0px));
--custom-safe-inset-right: var(--injected-safe-inset-right, env(safe-area-inset-right, 0px));
--custom-safe-inset-bottom: var(--injected-safe-inset-bottom, env(safe-area-inset-bottom, 0px));
--custom-safe-inset-left: var(--injected-safe-inset-left, env(safe-area-inset-left, 0px));
}
}
@@ -0,0 +1,9 @@
@layer mobile_app {
.hide-in-mobile-app {
display: none;
}
.show-in-mobile-app {
display: unset;
}
}
@@ -1,4 +1,4 @@
@layer reset, base, components, modules, utilities;
@layer reset, base, components, modules, utilities, mobile_app, android, ios;
:root {
/* Spacing */
@@ -221,6 +221,7 @@
background-color: var(--color-canvas);
grid-row-start: 1;
inset-block-start: calc(var(--block-space) * -1);
margin-inline: calc(var(--main-padding) * -1);
margin-block-end: var(--events-gap);
padding-block: calc(var(--events-gap) * 3) var(--events-gap);
position: sticky;
@@ -39,6 +39,7 @@
.header__actions {
display: flex;
align-items: center;
font-size: var(--text-x-small);
gap: var(--header-gap);
inline-size: var(--header-actions-width);
@@ -116,17 +117,15 @@
/* Optional class to stack header actions on small screens
/* ------------------------------------------------------------------------ */
/* .header--mobile-actions-stack {
.header--mobile-actions-stack {
@media (max-width: 639px) {
grid-template-columns: 1fr 1fr;
grid-template-areas:
"menu menu"
"actions-start actions-end"
"title title";
"actions-start menu actions-end"
"title title title";
.header__title {
margin-block-start: 0.25rem;
}
}
} */
}
}
+108
View File
@@ -0,0 +1,108 @@
@layer components {
.icon {
-webkit-touch-callout: none;
background-color: currentColor;
block-size: var(--icon-size, 1em);
display: inline-block;
flex-shrink: 0;
inline-size: var(--icon-size, 1em);
mask-image: var(--svg);
mask-position: center;
mask-repeat: no-repeat;
mask-size: var(--icon-size, 1em);
pointer-events: none;
user-select: none;
}
img.icon {
background: none;
}
.icon--37signals { --svg: url("/37signals.svg"); }
.icon--add { --svg: url("/add.svg"); }
.icon--add--meta { --svg: url("/add--meta.svg"); }
.icon--arrow-left { --svg: url("/arrow-left.svg"); }
.icon--arrow-right { --svg: url("/arrow-right.svg"); }
.icon--arrow-up { --svg: url("/arrow-up.svg"); }
.icon--art { --svg: url("/art.svg"); }
.icon--assigned { --svg: url("/assigned.svg"); }
.icon--attachment { --svg: url("/attachment.svg"); }
.icon--bell-alert { --svg: url("/bell-alert.svg"); }
.icon--bell-off { --svg: url("/bell-off.svg"); }
.icon--bell { --svg: url("/bell.svg"); }
.icon--bolt { --svg: url("/bolt.svg"); }
.icon--bookmark-outline { --svg: url("/bookmark-outline.svg"); }
.icon--bookmark { --svg: url("/bookmark.svg"); }
.icon--boost { --svg: url("/boost.svg"); }
.icon--camera { --svg: url("/camera.svg"); }
.icon--caret-down { --svg: url("/caret-down.svg"); }
.icon--check { --svg: url("/check.svg"); }
.icon--check-circle { --svg: url("/check-circle.svg"); }
.icon--check-all { --svg: url("/check-all.svg"); }
.icon--clipboard { --svg: url("/clipboard.svg"); }
.icon--close { --svg: url("/close.svg"); }
.icon--close-circle { --svg: url("/close-circle.svg"); }
.icon--collapse { --svg: url("/collapse.svg"); }
.icon--board { --svg: url("/board.svg"); }
.icon--board-add { --svg: url("/board-add.svg"); }
.icon--column-left { --svg: url("/column-left.svg"); }
.icon--column-right { --svg: url("/column-right.svg"); }
.icon--comment { --svg: url("/comment.svg"); }
.icon--copy-paste { --svg: url("/copy-paste.svg"); }
.icon--crown { --svg: url("/crown.svg"); }
.icon--email { --svg: url("/email.svg"); }
.icon--everyone { --svg: url("/everyone.svg"); }
.icon--expand { --svg: url("/expand.svg"); }
.icon--gear { --svg: url("/gear.svg"); }
.icon--grid { --svg: url("/grid.svg"); }
.icon--filter { --svg: url("/filter.svg"); }
.icon--fizzy { --svg: url("/fizzy.svg"); }
.icon--globe { --svg: url("/globe.svg"); }
.icon--golden-ticket { --svg: url("/golden-ticket.svg"); }
.icon--home { --svg: url("/home.svg"); }
.icon--install { --svg: url("/install.svg"); }
.icon--install { --svg: url("/install.svg"); }
.icon--install-edge { --svg: url("/install-edge.svg"); }
.icon--lifebuoy { --svg: url("/lifebuoy.svg"); }
.icon--lock { --svg: url("/lock.svg"); }
.icon--logout { --svg: url("/logout.svg"); }
.icon--marker { --svg: url("/marker.svg"); }
.icon--maximize { --svg: url("/maximize.svg"); }
.icon--menu { --svg: url("/menu.svg"); }
.icon--menu-dots-horizontal { --svg: url("/menu-dots-horizontal.svg"); }
.icon--menu-dots-vertical { --svg: url("/menu-dots-vertical.svg"); }
.icon--minus { --svg: url("/minus.svg"); }
.icon--move { --svg: url("/move.svg"); }
.icon--notification-bell-access-only { --svg: url("/bell.svg"); }
.icon--notification-bell-watching { --svg: url("/bell-off.svg"); }
.icon--notification-bell-reverse-access-only { --svg: url("/bell-off.svg"); }
.icon--notification-bell-reverse-watching { --svg: url("/bell.svg"); }
.icon--password { --svg: url("/password.svg"); }
.icon--pencil { --svg: url("/pencil.svg"); }
.icon--person { --svg: url("/person.svg"); }
.icon--person-add { --svg: url("/person-add.svg"); }
.icon--picture-add { --svg: url("/picture-add.svg"); }
.icon--picture-double { --svg: url("/picture-double.svg"); }
.icon--picture-remove { --svg: url("/picture-remove.svg"); }
.icon--picture-zoom { --svg: url("/picture-zoom.svg"); }
.icon--pinned { --svg: url("/pinned.svg"); }
.icon--qr-code { --svg: url("/qr-code.svg"); }
.icon--reaction { --svg: url("/reaction.svg"); }
.icon--refresh { --svg: url("/refresh.svg"); }
.icon--refresh--meta { --svg: url("/refresh--meta.svg"); }
.icon--remove { --svg: url("/remove.svg"); }
.icon--rename { --svg: url("/rename.svg"); }
.icon--search { --svg: url("/search.svg"); }
.icon--settings { --svg: url("/settings.svg"); }
.icon--share { --svg: url("/share.svg"); }
.icon--sliders { --svg: url("/sliders.svg"); }
.icon--switch { --svg: url("/switch.svg"); }
.icon--tag { --svg: url("/tag.svg"); }
.icon--tag-outline { --svg: url("/tag-outline.svg"); }
.icon--thumb-up { --svg: url("/thumb-up.svg"); }
.icon--trash { --svg: url("/trash.svg"); }
.icon--unpinned { --svg: url("/unpinned.svg"); }
.icon--unseen { --svg: url("/unseen.svg"); }
.icon--world { --svg: url("/world.svg"); }
.icon--youtube { --svg: url("/youtube.svg"); }
}
@@ -3,7 +3,7 @@
--opacity: 0.5;
--size: 0.8em;
background: url(link.svg) no-repeat center bottom 0.2em;
background: url("/link.svg") no-repeat center bottom 0.2em;
background-size: var(--size);
block-size: 1em;
color: var(--color-link);
@@ -252,6 +252,10 @@
}
}
.show-in-mobile-app {
display: none;
}
.hide-scrollbar {
-ms-overflow-style: none; /* Edge */
scrollbar-width: none; /* FF */
+4
View File
@@ -1,4 +1,8 @@
module ApplicationHelper
def stylesheet_link_tags
stylesheet_link_tag *platform.stylesheet_paths, "data-turbo-track": "reload"
end
def page_title_tag
account_name = if Current.account && Current.session&.identity&.users&.many?
Current.account&.name
+41
View File
@@ -1,4 +1,6 @@
class ApplicationPlatform < PlatformAgent
SCOPED_STYLESHEET_PATHS = {}
def ios?
match? /iPhone|iPad/
end
@@ -39,6 +41,18 @@ class ApplicationPlatform < PlatformAgent
operating_system == "Windows"
end
def ios_app?
match? /Fizzy iOS/
end
def android_app?
match? /Fizzy Android/
end
def mobile_app?
ios_app? || android_app?
end
def operating_system
case user_agent.platform
when /Android/ then "Android"
@@ -51,4 +65,31 @@ class ApplicationPlatform < PlatformAgent
os =~ /Linux/ ? "Linux" : os
end
end
def stylesheet_paths
scoped_stylesheet_paths("web") +
(mobile_app? ? scoped_stylesheet_paths("mobile_app") : []) +
scoped_stylesheet_paths(stylesheet_asset_name)
end
private
def stylesheet_asset_name
case
when android_app? then "android"
when ios_app? then "ios"
else "desktop"
end
end
def scoped_stylesheet_paths(scope = css_asset_name)
# Allow new stylesheets to be added in dev/test without restarting server
SCOPED_STYLESHEET_PATHS.clear if Rails.env.development?
SCOPED_STYLESHEET_PATHS[scope] ||=
Rails.root.join("app/assets/stylesheets").then do |stylesheet_root|
stylesheet_root.glob("#{scope}/**/*.css").collect do |path|
path.to_s.remove(stylesheet_root.to_s + "/", ".css")
end
end
end
end
+1 -1
View File
@@ -16,7 +16,7 @@
<% turbo_refreshes_with method: :morph, scroll: :preserve %>
<%= render "layouts/theme_preference" %>
<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
<%= stylesheet_link_tags %>
<%= javascript_importmap_tags %>
<%= tenanted_action_cable_meta_tag %>