Compare commits
	
		
			2 commits
		
	
	
		
			b6fbe71a51
			...
			4718239723
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 4718239723 | ||
|  | 8854a3b86a | 
					 23 changed files with 234 additions and 163 deletions
				
			
		|  | @ -84,3 +84,64 @@ But, sometimes you need a redesigned (sometimes better) tool for the generic usa | ||||||
|   - *What* this new tool does? |   - *What* this new tool does? | ||||||
|   - *How* this tool works? |   - *How* this tool works? | ||||||
| - Clean up code regularly. Don't keep the unused code forever. | - 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 <div class="root"></div> | ||||||
|  | }; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 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. | ||||||
|  |  | ||||||
							
								
								
									
										69
									
								
								src/App.css
									
										
									
									
									
								
							
							
						
						
									
										69
									
								
								src/App.css
									
										
									
									
									
								
							|  | @ -1,41 +1,46 @@ | ||||||
| @import "normalize.css/normalize.css"; | @layer compat, theme, material; | ||||||
| @import "./material/theme.css"; |  | ||||||
| 
 | 
 | ||||||
| :root { | @import "normalize.css/normalize.css" layer(compat); | ||||||
|   --safe-area-inset-top: env(safe-area-inset-top); | @import "./material/theme.css" layer(theme); | ||||||
|   --safe-area-inset-left: env(safe-area-inset-left); | @import "./material/material.css" layer(material); | ||||||
|   --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 { | ||||||
| Fix the bottom gap on iOS standalone. |   :root { | ||||||
| https://stackoverflow.com/questions/66005655/pwa-ios-child-of-body-not-taking-100-height-gap-on-bottom |     --safe-area-inset-top: env(safe-area-inset-top); | ||||||
| */ |     --safe-area-inset-left: env(safe-area-inset-left); | ||||||
| @media screen and (display-mode: standalone) { |     --safe-area-inset-bottom: env(safe-area-inset-bottom); | ||||||
|   body { |     --safe-area-inset-right: env(safe-area-inset-right); | ||||||
|     width: 100%; |     background-color: var(--tutu-color-surface, transparent); | ||||||
|     height: 100vh; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   #root { |   /* | ||||||
|     position: fixed; |   Fix the bottom gap on iOS standalone. | ||||||
|     top: 0; |   https://stackoverflow.com/questions/66005655/pwa-ios-child-of-body-not-taking-100-height-gap-on-bottom | ||||||
|     left: 0; |   */ | ||||||
|     height: 100vh; |   @media screen and (display-mode: standalone) { | ||||||
|     width: 100vw; |     body { | ||||||
|  |       width: 100%; | ||||||
|  |       height: 100vh; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #root { | ||||||
|  |       position: fixed; | ||||||
|  |       top: 0; | ||||||
|  |       left: 0; | ||||||
|  |       height: 100vh; | ||||||
|  |       width: 100vw; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   h1 { | ||||||
|  |     margin: 0; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   * { | ||||||
|  |     user-select: none; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .custom-emoji { | .custom-emoji { | ||||||
|   width: 1em; |   width: 1em; | ||||||
| } | } | ||||||
| 
 |  | ||||||
| h1 { |  | ||||||
|   margin: 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| * { |  | ||||||
|   user-select: none; |  | ||||||
| } |  | ||||||
							
								
								
									
										22
									
								
								src/accounts/MastodonOAuth2Callback.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/accounts/MastodonOAuth2Callback.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -8,7 +8,7 @@ import { | ||||||
| } from "solid-js"; | } from "solid-js"; | ||||||
| import { acceptAccountViaAuthCode } from "./stores"; | import { acceptAccountViaAuthCode } from "./stores"; | ||||||
| import { $settings } from "../settings/stores"; | import { $settings } from "../settings/stores"; | ||||||
| import cards from "~material/cards.module.css"; | import "~material/cards.css"; | ||||||
| import { LinearProgress } from "@suid/material"; | import { LinearProgress } from "@suid/material"; | ||||||
| import Img from "~material/Img"; | import Img from "~material/Img"; | ||||||
| import { createRestAPIClient } from "masto"; | import { createRestAPIClient } from "masto"; | ||||||
|  | @ -92,11 +92,11 @@ const MastodonOAuth2Callback: Component = () => { | ||||||
|   }); |   }); | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|     <DocumentTitle>Back from {siteTitle()}</DocumentTitle> |       <DocumentTitle>Back from {siteTitle()}</DocumentTitle> | ||||||
|       <div class={cards.layoutCentered}> |       <main class="MastodonOAuth2Callback"> | ||||||
|         <div class={cards.card} aria-busy="true" aria-describedby={progressId}> |         <div class="card card-auto-margin" aria-busy="true" aria-describedby={progressId}> | ||||||
|           <LinearProgress |           <LinearProgress | ||||||
|             class={[cards.cardNoPad, cards.cardGutSkip].join(" ")} |             class="card-no-pad card-gut-skip" | ||||||
|             id={progressId} |             id={progressId} | ||||||
|             aria-labelledby={titleId} |             aria-labelledby={titleId} | ||||||
|           /> |           /> | ||||||
|  | @ -114,7 +114,7 @@ const MastodonOAuth2Callback: Component = () => { | ||||||
|               src={siteImg()?.src} |               src={siteImg()?.src} | ||||||
|               srcset={siteImg()?.srcset} |               srcset={siteImg()?.srcset} | ||||||
|               blurhash={siteImg()?.blurhash} |               blurhash={siteImg()?.blurhash} | ||||||
|               class={[cards.cardNoPad, cards.cardGutSkip].join(" ")} |               class="card-no-pad card-gut-skip" | ||||||
|               alt={`Banner image for ${siteTitle()}`} |               alt={`Banner image for ${siteTitle()}`} | ||||||
|               style={{ height: "235px", display: "block" }} |               style={{ height: "235px", display: "block" }} | ||||||
|             /> |             /> | ||||||
|  | @ -128,7 +128,7 @@ const MastodonOAuth2Callback: Component = () => { | ||||||
|             again. |             again. | ||||||
|           </p> |           </p> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </main> | ||||||
|     </> |     </> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ import { | ||||||
|   createUniqueId, |   createUniqueId, | ||||||
|   onMount, |   onMount, | ||||||
| } from "solid-js"; | } from "solid-js"; | ||||||
| import cards from "~material/cards.module.css"; | import "~material/cards.css"; | ||||||
| import TextField from "~material/TextField.js"; | import TextField from "~material/TextField.js"; | ||||||
| import Button from "~material/Button.js"; | import Button from "~material/Button.js"; | ||||||
| import { Title } from "~material/typography"; | import { Title } from "~material/typography"; | ||||||
|  | @ -115,7 +115,7 @@ const SignIn: Component = () => { | ||||||
|       <DocumentTitle>Sign In</DocumentTitle> |       <DocumentTitle>Sign In</DocumentTitle> | ||||||
|       <main class="SignIn"> |       <main class="SignIn"> | ||||||
|         <Show when={params.error || params.errorDescription}> |         <Show when={params.error || params.errorDescription}> | ||||||
|           <div class={cards.card} style={{ "margin-bottom": "20px" }}> |           <div class="card card-auto-margin" style={{ "margin-bottom": "20px" }}> | ||||||
|             <p>Authorization is failed.</p> |             <p>Authorization is failed.</p> | ||||||
|             <p>{params.errorDescription}</p> |             <p>{params.errorDescription}</p> | ||||||
|             <p> |             <p> | ||||||
|  | @ -125,7 +125,7 @@ const SignIn: Component = () => { | ||||||
|           </div> |           </div> | ||||||
|         </Show> |         </Show> | ||||||
|         <div |         <div | ||||||
|           class={`${cards.card} key-content`} |           class="card card-auto-margin key-content" | ||||||
|           aria-busy={currentState() !== "inactive" ? "true" : "false"} |           aria-busy={currentState() !== "inactive" ? "true" : "false"} | ||||||
|           aria-describedby={ |           aria-describedby={ | ||||||
|             currentState() !== "inactive" ? progressId : undefined |             currentState() !== "inactive" ? progressId : undefined | ||||||
|  | @ -135,7 +135,7 @@ const SignIn: Component = () => { | ||||||
|           }} |           }} | ||||||
|         > |         > | ||||||
|           <LinearProgress |           <LinearProgress | ||||||
|             class={[cards.cardNoPad, cards.cardGutSkip].join(" ")} |             class={"card-no-pad card-gut-skip"} | ||||||
|             id={progressId} |             id={progressId} | ||||||
|             sx={currentState() === "inactive" ? { display: "none" } : undefined} |             sx={currentState() === "inactive" ? { display: "none" } : undefined} | ||||||
|           /> |           /> | ||||||
|  |  | ||||||
|  | @ -7,7 +7,6 @@ import { | ||||||
|   type ParentComponent, |   type ParentComponent, | ||||||
| } from "solid-js"; | } from "solid-js"; | ||||||
| import "./BottomSheet.css"; | import "./BottomSheet.css"; | ||||||
| import material from "./material.module.css"; |  | ||||||
| import { ANIM_CURVE_ACELERATION, ANIM_CURVE_DECELERATION } from "./theme"; | import { ANIM_CURVE_ACELERATION, ANIM_CURVE_DECELERATION } from "./theme"; | ||||||
| import { | import { | ||||||
|   animateSlideInFromRight, |   animateSlideInFromRight, | ||||||
|  | @ -134,7 +133,7 @@ const BottomSheet: ParentComponent<BottomSheetProps> = (props) => { | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <dialog |     <dialog | ||||||
|       class={`BottomSheet ${material.surface} ${props.class || ""}`} |       class={`BottomSheet surface ${props.class || ""}`} | ||||||
|       classList={{ |       classList={{ | ||||||
|         ["bottom"]: props.bottomUp, |         ["bottom"]: props.bottomUp, | ||||||
|       }} |       }} | ||||||
|  |  | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| import { Component, JSX, splitProps } from "solid-js"; | import { Component, JSX, splitProps } from "solid-js"; | ||||||
| import materialStyles from "./material.module.css"; |  | ||||||
| import "./typography.css"; | import "./typography.css"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -10,13 +9,12 @@ import "./typography.css"; | ||||||
| const Button: Component<JSX.ButtonHTMLAttributes<HTMLButtonElement>> = ( | const Button: Component<JSX.ButtonHTMLAttributes<HTMLButtonElement>> = ( | ||||||
|   props, |   props, | ||||||
| ) => { | ) => { | ||||||
|   const [managed, passthough] = splitProps(props, ["class", "type"]); |   const [managed, passthough] = splitProps(props, [ "type"]); | ||||||
|   const type = () => managed.type ?? "button"; |   const type = () => managed.type ?? "button"; | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <button |     <button | ||||||
|       type={type()} |       type={type()} | ||||||
|       class={`${materialStyles.button} buttonText ${managed.class || ""}`} |  | ||||||
|       {...passthough} |       {...passthough} | ||||||
|     ></button> |     ></button> | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| .textfield { | .TextField { | ||||||
|   composes: touchTarget from "material.module.css"; |   min-width: 44px; | ||||||
|  |   min-height: 44px; | ||||||
|  |   cursor: pointer; | ||||||
| 
 | 
 | ||||||
|   --border-color: var(--tutu-color-inactive-on-surface); |   --border-color: var(--tutu-color-inactive-on-surface); | ||||||
|   --active-border-color: var(--tutu-color-primary); |   --active-border-color: var(--tutu-color-primary); | ||||||
|  | @ -6,7 +6,7 @@ import { | ||||||
|   onMount, |   onMount, | ||||||
|   Show, |   Show, | ||||||
| } from "solid-js"; | } from "solid-js"; | ||||||
| import formStyles from "./form.module.css"; | import "./TextField.css"; | ||||||
| 
 | 
 | ||||||
| export type TextFieldProps = { | export type TextFieldProps = { | ||||||
|   label?: string; |   label?: string; | ||||||
|  | @ -47,12 +47,12 @@ const TextField: Component<TextFieldProps> = (props) => { | ||||||
|   const inputId = () => props.inputId ?? altInputId; |   const inputId = () => props.inputId ?? altInputId; | ||||||
| 
 | 
 | ||||||
|   const fieldClass = () => { |   const fieldClass = () => { | ||||||
|     const cls = [formStyles.textfield]; |     const cls = ["TextField"]; | ||||||
|     if (typeof props.helperText !== "undefined") { |     if (typeof props.helperText !== "undefined") { | ||||||
|       cls.push(formStyles.withHelperText); |       cls.push("withHelperText"); | ||||||
|     } |     } | ||||||
|     if (props.error) { |     if (props.error) { | ||||||
|       cls.push(formStyles.error); |       cls.push("error"); | ||||||
|     } |     } | ||||||
|     return cls.join(" "); |     return cls.join(" "); | ||||||
|   }; |   }; | ||||||
|  | @ -71,7 +71,7 @@ const TextField: Component<TextFieldProps> = (props) => { | ||||||
|         name={props.name} |         name={props.name} | ||||||
|       /> |       /> | ||||||
|       <Show when={typeof props.helperText !== "undefined"}> |       <Show when={typeof props.helperText !== "undefined"}> | ||||||
|         <span class={formStyles.helperText}>{props.helperText}</span> |         <span class="helperText">{props.helperText}</span> | ||||||
|       </Show> |       </Show> | ||||||
|     </div> |     </div> | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
							
								
								
									
										56
									
								
								src/material/cards.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/material/cards.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -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; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  | @ -1,16 +1,14 @@ | ||||||
|  | @import "./typography.css"; | ||||||
|  | 
 | ||||||
| .surface { | .surface { | ||||||
|   background-color: var(--tutu-color-surface); |   background-color: var(--tutu-color-surface); | ||||||
|   color: var(--tutu-color-on-surface); |   color: var(--tutu-color-on-surface); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .touchTarget { | button { | ||||||
|   min-width: 44px; |   min-width: 44px; | ||||||
|   min-height: 44px; |   min-height: 44px; | ||||||
|   cursor: pointer; |   cursor: pointer; | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .button { |  | ||||||
|   composes: touchTarget; |  | ||||||
| 
 | 
 | ||||||
|   border: none; |   border: none; | ||||||
|   background-color: transparent; |   background-color: transparent; | ||||||
|  | @ -1,3 +1,6 @@ | ||||||
|  | /* Don't import this file directly. This file is already included in material.css */ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| .display4 { | .display4 { | ||||||
|   font-size: 7rem; |   font-size: 7rem; | ||||||
|   font-weight: 300; |   font-weight: 300; | ||||||
|  |  | ||||||
|  | @ -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 { Dynamic } from "solid-js/web"; | ||||||
| import "./typography.css"; |  | ||||||
| 
 | 
 | ||||||
| type AnyElement = keyof JSX.IntrinsicElements | ParentComponent<any>; | export type TypographyProps<E extends ValidComponent> = { | ||||||
| 
 |  | ||||||
| type PropsOf<E extends AnyElement> = |  | ||||||
|   E extends ParentComponent<infer Props> |  | ||||||
|     ? Props |  | ||||||
|     : E extends keyof JSX.IntrinsicElements |  | ||||||
|       ? JSX.IntrinsicElements[E] |  | ||||||
|       : JSX.HTMLAttributes<HTMLElement>; |  | ||||||
| 
 |  | ||||||
| export type TypographyProps<E extends AnyElement> = { |  | ||||||
|   ref?: Ref<E>; |   ref?: Ref<E>; | ||||||
|   component?: E; |   component?: E; | ||||||
|   class?: string; |   class?: string; | ||||||
| } & PropsOf<E>; | } & ComponentProps<E>; | ||||||
| 
 | 
 | ||||||
| type TypographyKind = | type TypographyKind = | ||||||
|   | "display4" |   | "display4" | ||||||
|  | @ -30,7 +20,7 @@ type TypographyKind = | ||||||
|   | "caption" |   | "caption" | ||||||
|   | "buttonText"; |   | "buttonText"; | ||||||
| 
 | 
 | ||||||
| export function Typography<T extends AnyElement>( | export function Typography<T extends ValidComponent>( | ||||||
|   props: { typography: TypographyKind } & TypographyProps<T>, |   props: { typography: TypographyKind } & TypographyProps<T>, | ||||||
| ) { | ) { | ||||||
|   const [managed, passthough] = splitProps(props, [ |   const [managed, passthough] = splitProps(props, [ | ||||||
|  | @ -49,36 +39,36 @@ export function Typography<T extends AnyElement>( | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function Display4<E extends AnyElement>(props: TypographyProps<E>) { | export function Display4<E extends ValidComponent>(props: TypographyProps<E>) { | ||||||
|   return <Typography typography={"display4"} {...props}></Typography>; |   return <Typography typography={"display4"} {...props}></Typography>; | ||||||
| } | } | ||||||
| export function Display3<E extends AnyElement>(props: TypographyProps<E>) { | export function Display3<E extends ValidComponent>(props: TypographyProps<E>) { | ||||||
|   return <Typography typography={"display3"} {...props}></Typography>; |   return <Typography typography={"display3"} {...props}></Typography>; | ||||||
| } | } | ||||||
| export function Display2<E extends AnyElement>(props: TypographyProps<E>) { | export function Display2<E extends ValidComponent>(props: TypographyProps<E>) { | ||||||
|   return <Typography typography={"display2"} {...props}></Typography>; |   return <Typography typography={"display2"} {...props}></Typography>; | ||||||
| } | } | ||||||
| export function Display1<E extends AnyElement>(props: TypographyProps<E>) { | export function Display1<E extends ValidComponent>(props: TypographyProps<E>) { | ||||||
|   return <Typography typography={"display1"} {...props}></Typography>; |   return <Typography typography={"display1"} {...props}></Typography>; | ||||||
| } | } | ||||||
| export function Headline<E extends AnyElement>(props: TypographyProps<E>) { | export function Headline<E extends ValidComponent>(props: TypographyProps<E>) { | ||||||
|   return <Typography typography={"headline"} {...props}></Typography>; |   return <Typography typography={"headline"} {...props}></Typography>; | ||||||
| } | } | ||||||
| export function Title<E extends AnyElement>(props: TypographyProps<E>) { | export function Title<E extends ValidComponent>(props: TypographyProps<E>) { | ||||||
|   return <Typography typography={"title"} {...props}></Typography>; |   return <Typography typography={"title"} {...props}></Typography>; | ||||||
| } | } | ||||||
| export function Subheading<E extends AnyElement>(props: TypographyProps<E>) { | export function Subheading<E extends ValidComponent>(props: TypographyProps<E>) { | ||||||
|   return <Typography typography={"subheading"} {...props}></Typography>; |   return <Typography typography={"subheading"} {...props}></Typography>; | ||||||
| } | } | ||||||
| export function Body1<E extends AnyElement>(props: TypographyProps<E>) { | export function Body1<E extends ValidComponent>(props: TypographyProps<E>) { | ||||||
|   return <Typography typography={"body1"} {...props}></Typography>; |   return <Typography typography={"body1"} {...props}></Typography>; | ||||||
| } | } | ||||||
| export function Body2<E extends AnyElement>(props: TypographyProps<E>) { | export function Body2<E extends ValidComponent>(props: TypographyProps<E>) { | ||||||
|   return <Typography typography={"body2"} {...props}></Typography>; |   return <Typography typography={"body2"} {...props}></Typography>; | ||||||
| } | } | ||||||
| export function Caption<E extends AnyElement>(props: TypographyProps<E>) { | export function Caption<E extends ValidComponent>(props: TypographyProps<E>) { | ||||||
|   return <Typography typography={"caption"} {...props}></Typography>; |   return <Typography typography={"caption"} {...props}></Typography>; | ||||||
| } | } | ||||||
| export function ButtonText<E extends AnyElement>(props: TypographyProps<E>) { | export function ButtonText<E extends ValidComponent>(props: TypographyProps<E>) { | ||||||
|   return <Typography typography={"buttonText"} {...props}></Typography>; |   return <Typography typography={"buttonText"} {...props}></Typography>; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -13,7 +13,6 @@ import { Body2 } from "~material/typography.js"; | ||||||
| import { useTimeSource } from "~platform/timesrc.js"; | import { useTimeSource } from "~platform/timesrc.js"; | ||||||
| import { resolveCustomEmoji } from "../masto/toot.js"; | import { resolveCustomEmoji } from "../masto/toot.js"; | ||||||
| import { Divider } from "@suid/material"; | import { Divider } from "@suid/material"; | ||||||
| import cardStyle from "~material/cards.module.css"; |  | ||||||
| import MediaAttachmentGrid from "./toots/MediaAttachmentGrid.jsx"; | import MediaAttachmentGrid from "./toots/MediaAttachmentGrid.jsx"; | ||||||
| import { makeAcctText, useDefaultSession } from "../masto/clients"; | import { makeAcctText, useDefaultSession } from "../masto/clients"; | ||||||
| import TootContent from "./toots/TootContent"; | import TootContent from "./toots/TootContent"; | ||||||
|  | @ -25,6 +24,7 @@ import TootAuthorGroup from "./toots/TootAuthorGroup.js"; | ||||||
| import "./RegularToot.css"; | import "./RegularToot.css"; | ||||||
| import { vibrate } from "~platform/hardware.js"; | import { vibrate } from "~platform/hardware.js"; | ||||||
| import { Transition } from "solid-transition-group"; | import { Transition } from "solid-transition-group"; | ||||||
|  | import "~material/cards.css"; | ||||||
| 
 | 
 | ||||||
| export type TootEnv = { | export type TootEnv = { | ||||||
|   boost: (value: mastodon.v1.Status) => void; |   boost: (value: mastodon.v1.Status) => void; | ||||||
|  | @ -251,6 +251,7 @@ const RegularToot: Component<RegularTootProps> = (oprops) => { | ||||||
|       <article |       <article | ||||||
|         classList={{ |         classList={{ | ||||||
|           RegularToot: true, |           RegularToot: true, | ||||||
|  |           "card": true, | ||||||
|           expanded: props.evaluated, |           expanded: props.evaluated, | ||||||
|           "thread-top": props.thread === "top", |           "thread-top": props.thread === "top", | ||||||
|           "thread-mid": props.thread === "middle", |           "thread-mid": props.thread === "middle", | ||||||
|  | @ -262,7 +263,7 @@ const RegularToot: Component<RegularTootProps> = (oprops) => { | ||||||
|         {...rest} |         {...rest} | ||||||
|       > |       > | ||||||
|         <Show when={!!status().reblog}> |         <Show when={!!status().reblog}> | ||||||
|           <div class="retoot-grp"> |           <div class="retoot-grp card-gut card-pad"> | ||||||
|             <BoostIcon /> |             <BoostIcon /> | ||||||
|             <Body2 |             <Body2 | ||||||
|               innerHTML={resolveCustomEmoji( |               innerHTML={resolveCustomEmoji( | ||||||
|  | @ -284,7 +285,6 @@ const RegularToot: Component<RegularTootProps> = (oprops) => { | ||||||
|           source={toot().content} |           source={toot().content} | ||||||
|           emojis={toot().emojis} |           emojis={toot().emojis} | ||||||
|           mentions={toot().mentions} |           mentions={toot().mentions} | ||||||
|           class={cardStyle.cardNoPad} |  | ||||||
|           sensitive={toot().sensitive} |           sensitive={toot().sensitive} | ||||||
|           spoilerText={toot().spoilerText} |           spoilerText={toot().spoilerText} | ||||||
|           reveal={reveal()} |           reveal={reveal()} | ||||||
|  | @ -308,7 +308,6 @@ const RegularToot: Component<RegularTootProps> = (oprops) => { | ||||||
|         </Show> |         </Show> | ||||||
|         {props.actionable && ( |         {props.actionable && ( | ||||||
|           <Divider |           <Divider | ||||||
|             class={cardStyle.cardNoPad} |  | ||||||
|             style={{ "margin-top": "8px" }} |             style={{ "margin-top": "8px" }} | ||||||
|           /> |           /> | ||||||
|         )} |         )} | ||||||
|  | @ -319,7 +318,7 @@ const RegularToot: Component<RegularTootProps> = (oprops) => { | ||||||
|           }} |           }} | ||||||
|         > |         > | ||||||
|           <Show when={props.actionable}> |           <Show when={props.actionable}> | ||||||
|             <TootActionGroup value={status()} class={cardStyle.cardGutSkip} /> |             <TootActionGroup value={status()} /> | ||||||
|           </Show> |           </Show> | ||||||
|         </Transition> |         </Transition> | ||||||
|       </article> |       </article> | ||||||
|  |  | ||||||
|  | @ -10,7 +10,6 @@ import RegularToot, { | ||||||
|   findElementActionable, |   findElementActionable, | ||||||
|   TootEnvProvider, |   TootEnvProvider, | ||||||
| } from "./RegularToot"; | } from "./RegularToot"; | ||||||
| import cards from "~material/cards.module.css"; |  | ||||||
| import { css } from "solid-styled"; | import { css } from "solid-styled"; | ||||||
| import { createTimeSource, TimeSourceProvider } from "~platform/timesrc"; | import { createTimeSource, TimeSourceProvider } from "~platform/timesrc"; | ||||||
| import TootComposer from "./TootComposer"; | import TootComposer from "./TootComposer"; | ||||||
|  | @ -177,7 +176,6 @@ const TootBottomSheet: Component = (props) => { | ||||||
|               <TootEnvProvider value={mainTootEnv}> |               <TootEnvProvider value={mainTootEnv}> | ||||||
|                 <RegularToot |                 <RegularToot | ||||||
|                   id={`toot-${toot()!.id}`} |                   id={`toot-${toot()!.id}`} | ||||||
|                   class={cards.card} |  | ||||||
|                   style={{ |                   style={{ | ||||||
|                     "scroll-margin-top": |                     "scroll-margin-top": | ||||||
|                       "calc(var(--scaffold-topbar-height) + 20px)", |                       "calc(var(--scaffold-topbar-height) + 20px)", | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| import { | import { | ||||||
|   createEffect, |   createEffect, | ||||||
|   createMemo, |   createMemo, | ||||||
|   createRenderEffect, |  | ||||||
|   createSignal, |   createSignal, | ||||||
|   Show, |   Show, | ||||||
|   type Accessor, |   type Accessor, | ||||||
|  | @ -39,14 +38,13 @@ import { | ||||||
|   Close, |   Close, | ||||||
|   MoreVert, |   MoreVert, | ||||||
| } from "@suid/icons-material"; | } from "@suid/icons-material"; | ||||||
| import type { Account } from "../accounts/stores"; |  | ||||||
| import "./TootComposer.css"; | import "./TootComposer.css"; | ||||||
| import BottomSheet from "~material/BottomSheet"; | import BottomSheet from "~material/BottomSheet"; | ||||||
| import { useAppLocale } from "~platform/i18n"; | import { useAppLocale } from "~platform/i18n"; | ||||||
| import iso639_1 from "iso-639-1"; | import iso639_1 from "iso-639-1"; | ||||||
| import ChooseTootLang from "./TootLangPicker"; | import ChooseTootLang from "./TootLangPicker"; | ||||||
| import type { mastodon } from "masto"; | import type { mastodon } from "masto"; | ||||||
| import cardStyles from "~material/cards.module.css"; | import "~material/cards.css"; | ||||||
| import Menu, { createManagedMenuState } from "~material/Menu"; | import Menu, { createManagedMenuState } from "~material/Menu"; | ||||||
| import { useDefaultSession } from "../masto/clients"; | import { useDefaultSession } from "../masto/clients"; | ||||||
| import { resolveCustomEmoji } from "../masto/toot"; | import { resolveCustomEmoji } from "../masto/toot"; | ||||||
|  | @ -318,7 +316,7 @@ const TootComposer: Component<{ | ||||||
|   return ( |   return ( | ||||||
|     <div |     <div | ||||||
|       ref={props.ref} |       ref={props.ref} | ||||||
|       class={/* @once */ `TootComposer ${cardStyles.card}`} |       class={/* @once */ `TootComposer card`} | ||||||
|       style={containerStyle()} |       style={containerStyle()} | ||||||
|       on:touchend={ |       on:touchend={ | ||||||
|         cancelEvent |         cancelEvent | ||||||
|  | @ -328,7 +326,7 @@ const TootComposer: Component<{ | ||||||
|       on:wheel={cancelEvent} |       on:wheel={cancelEvent} | ||||||
|     > |     > | ||||||
|       <Show when={active()}> |       <Show when={active()}> | ||||||
|         <Toolbar class={cardStyles.cardNoPad}> |         <Toolbar class="card-gut"> | ||||||
|           <IconButton |           <IconButton | ||||||
|             onClick={[setActive, false]} |             onClick={[setActive, false]} | ||||||
|             aria-label="Close the composer" |             aria-label="Close the composer" | ||||||
|  | @ -341,7 +339,7 @@ const TootComposer: Component<{ | ||||||
|             <MoreVert /> |             <MoreVert /> | ||||||
|           </IconButton> |           </IconButton> | ||||||
|         </Toolbar> |         </Toolbar> | ||||||
|         <div class={cardStyles.cardNoPad}> |         <div class="card-gut"> | ||||||
|           <Menu {...menuState}> |           <Menu {...menuState}> | ||||||
|             <MenuItem> |             <MenuItem> | ||||||
|               <ListItemAvatar> |               <ListItemAvatar> | ||||||
|  | @ -360,7 +358,7 @@ const TootComposer: Component<{ | ||||||
|         </div> |         </div> | ||||||
|       </Show> |       </Show> | ||||||
| 
 | 
 | ||||||
|       <div class="reply-input"> |       <div class="reply-input card-gut card-pad"> | ||||||
|         <Show when={props.profile}> |         <Show when={props.profile}> | ||||||
|           <Avatar |           <Avatar | ||||||
|             src={props.profile!.avatar} |             src={props.profile!.avatar} | ||||||
|  | @ -406,7 +404,7 @@ const TootComposer: Component<{ | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|       <Show when={active()}> |       <Show when={active()}> | ||||||
|         <div class="options"> |         <div class="options card-pad card-gut"> | ||||||
|           <Button |           <Button | ||||||
|             startIcon={<Translate />} |             startIcon={<Translate />} | ||||||
|             endIcon={<ArrowDropDown />} |             endIcon={<ArrowDropDown />} | ||||||
|  | @ -430,7 +428,6 @@ const TootComposer: Component<{ | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|         <TootVisibilityPickerDialog |         <TootVisibilityPickerDialog | ||||||
|           class={cardStyles.cardNoPad} |  | ||||||
|           open={permPicker()} |           open={permPicker()} | ||||||
|           onClose={() => setPermPicker(false)} |           onClose={() => setPermPicker(false)} | ||||||
|           visibility={visibility()} |           visibility={visibility()} | ||||||
|  | @ -438,7 +435,6 @@ const TootComposer: Component<{ | ||||||
|         /> |         /> | ||||||
| 
 | 
 | ||||||
|         <TootLanguagePickerDialog |         <TootLanguagePickerDialog | ||||||
|           class={cardStyles.cardNoPad} |  | ||||||
|           open={langPickerOpen()} |           open={langPickerOpen()} | ||||||
|           onClose={() => setLangPickerOpen(false)} |           onClose={() => setLangPickerOpen(false)} | ||||||
|           code={language()} |           code={language()} | ||||||
|  |  | ||||||
|  | @ -15,7 +15,6 @@ import RegularToot, { | ||||||
|   findRootToot, |   findRootToot, | ||||||
|   TootEnvProvider, |   TootEnvProvider, | ||||||
| } from "./RegularToot"; | } from "./RegularToot"; | ||||||
| import cardStyle from "~material/cards.module.css"; |  | ||||||
| import type { ThreadNode } from "../masto/timelines"; | import type { ThreadNode } from "../masto/timelines"; | ||||||
| import { useNavigator } from "~platform/StackedRouter"; | import { useNavigator } from "~platform/StackedRouter"; | ||||||
| import { ANIM_CURVE_STD } from "~material/theme"; | import { ANIM_CURVE_STD } from "~material/theme"; | ||||||
|  | @ -221,7 +220,6 @@ const TootList: Component<{ | ||||||
|                             ? positionTootInThread(index, threadLength()) |                             ? positionTootInThread(index, threadLength()) | ||||||
|                             : undefined |                             : undefined | ||||||
|                         } |                         } | ||||||
|                         class={cardStyle.card} |  | ||||||
|                         evaluated={isExpanded(id)} |                         evaluated={isExpanded(id)} | ||||||
|                         actionable={isExpanded(id)} |                         actionable={isExpanded(id)} | ||||||
|                         onClick={[onItemClick, status()]} |                         onClick={[onItemClick, status()]} | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ import { useStore } from "@nanostores/solid"; | ||||||
| import { $settings } from "../../settings/stores"; | import { $settings } from "../../settings/stores"; | ||||||
| import { averageColorHex } from "~platform/blurhash"; | import { averageColorHex } from "~platform/blurhash"; | ||||||
| import "./MediaAttachmentGrid.css"; | import "./MediaAttachmentGrid.css"; | ||||||
| import cardStyle from "~material/cards.module.css"; | import "~material/cards.css"; | ||||||
| import { Preview } from "@suid/icons-material"; | import { Preview } from "@suid/icons-material"; | ||||||
| import { IconButton } from "@suid/material"; | import { IconButton } from "@suid/material"; | ||||||
| import Masonry from "~platform/Masonry"; | import Masonry from "~platform/Masonry"; | ||||||
|  | @ -153,7 +153,7 @@ const MediaAttachmentGrid: Component<{ | ||||||
|     <Masonry |     <Masonry | ||||||
|       component="section" |       component="section" | ||||||
|       ref={setRootRef} |       ref={setRootRef} | ||||||
|       class={`MediaAttachmentGrid ${cardStyle.cardNoPad}`} |       class={`MediaAttachmentGrid card-gut`} | ||||||
|       classList={{ |       classList={{ | ||||||
|         sensitive: props.sensitive, |         sensitive: props.sensitive, | ||||||
|       }} |       }} | ||||||
|  |  | ||||||
|  | @ -75,7 +75,7 @@ export function PreviewCard(props: { | ||||||
|   return ( |   return ( | ||||||
|     <a |     <a | ||||||
|       ref={root!} |       ref={root!} | ||||||
|       class={"PreviewCard"} |       class={"PreviewCard card-pad card-gut"} | ||||||
|       href={props.src.url} |       href={props.src.url} | ||||||
|       target="_blank" |       target="_blank" | ||||||
|       referrerPolicy="unsafe-url" |       referrerPolicy="unsafe-url" | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ function TootAuthorGroup( | ||||||
|   const { dateFn: dateFnLocale } = useAppLocale(); |   const { dateFn: dateFnLocale } = useAppLocale(); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div class="TootAuthorGroup" {...rest}> |     <div class="TootAuthorGroup card-gut card-pad" {...rest}> | ||||||
|       <Img src={toot().account.avatar} class="avatar" /> |       <Img src={toot().account.avatar} class="avatar" /> | ||||||
|       <div class="name-grp"> |       <div class="name-grp"> | ||||||
|         <div class="name-primary"> |         <div class="name-primary"> | ||||||
|  |  | ||||||
|  | @ -75,7 +75,7 @@ const TootContent: Component<TootContentProps> = (oprops) => { | ||||||
|           } |           } | ||||||
|         }); |         }); | ||||||
|       }} |       }} | ||||||
|       class={`TootContent ${props.class || ""}`} |       class={`TootContent card-gut card-pad ${props.class || ""}`} | ||||||
|       {...rest} |       {...rest} | ||||||
|     > |     > | ||||||
|       <Show when={props.sensitive}> |       <Show when={props.sensitive}> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue