diff --git a/docs/devnotes.md b/docs/devnotes.md index fc2d7ad..6e10652 100644 --- a/docs/devnotes.md +++ b/docs/devnotes.md @@ -99,10 +99,22 @@ by the component file, so the side effect will be applied by the bundler. The speicifc component uses a root class to scope the rulesets' scope. This convention allows the component's style can be influenced by the other stylesheets. It works because Tutu is an end-user application, we gain the control of all stylesheets in the app (kind of). +Keep in mind that the native stylesheets will be applied globally at any time, you must carefully craft the stylesheet to avoid leaking +of style. -Styled component is still existing for its own good: decalrable and scoped by randomized names. -Though styled component, using attributes for scoping, may not be as performant as the techniques with CSS class names; -It's still provided in the Tutu's code infrastructure for its ease (it even provides a bridge to use js variable in css!). +Three additional CSS layers are declared as: + +- compat: Compatibility rules, like normalize.css +- theme: The theme rules +- material: The internal material styles + +When working on the material package, if the style is intended to work with the user styles, +it must be declared under the material layer. Otherwise the unlayer, which has the +highest priority in the author's, can be used. + +Styled component is still existing. Though styled component, using attributes for scoping, +may not be as performant as the techniques with CSS class names; +it's still provided in the code infrastructure for its ease. The following is an example of the recommended usage of solid-styled: @@ -123,7 +135,7 @@ const Component = () => { }; ``` -Native CSS is always recommended. When developing new component, you can use styled component first, and migrate +When developing new component, you can use styled component at first, and migrate to native css slowly. Before v2.0.0, there are CSS modules in use, but they are removed: diff --git a/src/App.css b/src/App.css index 70ee2fb..d2643a9 100644 --- a/src/App.css +++ b/src/App.css @@ -1,41 +1,46 @@ -@import "normalize.css/normalize.css"; -@import "./material/theme.css"; +@layer compat, theme, material; -:root { - --safe-area-inset-top: env(safe-area-inset-top); - --safe-area-inset-left: env(safe-area-inset-left); - --safe-area-inset-bottom: env(safe-area-inset-bottom); - --safe-area-inset-right: env(safe-area-inset-right); - background-color: var(--tutu-color-surface, transparent); -} +@import "normalize.css/normalize.css" layer(compat); +@import "./material/theme.css" layer(theme); +@import "./material/material.css" layer(material); -/* -Fix the bottom gap on iOS standalone. -https://stackoverflow.com/questions/66005655/pwa-ios-child-of-body-not-taking-100-height-gap-on-bottom -*/ -@media screen and (display-mode: standalone) { - body { - width: 100%; - height: 100vh; +@layer compat { + :root { + --safe-area-inset-top: env(safe-area-inset-top); + --safe-area-inset-left: env(safe-area-inset-left); + --safe-area-inset-bottom: env(safe-area-inset-bottom); + --safe-area-inset-right: env(safe-area-inset-right); + background-color: var(--tutu-color-surface, transparent); } - #root { - position: fixed; - top: 0; - left: 0; - height: 100vh; - width: 100vw; + /* + Fix the bottom gap on iOS standalone. + https://stackoverflow.com/questions/66005655/pwa-ios-child-of-body-not-taking-100-height-gap-on-bottom + */ + @media screen and (display-mode: standalone) { + body { + width: 100%; + height: 100vh; + } + + #root { + position: fixed; + top: 0; + left: 0; + height: 100vh; + width: 100vw; + } + } + + h1 { + margin: 0; + } + + * { + user-select: none; } } .custom-emoji { width: 1em; -} - -h1 { - margin: 0; -} - -* { - user-select: none; -} +} \ No newline at end of file diff --git a/src/accounts/MastodonOAuth2Callback.css b/src/accounts/MastodonOAuth2Callback.css new file mode 100644 index 0000000..c150d03 --- /dev/null +++ b/src/accounts/MastodonOAuth2Callback.css @@ -0,0 +1,22 @@ +.MastodonOAuth2Callback { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 448px; + + @media (max-width: 600px) { + & { + position: static; + height: 100%; + width: 100%; + left: 0; + right: 0; + transform: none; + display: grid; + grid-template-rows: 1fr auto; + height: 100vh; + overflow: auto; + } + } +} \ No newline at end of file diff --git a/src/accounts/MastodonOAuth2Callback.tsx b/src/accounts/MastodonOAuth2Callback.tsx index ae7255c..d530095 100644 --- a/src/accounts/MastodonOAuth2Callback.tsx +++ b/src/accounts/MastodonOAuth2Callback.tsx @@ -8,7 +8,7 @@ import { } from "solid-js"; import { acceptAccountViaAuthCode } from "./stores"; import { $settings } from "../settings/stores"; -import cards from "~material/cards.module.css"; +import "~material/cards.css"; import { LinearProgress } from "@suid/material"; import Img from "~material/Img"; import { createRestAPIClient } from "masto"; @@ -92,11 +92,11 @@ const MastodonOAuth2Callback: Component = () => { }); return ( <> - Back from {siteTitle()} -
-
+ Back from {siteTitle()} +
+
@@ -114,7 +114,7 @@ const MastodonOAuth2Callback: Component = () => { src={siteImg()?.src} srcset={siteImg()?.srcset} blurhash={siteImg()?.blurhash} - class={[cards.cardNoPad, cards.cardGutSkip].join(" ")} + class="card-no-pad card-gut-skip" alt={`Banner image for ${siteTitle()}`} style={{ height: "235px", display: "block" }} /> @@ -128,7 +128,7 @@ const MastodonOAuth2Callback: Component = () => { again.

-
+ ); }; diff --git a/src/accounts/SignIn.tsx b/src/accounts/SignIn.tsx index 1e37517..3a6233d 100644 --- a/src/accounts/SignIn.tsx +++ b/src/accounts/SignIn.tsx @@ -6,7 +6,7 @@ import { createUniqueId, onMount, } from "solid-js"; -import cards from "~material/cards.module.css"; +import "~material/cards.css"; import TextField from "~material/TextField.js"; import Button from "~material/Button.js"; import { Title } from "~material/typography"; @@ -115,7 +115,7 @@ const SignIn: Component = () => { Sign In
-
+

Authorization is failed.

{params.errorDescription}

@@ -125,7 +125,7 @@ const SignIn: Component = () => {

{ }} > diff --git a/src/material/BottomSheet.tsx b/src/material/BottomSheet.tsx index 5b463a0..843f245 100644 --- a/src/material/BottomSheet.tsx +++ b/src/material/BottomSheet.tsx @@ -7,7 +7,6 @@ import { type ParentComponent, } from "solid-js"; import "./BottomSheet.css"; -import material from "./material.module.css"; import { ANIM_CURVE_ACELERATION, ANIM_CURVE_DECELERATION } from "./theme"; import { animateSlideInFromRight, @@ -134,7 +133,7 @@ const BottomSheet: ParentComponent = (props) => { return ( > = ( props, ) => { - const [managed, passthough] = splitProps(props, ["class", "type"]); + const [managed, passthough] = splitProps(props, [ "type"]); const type = () => managed.type ?? "button"; return ( ); diff --git a/src/material/form.module.css b/src/material/TextField.css similarity index 96% rename from src/material/form.module.css rename to src/material/TextField.css index 1fde099..22c09ab 100644 --- a/src/material/form.module.css +++ b/src/material/TextField.css @@ -1,5 +1,7 @@ -.textfield { - composes: touchTarget from "material.module.css"; +.TextField { + min-width: 44px; + min-height: 44px; + cursor: pointer; --border-color: var(--tutu-color-inactive-on-surface); --active-border-color: var(--tutu-color-primary); diff --git a/src/material/TextField.tsx b/src/material/TextField.tsx index 6dcfe00..d139220 100644 --- a/src/material/TextField.tsx +++ b/src/material/TextField.tsx @@ -6,7 +6,7 @@ import { onMount, Show, } from "solid-js"; -import formStyles from "./form.module.css"; +import "./TextField.css"; export type TextFieldProps = { label?: string; @@ -47,12 +47,12 @@ const TextField: Component = (props) => { const inputId = () => props.inputId ?? altInputId; const fieldClass = () => { - const cls = [formStyles.textfield]; + const cls = ["TextField"]; if (typeof props.helperText !== "undefined") { - cls.push(formStyles.withHelperText); + cls.push("withHelperText"); } if (props.error) { - cls.push(formStyles.error); + cls.push("error"); } return cls.join(" "); }; @@ -71,7 +71,7 @@ const TextField: Component = (props) => { name={props.name} /> - {props.helperText} + {props.helperText}
); diff --git a/src/material/cards.css b/src/material/cards.css new file mode 100644 index 0000000..b49f8b3 --- /dev/null +++ b/src/material/cards.css @@ -0,0 +1,56 @@ +@layer material { + .card { + --card-pad: 20px; + --card-gut: 20px; + + background-color: var(--tutu-color-surface); + color: var(--tutu-color-on-surface); + + border-radius: 2px; + box-shadow: var(--tutu-shadow-e2); + transition: var(--tutu-transition-shadow); + overflow: hidden; + background-color: var(--tutu-color-surface-l); + + &:focus-within, + &:focus-visible { + box-shadow: var(--tutu-shadow-e8); + } + + &>.card-pad { + margin-inline: var(--card-pad); + } + + &>.card-gut { + &:first-child { + margin-top: var(--card-gut); + } + + &+.card-gut { + margin-top: var(--card-gut); + } + + &:last-child { + margin-bottom: var(--card-gut); + } + } + + &.card-auto-margin { + &> :not(.card-no-pad) { + margin-inline: var(--card-pad, 20px); + } + + > :not(.card-gut-skip):first-child { + margin-top: var(--card-gut, 20px); + } + + >.card-gut-skip+*:not(.card-gut-skip) { + margin-top: var(--card-gut, 20px); + } + + > :not(.card-gut-skip):last-child { + margin-bottom: var(--card-gut, 20px); + } + } + } +} \ No newline at end of file diff --git a/src/material/cards.module.css b/src/material/cards.module.css deleted file mode 100644 index 6199bf7..0000000 --- a/src/material/cards.module.css +++ /dev/null @@ -1,54 +0,0 @@ -.card { - composes: surface from "material.module.css"; - border-radius: 2px; - box-shadow: var(--tutu-shadow-e2); - transition: var(--tutu-transition-shadow); - overflow: hidden; - background-color: var(--tutu-color-surface-l); - - &:focus-within, - &:focus-visible { - box-shadow: var(--tutu-shadow-e8); - } - - &:not(.manualMargin) { - &> :not(.cardNoPad) { - margin-inline: var(--card-pad, 20px); - } - - > :not(.cardGutSkip):first-child { - margin-top: var(--card-gut, 20px); - } - - >.cardGutSkip+*:not(.cardGutSkip) { - margin-top: var(--card-gut, 20px); - } - - > :not(.cardGutSkip):last-child { - margin-bottom: var(--card-gut, 20px); - } - } -} - -.layoutCentered { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - width: 448px; - - @media (max-width: 600px) { - & { - position: static; - height: 100%; - width: 100%; - left: 0; - right: 0; - transform: none; - display: grid; - grid-template-rows: 1fr auto; - height: 100vh; - overflow: auto; - } - } -} diff --git a/src/material/material.module.css b/src/material/material.css similarity index 95% rename from src/material/material.module.css rename to src/material/material.css index 18d8fc8..28b082e 100644 --- a/src/material/material.module.css +++ b/src/material/material.css @@ -1,16 +1,14 @@ +@import "./typography.css"; + .surface { background-color: var(--tutu-color-surface); color: var(--tutu-color-on-surface); } -.touchTarget { +button { min-width: 44px; min-height: 44px; cursor: pointer; -} - -.button { - composes: touchTarget; border: none; background-color: transparent; diff --git a/src/material/toolbar.module.css b/src/material/toolbar.module.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/material/typography.css b/src/material/typography.css index 2e1c9af..4d96c9a 100644 --- a/src/material/typography.css +++ b/src/material/typography.css @@ -1,3 +1,6 @@ +/* Don't import this file directly. This file is already included in material.css */ + + .display4 { font-size: 7rem; font-weight: 300; diff --git a/src/material/typography.tsx b/src/material/typography.tsx index ddc8cdd..9979479 100644 --- a/src/material/typography.tsx +++ b/src/material/typography.tsx @@ -1,21 +1,11 @@ -import { JSX, ParentComponent, splitProps, type Ref } from "solid-js"; +import { splitProps, type Ref, ComponentProps, ValidComponent } from "solid-js"; import { Dynamic } from "solid-js/web"; -import "./typography.css"; -type AnyElement = keyof JSX.IntrinsicElements | ParentComponent; - -type PropsOf = - E extends ParentComponent - ? Props - : E extends keyof JSX.IntrinsicElements - ? JSX.IntrinsicElements[E] - : JSX.HTMLAttributes; - -export type TypographyProps = { +export type TypographyProps = { ref?: Ref; component?: E; class?: string; -} & PropsOf; +} & ComponentProps; type TypographyKind = | "display4" @@ -30,7 +20,7 @@ type TypographyKind = | "caption" | "buttonText"; -export function Typography( +export function Typography( props: { typography: TypographyKind } & TypographyProps, ) { const [managed, passthough] = splitProps(props, [ @@ -49,36 +39,36 @@ export function Typography( ); } -export function Display4(props: TypographyProps) { +export function Display4(props: TypographyProps) { return ; } -export function Display3(props: TypographyProps) { +export function Display3(props: TypographyProps) { return ; } -export function Display2(props: TypographyProps) { +export function Display2(props: TypographyProps) { return ; } -export function Display1(props: TypographyProps) { +export function Display1(props: TypographyProps) { return ; } -export function Headline(props: TypographyProps) { +export function Headline(props: TypographyProps) { return ; } -export function Title(props: TypographyProps) { +export function Title(props: TypographyProps) { return ; } -export function Subheading(props: TypographyProps) { +export function Subheading(props: TypographyProps) { return ; } -export function Body1(props: TypographyProps) { +export function Body1(props: TypographyProps) { return ; } -export function Body2(props: TypographyProps) { +export function Body2(props: TypographyProps) { return ; } -export function Caption(props: TypographyProps) { +export function Caption(props: TypographyProps) { return ; } -export function ButtonText(props: TypographyProps) { +export function ButtonText(props: TypographyProps) { return ; } diff --git a/src/timelines/RegularToot.tsx b/src/timelines/RegularToot.tsx index 6a5f528..7d7deb9 100644 --- a/src/timelines/RegularToot.tsx +++ b/src/timelines/RegularToot.tsx @@ -13,7 +13,6 @@ import { Body2 } from "~material/typography.js"; import { useTimeSource } from "~platform/timesrc.js"; import { resolveCustomEmoji } from "../masto/toot.js"; import { Divider } from "@suid/material"; -import cardStyle from "~material/cards.module.css"; import MediaAttachmentGrid from "./toots/MediaAttachmentGrid.jsx"; import { makeAcctText, useDefaultSession } from "../masto/clients"; import TootContent from "./toots/TootContent"; @@ -25,6 +24,7 @@ import TootAuthorGroup from "./toots/TootAuthorGroup.js"; import "./RegularToot.css"; import { vibrate } from "~platform/hardware.js"; import { Transition } from "solid-transition-group"; +import "~material/cards.css"; export type TootEnv = { boost: (value: mastodon.v1.Status) => void; @@ -251,6 +251,7 @@ const RegularToot: Component = (oprops) => {
= (oprops) => { {...rest} > -
+
= (oprops) => { source={toot().content} emojis={toot().emojis} mentions={toot().mentions} - class={cardStyle.cardNoPad} sensitive={toot().sensitive} spoilerText={toot().spoilerText} reveal={reveal()} @@ -308,7 +308,6 @@ const RegularToot: Component = (oprops) => { {props.actionable && ( )} @@ -319,7 +318,7 @@ const RegularToot: Component = (oprops) => { }} > - +
diff --git a/src/timelines/TootBottomSheet.tsx b/src/timelines/TootBottomSheet.tsx index 68932d3..9fd3850 100644 --- a/src/timelines/TootBottomSheet.tsx +++ b/src/timelines/TootBottomSheet.tsx @@ -10,7 +10,6 @@ import RegularToot, { findElementActionable, TootEnvProvider, } from "./RegularToot"; -import cards from "~material/cards.module.css"; import { css } from "solid-styled"; import { createTimeSource, TimeSourceProvider } from "~platform/timesrc"; import TootComposer from "./TootComposer"; @@ -177,7 +176,6 @@ const TootBottomSheet: Component = (props) => { - + -
+
@@ -360,7 +358,7 @@ const TootComposer: Component<{
-
+
-
+
setPermPicker(false)} visibility={visibility()} @@ -438,7 +435,6 @@ const TootComposer: Component<{ /> setLangPickerOpen(false)} code={language()} diff --git a/src/timelines/TootList.tsx b/src/timelines/TootList.tsx index b56d98c..8d0740d 100644 --- a/src/timelines/TootList.tsx +++ b/src/timelines/TootList.tsx @@ -15,7 +15,6 @@ import RegularToot, { findRootToot, TootEnvProvider, } from "./RegularToot"; -import cardStyle from "~material/cards.module.css"; import type { ThreadNode } from "../masto/timelines"; import { useNavigator } from "~platform/StackedRouter"; import { ANIM_CURVE_STD } from "~material/theme"; @@ -221,7 +220,6 @@ const TootList: Component<{ ? positionTootInThread(index, threadLength()) : undefined } - class={cardStyle.card} evaluated={isExpanded(id)} actionable={isExpanded(id)} onClick={[onItemClick, status()]} diff --git a/src/timelines/toots/MediaAttachmentGrid.tsx b/src/timelines/toots/MediaAttachmentGrid.tsx index c9d72fc..4b22564 100644 --- a/src/timelines/toots/MediaAttachmentGrid.tsx +++ b/src/timelines/toots/MediaAttachmentGrid.tsx @@ -20,7 +20,7 @@ import { useStore } from "@nanostores/solid"; import { $settings } from "../../settings/stores"; import { averageColorHex } from "~platform/blurhash"; import "./MediaAttachmentGrid.css"; -import cardStyle from "~material/cards.module.css"; +import "~material/cards.css"; import { Preview } from "@suid/icons-material"; import { IconButton } from "@suid/material"; import Masonry from "~platform/Masonry"; @@ -153,7 +153,7 @@ const MediaAttachmentGrid: Component<{ +
diff --git a/src/timelines/toots/TootContent.tsx b/src/timelines/toots/TootContent.tsx index 956ff45..ef292a0 100644 --- a/src/timelines/toots/TootContent.tsx +++ b/src/timelines/toots/TootContent.tsx @@ -75,7 +75,7 @@ const TootContent: Component = (oprops) => { } }); }} - class={`TootContent ${props.class || ""}`} + class={`TootContent card-gut card-pad ${props.class || ""}`} {...rest} >