diff --git a/docs/devnotes.md b/docs/devnotes.md index 6e10652..577c968 100644 --- a/docs/devnotes.md +++ b/docs/devnotes.md @@ -84,64 +84,3 @@ But, sometimes you need a redesigned (sometimes better) tool for the generic usa - *What* this new tool does? - *How* this tool works? - Clean up code regularly. Don't keep the unused code forever. - -## Managing CSS - -Two techniques are still: - -- Styled compoenent (solid-styled) -- Native CSS with CSS layering - -The second is recommended for massive use. A stylesheet for a component can be placed alongside -the component's file. The stylesheet must use the same name as the component's file name, but replace the extension with -`.css`. Say there is a component file "PreviewCard.tsx", the corresponding stylesheet is "PreviewCard.css". They are imported -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. - -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: - -```tsx -// An example of using solid-styled -import { css } from "solid-styled"; -import { createSignal } from "solid-js"; - -const Component = () => { - const [width, setWidth] = createSignal(100); - - css` - .root { - width: ${width()}%; - } - ` - return
-}; -``` - -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: - -- Duplicated loads -- Unaware of order (failed composing) -- Not-ready for hot reload - -In short, CSS module does not works well if the stylesheet will be accessed from more than one component. diff --git a/src/App.css b/src/App.css index d2643a9..70ee2fb 100644 --- a/src/App.css +++ b/src/App.css @@ -1,46 +1,41 @@ -@layer compat, theme, material; +@import "normalize.css/normalize.css"; +@import "./material/theme.css"; -@import "normalize.css/normalize.css" layer(compat); -@import "./material/theme.css" layer(theme); -@import "./material/material.css" layer(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); +} -@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); +/* +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; } - /* - 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; + #root { + position: fixed; + top: 0; + left: 0; + height: 100vh; + width: 100vw; } } .custom-emoji { width: 1em; -} \ No newline at end of file +} + +h1 { + margin: 0; +} + +* { + user-select: none; +} diff --git a/src/accounts/MastodonOAuth2Callback.css b/src/accounts/MastodonOAuth2Callback.css deleted file mode 100644 index c150d03..0000000 --- a/src/accounts/MastodonOAuth2Callback.css +++ /dev/null @@ -1,22 +0,0 @@ -.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 d530095..ae7255c 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 "~material/cards.css"; +import cards from "~material/cards.module.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="card-no-pad card-gut-skip" + class={[cards.cardNoPad, cards.cardGutSkip].join(" ")} 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 3a6233d..1e37517 100644 --- a/src/accounts/SignIn.tsx +++ b/src/accounts/SignIn.tsx @@ -6,7 +6,7 @@ import { createUniqueId, onMount, } from "solid-js"; -import "~material/cards.css"; +import cards from "~material/cards.module.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 843f245..5b463a0 100644 --- a/src/material/BottomSheet.tsx +++ b/src/material/BottomSheet.tsx @@ -7,6 +7,7 @@ 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, @@ -133,7 +134,7 @@ const BottomSheet: ParentComponent = (props) => { return ( > = ( props, ) => { - const [managed, passthough] = splitProps(props, [ "type"]); + const [managed, passthough] = splitProps(props, ["class", "type"]); const type = () => managed.type ?? "button"; return ( ); diff --git a/src/material/TextField.tsx b/src/material/TextField.tsx index d139220..6dcfe00 100644 --- a/src/material/TextField.tsx +++ b/src/material/TextField.tsx @@ -6,7 +6,7 @@ import { onMount, Show, } from "solid-js"; -import "./TextField.css"; +import formStyles from "./form.module.css"; export type TextFieldProps = { label?: string; @@ -47,12 +47,12 @@ const TextField: Component = (props) => { const inputId = () => props.inputId ?? altInputId; const fieldClass = () => { - const cls = ["TextField"]; + const cls = [formStyles.textfield]; if (typeof props.helperText !== "undefined") { - cls.push("withHelperText"); + cls.push(formStyles.withHelperText); } if (props.error) { - cls.push("error"); + cls.push(formStyles.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 deleted file mode 100644 index b49f8b3..0000000 --- a/src/material/cards.css +++ /dev/null @@ -1,56 +0,0 @@ -@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 new file mode 100644 index 0000000..6199bf7 --- /dev/null +++ b/src/material/cards.module.css @@ -0,0 +1,54 @@ +.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/TextField.css b/src/material/form.module.css similarity index 96% rename from src/material/TextField.css rename to src/material/form.module.css index 22c09ab..1fde099 100644 --- a/src/material/TextField.css +++ b/src/material/form.module.css @@ -1,7 +1,5 @@ -.TextField { - min-width: 44px; - min-height: 44px; - cursor: pointer; +.textfield { + composes: touchTarget from "material.module.css"; --border-color: var(--tutu-color-inactive-on-surface); --active-border-color: var(--tutu-color-primary); diff --git a/src/material/material.css b/src/material/material.module.css similarity index 95% rename from src/material/material.css rename to src/material/material.module.css index 28b082e..18d8fc8 100644 --- a/src/material/material.css +++ b/src/material/material.module.css @@ -1,14 +1,16 @@ -@import "./typography.css"; - .surface { background-color: var(--tutu-color-surface); color: var(--tutu-color-on-surface); } -button { +.touchTarget { 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 new file mode 100644 index 0000000..e69de29 diff --git a/src/material/typography.css b/src/material/typography.css index 4d96c9a..2e1c9af 100644 --- a/src/material/typography.css +++ b/src/material/typography.css @@ -1,6 +1,3 @@ -/* 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 9979479..ddc8cdd 100644 --- a/src/material/typography.tsx +++ b/src/material/typography.tsx @@ -1,11 +1,21 @@ -import { splitProps, type Ref, ComponentProps, ValidComponent } from "solid-js"; +import { JSX, ParentComponent, splitProps, type Ref } from "solid-js"; import { Dynamic } from "solid-js/web"; +import "./typography.css"; -export type TypographyProps = { +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 = { ref?: Ref; component?: E; class?: string; -} & ComponentProps; +} & PropsOf; type TypographyKind = | "display4" @@ -20,7 +30,7 @@ type TypographyKind = | "caption" | "buttonText"; -export function Typography( +export function Typography( props: { typography: TypographyKind } & TypographyProps, ) { const [managed, passthough] = splitProps(props, [ @@ -39,36 +49,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 7d7deb9..6a5f528 100644 --- a/src/timelines/RegularToot.tsx +++ b/src/timelines/RegularToot.tsx @@ -13,6 +13,7 @@ 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"; @@ -24,7 +25,6 @@ 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,7 +251,6 @@ 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,6 +308,7 @@ const RegularToot: Component = (oprops) => { {props.actionable && ( )} @@ -318,7 +319,7 @@ const RegularToot: Component = (oprops) => { }} > - +
diff --git a/src/timelines/TootBottomSheet.tsx b/src/timelines/TootBottomSheet.tsx index 9fd3850..68932d3 100644 --- a/src/timelines/TootBottomSheet.tsx +++ b/src/timelines/TootBottomSheet.tsx @@ -10,6 +10,7 @@ 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"; @@ -176,6 +177,7 @@ const TootBottomSheet: Component = (props) => { - + -
+
@@ -358,7 +360,7 @@ const TootComposer: Component<{
-
+
-
+
setPermPicker(false)} visibility={visibility()} @@ -435,6 +438,7 @@ const TootComposer: Component<{ /> setLangPickerOpen(false)} code={language()} diff --git a/src/timelines/TootList.tsx b/src/timelines/TootList.tsx index 8d0740d..b56d98c 100644 --- a/src/timelines/TootList.tsx +++ b/src/timelines/TootList.tsx @@ -15,6 +15,7 @@ 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"; @@ -220,6 +221,7 @@ 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 4b22564..c9d72fc 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 "~material/cards.css"; +import cardStyle from "~material/cards.module.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 ef292a0..956ff45 100644 --- a/src/timelines/toots/TootContent.tsx +++ b/src/timelines/toots/TootContent.tsx @@ -75,7 +75,7 @@ const TootContent: Component = (oprops) => { } }); }} - class={`TootContent card-gut card-pad ${props.class || ""}`} + class={`TootContent ${props.class || ""}`} {...rest} >