Compare commits
23 Commits
b0dd8cded1
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0af0129054 | ||
|
|
8ff2ec3766 | ||
|
|
843c71946f | ||
|
|
51e3629fa5 | ||
|
|
ddc003eb13 | ||
|
|
e81a22dd10 | ||
|
|
269cb2967c | ||
|
|
6b764ae61b | ||
|
|
44c89e6559 | ||
|
|
a541a2dac2 | ||
|
|
ff7baa0a24 | ||
|
|
623d05da91 | ||
|
|
8601288230 | ||
|
|
fa11926642 | ||
|
|
9c8ec7fb0d | ||
|
|
78c9bd1d61 | ||
|
|
9c19bd7b6b | ||
|
|
de2fd2bdc0 | ||
|
|
81235e3ce6 | ||
|
|
4f7b2fcd17 | ||
|
|
e13fa186e1 | ||
|
|
f238940622 | ||
|
|
7f4b8cab8a |
@@ -61,10 +61,10 @@
|
||||
hx-get="/country"
|
||||
hx-target="body"
|
||||
hx-push-url="true"
|
||||
class="md:w-1/4 md:min-w-max"
|
||||
class="contents"
|
||||
>
|
||||
<div
|
||||
class="flex flex-row items-center bg-white shadow-md h-14 md:h-16 rounded-lg md:w-full dark:bg-dark-blue"
|
||||
class="md:w-1/4 md:min-w-max flex flex-row items-center bg-white shadow-md h-14 md:h-16 rounded-lg dark:bg-dark-blue"
|
||||
>
|
||||
<!-- <p class="w-16 grid place-content-center">IC</p> -->
|
||||
<svg
|
||||
@@ -87,11 +87,9 @@
|
||||
name="countryName"
|
||||
list="country.nameViews-list"
|
||||
placeholder="Search for a country..."
|
||||
th:hx-get="@{~/}"
|
||||
hx-trigger="keyup changed delay:500ms"
|
||||
hx-select="#countries-main-list"
|
||||
hx-post="/countries-cards"
|
||||
hx-target="#countries-main-list"
|
||||
hx-swap="outerHTML"
|
||||
hx-trigger="keyup changed delay:500ms"
|
||||
/>
|
||||
</div>
|
||||
<datalist id="country.nameViews-list">
|
||||
@@ -101,12 +99,10 @@
|
||||
value=""
|
||||
></option>
|
||||
</datalist>
|
||||
</form>
|
||||
<form>
|
||||
<select
|
||||
name="region"
|
||||
class="block w-62 mt-1 h-14 md:h-16 w-64 bg-white shadow-lg rounded-md shadow-sm focus:outline-none px-7 dark:bg-dark-blue"
|
||||
hx-get="/countries-cards"
|
||||
hx-post="/countries-cards"
|
||||
hx-target="#countries-main-list"
|
||||
>
|
||||
<option
|
||||
|
||||
@@ -64,6 +64,15 @@ case class Routes(countries: List[Country])(implicit
|
||||
)
|
||||
}
|
||||
|
||||
@cask.postForm("/countries-cards")
|
||||
def postPageOfCountriesCards(
|
||||
region: cask.FormValue,
|
||||
countryName: cask.FormValue
|
||||
) = {
|
||||
println(s"in get for countries cards with $region and $countryName")
|
||||
getPageOfCountriesCards(Some(region.value), 0, Some(countryName.value))
|
||||
}
|
||||
|
||||
/** this method returns directly set of cards and new anchor for loading next
|
||||
* page of cards
|
||||
*
|
||||
@@ -98,7 +107,7 @@ case class Routes(countries: List[Country])(implicit
|
||||
context
|
||||
)
|
||||
// this is to store switch to another region in the history
|
||||
val newUrl = s"/?region=${region.getOrElse("")}"
|
||||
val newUrl = s"/?region=${region.getOrElse("")}&countryName=${countryName.getOrElse("")}"
|
||||
// only save url when new region is requested, not on addtional card loads
|
||||
val urlHeaderOpt = if (page == 0) Seq("HX-Push" -> newUrl) else Seq.empty
|
||||
Response(
|
||||
@@ -140,7 +149,7 @@ case class Routes(countries: List[Country])(implicit
|
||||
}
|
||||
|
||||
@cask.get("/country")
|
||||
def getCountryPage(countryName: String) = {
|
||||
def getCountryPage(countryName: String, region: Option[String] = None) = {
|
||||
val context = new Context()
|
||||
countries.find(_.name.common == countryName) match {
|
||||
case Some(selectedCountry) =>
|
||||
|
||||
2
17-results-summary-component-go/.gitignore
vendored
@@ -14,3 +14,5 @@
|
||||
# Avoid your project being littered with annoying .DS_Store files!
|
||||
.DS_Store
|
||||
.prettierignore
|
||||
|
||||
public/out.css
|
||||
|
||||
96
17-results-summary-component-go/README.org
Normal file
@@ -0,0 +1,96 @@
|
||||
* Frontend Mentor - Results summary component solution
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: frontend-mentor---results-summary-component-solution
|
||||
:END:
|
||||
This is a solution to the
|
||||
[[https://www.frontendmentor.io/challenges/results-summary-component-CE_K6s0maV][Results
|
||||
summary component challenge on Frontend Mentor]]. Frontend Mentor
|
||||
challenges help you improve your coding skills by building realistic
|
||||
projects.
|
||||
|
||||
** Overview
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: overview
|
||||
:END:
|
||||
*** The challenge
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: the-challenge
|
||||
:END:
|
||||
Users should be able to:
|
||||
|
||||
- View the optimal layout for the interface depending on their device's
|
||||
screen size
|
||||
- See hover and focus states for all interactive elements on the page
|
||||
- *Bonus*: Use the local JSON data to dynamically populate the content
|
||||
|
||||
*** Screenshot
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: screenshot
|
||||
:END:
|
||||
[[screenshot-mobile.png]]
|
||||
[[screenshot-desktop.png]]
|
||||
|
||||
*** Links
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: links
|
||||
:END:
|
||||
- Solution URL: https://github.com/efim/Learning-HTMX/tree/master/17-results-summary-component-go
|
||||
- Live Site URL: https://efim-frontentmentor-results-summary-go.onrender.com/
|
||||
|
||||
** My process
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: my-process
|
||||
:END:
|
||||
*** Built with
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: built-with
|
||||
:END:
|
||||
- Semantic HTML5 markup
|
||||
- TailwindCSS
|
||||
- Flexbox
|
||||
- CSS Grid
|
||||
- Mobile-first workflow
|
||||
- Go Server Side Rendering
|
||||
- Nix for building and docker image creation
|
||||
|
||||
*** What I learned
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: what-i-learned
|
||||
:END:
|
||||
**** restarting server of file watch
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: restarting-server-of-file-watch
|
||||
:END:
|
||||
go doesn't have built in restart on file watch, and there are separate programs that do that
|
||||
|
||||
#+begin_src bash
|
||||
wgo -verbose -file .go -file .gohtml -file tailwind.config.js tailwindcss -i ./input.css -o public/out.css :: go run main.go -p 1234
|
||||
#+end_src
|
||||
|
||||
and doing tailwind first, because server embeds the output css file, so
|
||||
it's needed for the build step
|
||||
**** setting up static routes with default go web server
|
||||
**** generating template and returning it as response of go route
|
||||
**** embedding files into the binary for portability
|
||||
**** using css variables to set custom colors in TailwindCSS
|
||||
**** getting cli arguments in the go program
|
||||
|
||||
*** Continued development
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: continued-development
|
||||
:END:
|
||||
Further things to learn: getting data from forms, general go things - coroutines, utilizing sqlite, embedding PocketBase
|
||||
|
||||
*** Useful resources
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: useful-resources
|
||||
:END:
|
||||
- https://pkg.go.dev/text/template#example-Template-Block
|
||||
main go templating documentation
|
||||
- https://pkg.go.dev/net/http
|
||||
main default server documentation
|
||||
- https://nixos.wiki/wiki/Go - packaging Go
|
||||
- https://golangdocs.com/command-line-arguments-in-golang
|
||||
command line arguments
|
||||
|
||||
|
||||
@@ -7,6 +7,11 @@ in rec {
|
||||
inherit pname version;
|
||||
src = pkgs.nix-gitignore.gitignoreSource [ ] ./.;
|
||||
vendorHash = null; # set to "" when get dependencies in go.mod
|
||||
|
||||
# Adding the Tailwind build step to preBuild
|
||||
preBuild = ''
|
||||
${pkgs.nodePackages.tailwindcss}/bin/tailwindcss -i ./input.css -o public/out.css
|
||||
'';
|
||||
};
|
||||
image = pkgs.dockerTools.buildLayeredImage {
|
||||
name = pname;
|
||||
|
||||
@@ -1,855 +0,0 @@
|
||||
/*
|
||||
! tailwindcss v3.3.2 | MIT License | https://tailwindcss.com
|
||||
*/
|
||||
|
||||
/*
|
||||
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
||||
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
||||
*/
|
||||
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
box-sizing: border-box;
|
||||
/* 1 */
|
||||
border-width: 0;
|
||||
/* 2 */
|
||||
border-style: solid;
|
||||
/* 2 */
|
||||
border-color: #e5e7eb;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
::before,
|
||||
::after {
|
||||
--tw-content: '';
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use a consistent sensible line-height in all browsers.
|
||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
3. Use a more readable tab size.
|
||||
4. Use the user's configured `sans` font-family by default.
|
||||
5. Use the user's configured `sans` font-feature-settings by default.
|
||||
6. Use the user's configured `sans` font-variation-settings by default.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.5;
|
||||
/* 1 */
|
||||
-webkit-text-size-adjust: 100%;
|
||||
/* 2 */
|
||||
-moz-tab-size: 4;
|
||||
/* 3 */
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
/* 3 */
|
||||
font-family: HankenGrotesk, sans-serif;
|
||||
/* 4 */
|
||||
font-feature-settings: normal;
|
||||
/* 5 */
|
||||
font-variation-settings: normal;
|
||||
/* 6 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Remove the margin in all browsers.
|
||||
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
/* 1 */
|
||||
line-height: inherit;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Add the correct height in Firefox.
|
||||
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
||||
3. Ensure horizontal rules are visible by default.
|
||||
*/
|
||||
|
||||
hr {
|
||||
height: 0;
|
||||
/* 1 */
|
||||
color: inherit;
|
||||
/* 2 */
|
||||
border-top-width: 1px;
|
||||
/* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
abbr:where([title]) {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the default font size and weight for headings.
|
||||
*/
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Reset links to optimize for opt-in styling instead of opt-out.
|
||||
*/
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font weight in Edge and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use the user's configured `mono` font family by default.
|
||||
2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp,
|
||||
pre {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
/* 1 */
|
||||
font-size: 1em;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
||||
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||
3. Remove gaps between table borders by default.
|
||||
*/
|
||||
|
||||
table {
|
||||
text-indent: 0;
|
||||
/* 1 */
|
||||
border-color: inherit;
|
||||
/* 2 */
|
||||
border-collapse: collapse;
|
||||
/* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Change the font styles in all browsers.
|
||||
2. Remove the margin in Firefox and Safari.
|
||||
3. Remove default padding in all browsers.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit;
|
||||
/* 1 */
|
||||
font-size: 100%;
|
||||
/* 1 */
|
||||
font-weight: inherit;
|
||||
/* 1 */
|
||||
line-height: inherit;
|
||||
/* 1 */
|
||||
color: inherit;
|
||||
/* 1 */
|
||||
margin: 0;
|
||||
/* 2 */
|
||||
padding: 0;
|
||||
/* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the inheritance of text transform in Edge and Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the inability to style clickable types in iOS and Safari.
|
||||
2. Remove default button styles.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type='button'],
|
||||
[type='reset'],
|
||||
[type='submit'] {
|
||||
-webkit-appearance: button;
|
||||
/* 1 */
|
||||
background-color: transparent;
|
||||
/* 2 */
|
||||
background-image: none;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Use the modern Firefox focus style for all focusable elements.
|
||||
*/
|
||||
|
||||
:-moz-focusring {
|
||||
outline: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
||||
*/
|
||||
|
||||
:-moz-ui-invalid {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct vertical alignment in Chrome and Firefox.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/*
|
||||
Correct the cursor style of increment and decrement buttons in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-inner-spin-button,
|
||||
::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the odd appearance in Chrome and Safari.
|
||||
2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type='search'] {
|
||||
-webkit-appearance: textfield;
|
||||
/* 1 */
|
||||
outline-offset: -2px;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the inability to style clickable types in iOS and Safari.
|
||||
2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button;
|
||||
/* 1 */
|
||||
font: inherit;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct display in Chrome and Safari.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/*
|
||||
Removes the default spacing and border for appropriate elements.
|
||||
*/
|
||||
|
||||
blockquote,
|
||||
dl,
|
||||
dd,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
hr,
|
||||
figure,
|
||||
p,
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
menu {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent resizing textareas horizontally by default.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
||||
2. Set the default placeholder color to the user's configured gray 400 color.
|
||||
*/
|
||||
|
||||
input::-moz-placeholder, textarea::-moz-placeholder {
|
||||
opacity: 1;
|
||||
/* 1 */
|
||||
color: #9ca3af;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
opacity: 1;
|
||||
/* 1 */
|
||||
color: #9ca3af;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Set the default cursor for buttons.
|
||||
*/
|
||||
|
||||
button,
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*
|
||||
Make sure disabled buttons don't get the pointer cursor.
|
||||
*/
|
||||
|
||||
:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
||||
This can trigger a poorly considered lint error in some tools but is included by design.
|
||||
*/
|
||||
|
||||
img,
|
||||
svg,
|
||||
video,
|
||||
canvas,
|
||||
audio,
|
||||
iframe,
|
||||
embed,
|
||||
object {
|
||||
display: block;
|
||||
/* 1 */
|
||||
vertical-align: middle;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
*/
|
||||
|
||||
img,
|
||||
video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* Make elements with the HTML hidden attribute stay hidden by default */
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:root {
|
||||
--summary-item-color-var: 0deg 100% 67%
|
||||
|
||||
}
|
||||
|
||||
*, ::before, ::after {
|
||||
--tw-border-spacing-x: 0;
|
||||
--tw-border-spacing-y: 0;
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-rotate: 0;
|
||||
--tw-skew-x: 0;
|
||||
--tw-skew-y: 0;
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
--tw-pan-x: ;
|
||||
--tw-pan-y: ;
|
||||
--tw-pinch-zoom: ;
|
||||
--tw-scroll-snap-strictness: proximity;
|
||||
--tw-gradient-from-position: ;
|
||||
--tw-gradient-via-position: ;
|
||||
--tw-gradient-to-position: ;
|
||||
--tw-ordinal: ;
|
||||
--tw-slashed-zero: ;
|
||||
--tw-numeric-figure: ;
|
||||
--tw-numeric-spacing: ;
|
||||
--tw-numeric-fraction: ;
|
||||
--tw-ring-inset: ;
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
--tw-blur: ;
|
||||
--tw-brightness: ;
|
||||
--tw-contrast: ;
|
||||
--tw-grayscale: ;
|
||||
--tw-hue-rotate: ;
|
||||
--tw-invert: ;
|
||||
--tw-saturate: ;
|
||||
--tw-sepia: ;
|
||||
--tw-drop-shadow: ;
|
||||
--tw-backdrop-blur: ;
|
||||
--tw-backdrop-brightness: ;
|
||||
--tw-backdrop-contrast: ;
|
||||
--tw-backdrop-grayscale: ;
|
||||
--tw-backdrop-hue-rotate: ;
|
||||
--tw-backdrop-invert: ;
|
||||
--tw-backdrop-opacity: ;
|
||||
--tw-backdrop-saturate: ;
|
||||
--tw-backdrop-sepia: ;
|
||||
}
|
||||
|
||||
::backdrop {
|
||||
--tw-border-spacing-x: 0;
|
||||
--tw-border-spacing-y: 0;
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-rotate: 0;
|
||||
--tw-skew-x: 0;
|
||||
--tw-skew-y: 0;
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
--tw-pan-x: ;
|
||||
--tw-pan-y: ;
|
||||
--tw-pinch-zoom: ;
|
||||
--tw-scroll-snap-strictness: proximity;
|
||||
--tw-gradient-from-position: ;
|
||||
--tw-gradient-via-position: ;
|
||||
--tw-gradient-to-position: ;
|
||||
--tw-ordinal: ;
|
||||
--tw-slashed-zero: ;
|
||||
--tw-numeric-figure: ;
|
||||
--tw-numeric-spacing: ;
|
||||
--tw-numeric-fraction: ;
|
||||
--tw-ring-inset: ;
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
--tw-blur: ;
|
||||
--tw-brightness: ;
|
||||
--tw-contrast: ;
|
||||
--tw-grayscale: ;
|
||||
--tw-hue-rotate: ;
|
||||
--tw-invert: ;
|
||||
--tw-saturate: ;
|
||||
--tw-sepia: ;
|
||||
--tw-drop-shadow: ;
|
||||
--tw-backdrop-blur: ;
|
||||
--tw-backdrop-brightness: ;
|
||||
--tw-backdrop-contrast: ;
|
||||
--tw-backdrop-grayscale: ;
|
||||
--tw-backdrop-hue-rotate: ;
|
||||
--tw-backdrop-invert: ;
|
||||
--tw-backdrop-opacity: ;
|
||||
--tw-backdrop-saturate: ;
|
||||
--tw-backdrop-sepia: ;
|
||||
}
|
||||
|
||||
.fixed {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.inset-x-0 {
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.bottom-1 {
|
||||
bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.h-12 {
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.h-32 {
|
||||
height: 8rem;
|
||||
}
|
||||
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.h-screen {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.w-32 {
|
||||
width: 8rem;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.w-screen {
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.place-items-center {
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.gap-y-3 {
|
||||
row-gap: 0.75rem;
|
||||
}
|
||||
|
||||
.gap-y-4 {
|
||||
row-gap: 1rem;
|
||||
}
|
||||
|
||||
.rounded-full {
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.rounded-lg {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.rounded-b-3xl {
|
||||
border-bottom-right-radius: 1.5rem;
|
||||
border-bottom-left-radius: 1.5rem;
|
||||
}
|
||||
|
||||
.bg-dark-gray-blue {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: hsl(224 30% 27% / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-summary-item-color\/5 {
|
||||
background-color: hsl(var(--summary-item-color-var) / 0.05);
|
||||
}
|
||||
|
||||
.bg-gradient-to-t {
|
||||
background-image: linear-gradient(to top, var(--tw-gradient-stops));
|
||||
}
|
||||
|
||||
.from-light-royal-blue {
|
||||
--tw-gradient-from: hsl(241, 81%, 54%) var(--tw-gradient-from-position);
|
||||
--tw-gradient-to: hsl(241 81% 54% / 0) var(--tw-gradient-to-position);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
|
||||
.from-violet-blue\/60 {
|
||||
--tw-gradient-from: hsla(256, 72%, 46%, 0.6) var(--tw-gradient-from-position);
|
||||
--tw-gradient-to: hsla(256, 72%, 46%, 0) var(--tw-gradient-to-position);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
|
||||
.from-50\% {
|
||||
--tw-gradient-from-position: 50%;
|
||||
}
|
||||
|
||||
.to-light-slate-blue {
|
||||
--tw-gradient-to: hsl(252, 100%, 67%) var(--tw-gradient-to-position);
|
||||
}
|
||||
|
||||
.to-persian-blue\/75 {
|
||||
--tw-gradient-to: hsla(241, 72%, 46%, 0.75) var(--tw-gradient-to-position);
|
||||
}
|
||||
|
||||
.p-7 {
|
||||
padding: 1.75rem;
|
||||
}
|
||||
|
||||
.px-10 {
|
||||
padding-left: 2.5rem;
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
|
||||
.px-5 {
|
||||
padding-left: 1.25rem;
|
||||
padding-right: 1.25rem;
|
||||
}
|
||||
|
||||
.pb-8 {
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.pr-2 {
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.pt-4 {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-5xl {
|
||||
font-size: 3rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.text-xl {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.font-extrabold {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.text-dark-gray-blue {
|
||||
--tw-text-opacity: 1;
|
||||
color: hsl(224 30% 27% / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-pale-blue {
|
||||
--tw-text-opacity: 1;
|
||||
color: hsl(221 100% 96% / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-summary-item-color {
|
||||
--tw-text-opacity: 1;
|
||||
color: hsl(var(--summary-item-color-var) / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-white {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'HankenGrotesk';
|
||||
|
||||
src: url('/static/public/fonts/static/HankenGrotesk-Medium.ttf') format('truetype');
|
||||
|
||||
font-weight: 400;
|
||||
|
||||
/* Regular */
|
||||
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'HankenGrotesk';
|
||||
|
||||
src: url('/static/public/fonts/static/HankenGrotesk-Bold.ttf') format('truetype');
|
||||
|
||||
font-weight: 700;
|
||||
|
||||
/* Bold */
|
||||
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'HankenGrotesk';
|
||||
|
||||
src: url('/static/public/fonts/static/HankenGrotesk-ExtraBold.ttf') format('truetype');
|
||||
|
||||
font-weight: 800;
|
||||
|
||||
/* ExtraBold */
|
||||
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.md\:h-44 {
|
||||
height: 11rem;
|
||||
}
|
||||
|
||||
.md\:h-\[500px\] {
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.md\:w-1\/2 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.md\:w-44 {
|
||||
width: 11rem;
|
||||
}
|
||||
|
||||
.md\:w-\[715px\] {
|
||||
width: 715px;
|
||||
}
|
||||
|
||||
.md\:flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.md\:justify-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.md\:rounded-3xl {
|
||||
border-radius: 1.5rem;
|
||||
}
|
||||
|
||||
.md\:bg-white {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.md\:px-8 {
|
||||
padding-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
.md\:pt-2 {
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
.md\:text-2xl {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
.md\:text-6xl {
|
||||
font-size: 3.75rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.md\:text-base {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
.md\:text-xl {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.md\:shadow-2xl {
|
||||
--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
|
||||
--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.md\:shadow-light-lavender\/50 {
|
||||
--tw-shadow-color: hsl(241 100% 89% / 0.5);
|
||||
--tw-shadow: var(--tw-shadow-colored);
|
||||
}
|
||||
}
|
||||
BIN
17-results-summary-component-go/screenshot-desktop.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
17-results-summary-component-go/screenshot-mobile.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
1
18-expenses-chart/.envrc
Normal file
@@ -0,0 +1 @@
|
||||
use flake
|
||||
5
18-expenses-chart/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/.direnv/
|
||||
/.go/
|
||||
*~
|
||||
*_templ.go
|
||||
static/output.css
|
||||
28
18-expenses-chart/Makefile
Normal file
@@ -0,0 +1,28 @@
|
||||
# List of all .templ files
|
||||
TEMPL_FILES := $(shell find templates -type f -name '*.templ')
|
||||
|
||||
# Name of the generated file(s) from templ
|
||||
GENERATED_FILES := $(TEMPL_FILES:.templ=_templ.go)
|
||||
|
||||
# Generate Go files from .templ files only if they have changed
|
||||
$(GENERATED_FILES): $(TEMPL_FILES)
|
||||
templ generate
|
||||
|
||||
TAILWIND_CONFIG := tailwind.config.js
|
||||
INPUT_CSS := input.css
|
||||
OUTPUT_CSS := static/output.css
|
||||
|
||||
# generate tailwindcss output
|
||||
$(OUTPUT_CSS): $(TEMPL_FILES) $(TAILWIND_CONFIG) $(INPUT_CSS)
|
||||
tailwindcss -i $(INPUT_CSS) -o $(OUTPUT_CSS)
|
||||
|
||||
# Run the server, with dependencies on templ and tailwind generation
|
||||
run: $(GENERATED_FILES) $(OUTPUT_CSS)
|
||||
go run .
|
||||
|
||||
# this uses wgo to re-execute 'make run'
|
||||
# which will toggle templ generate, when needed
|
||||
# and then do `go run .` to rebuild all else
|
||||
.PHONY: run/live
|
||||
run/live:
|
||||
wgo -verbose -file=.go -file=.templ -file=$(INPUT_CSS) -file=$(TAILWIND_CONFIG) make run
|
||||
@@ -1,6 +1,6 @@
|
||||
# Frontend Mentor - Results summary component solution
|
||||
# Frontend Mentor - Expenses chart component solution
|
||||
|
||||
This is a solution to the [Results summary component challenge on Frontend Mentor](https://www.frontendmentor.io/challenges/results-summary-component-CE_K6s0maV). Frontend Mentor challenges help you improve your coding skills by building realistic projects.
|
||||
This is a solution to the [Expenses chart component challenge on Frontend Mentor](https://www.frontendmentor.io/challenges/expenses-chart-component-e7yJBUdjwt). Frontend Mentor challenges help you improve your coding skills by building realistic projects.
|
||||
|
||||
## Table of contents
|
||||
|
||||
@@ -24,9 +24,11 @@ This is a solution to the [Results summary component challenge on Frontend Mento
|
||||
|
||||
Users should be able to:
|
||||
|
||||
- View the optimal layout for the interface depending on their device's screen size
|
||||
- See hover and focus states for all interactive elements on the page
|
||||
- **Bonus**: Use the local JSON data to dynamically populate the content
|
||||
- View the bar chart and hover over the individual bars to see the correct amounts for each day
|
||||
- See the current day’s bar highlighted in a different colour to the other bars
|
||||
- View the optimal layout for the content depending on their device’s screen size
|
||||
- See hover states for all interactive elements on the page
|
||||
- **Bonus**: Use the JSON data file provided to dynamically size the bars on the chart
|
||||
|
||||
### Screenshot
|
||||
|
||||
@@ -62,13 +64,6 @@ Then crop/optimize/edit your image however you like, add it to your project, and
|
||||
|
||||
### What I learned
|
||||
|
||||
#### restarting server of file watch
|
||||
|
||||
wgo -verbose -file .go -file .gohtml -file tailwind.config.js echo reloading :: bash -c 'tailwindcss -i ./input.css -o public/out.css' :: go run main.go
|
||||
|
||||
and doing tailwind first, because server embeds the output css file, so it's needed for the build step
|
||||
|
||||
#### sample
|
||||
Use this section to recap over some of your major learnings while working through this project. Writing these out and providing code samples of areas you want to highlight is a great way to reinforce your own knowledge.
|
||||
|
||||
To see how you can add code snippets, see below:
|
||||
@@ -1,6 +1,6 @@
|
||||
# Frontend Mentor - Results summary component
|
||||
# Frontend Mentor - Expenses chart component
|
||||
|
||||

|
||||

|
||||
|
||||
## Welcome! 👋
|
||||
|
||||
@@ -8,23 +8,25 @@ Thanks for checking out this front-end coding challenge.
|
||||
|
||||
[Frontend Mentor](https://www.frontendmentor.io) challenges help you improve your coding skills by building realistic projects.
|
||||
|
||||
**To do this challenge, you need a basic understanding of HTML and CSS.**
|
||||
**To do this challenge, you need a decent understanding of HTML, CSS and JavaScript.**
|
||||
|
||||
## The challenge
|
||||
|
||||
Your challenge is to build out this results summary component and get it looking as close to the design as possible.
|
||||
Your challenge is to build out this bar chart component and get it looking as close to the design as possible.
|
||||
|
||||
You can use any tools you like to help you complete the challenge. So if you've got something you'd like to practice, feel free to give it a go.
|
||||
|
||||
We provide the data for the results in a local `data.json` file. So you can use that to add the results and total score dynamically if you choose.
|
||||
We provide the data for the chart in a local `data.json` file. So you can use that to dynamically add the bars if you choose.
|
||||
|
||||
Your users should be able to:
|
||||
|
||||
- View the optimal layout for the interface depending on their device's screen size
|
||||
- See hover and focus states for all interactive elements on the page
|
||||
- **Bonus**: Use the local JSON data to dynamically populate the content
|
||||
- View the bar chart and hover over the individual bars to see the correct amounts for each day
|
||||
- See the current day's bar highlighted in a different colour to the other bars
|
||||
- View the optimal layout for the content depending on their device's screen size
|
||||
- See hover states for all interactive elements on the page
|
||||
- **Bonus**: See dynamically generated bars based on the data provided in the local JSON file
|
||||
|
||||
Want some support on the challenge? [Join our Slack community](https://www.frontendmentor.io/slack) and ask questions in the **#help** channel.
|
||||
Want some support on the challenge? [Join our community](https://www.frontendmentor.io/community) and ask questions in the **#help** channel.
|
||||
|
||||
## Where to find everything
|
||||
|
||||
@@ -34,9 +36,7 @@ The designs are in JPG static format. Using JPGs will mean that you'll need to u
|
||||
|
||||
If you would like the design files (we provide Sketch & Figma versions) to inspect the design in more detail, you can [subscribe as a PRO member](https://www.frontendmentor.io/pro).
|
||||
|
||||
All the required assets for this project are in the `/assets` folder. The images are already exported for the correct screen size and optimized.
|
||||
|
||||
We also include variable and static font files for the required fonts for this project. You can choose to either link to Google Fonts or use the local font files to host the fonts yourself. Note that we've removed the static font files for the font weights that aren't needed for this project.
|
||||
You will find all the required assets in the `/images` folder. The assets are already optimized.
|
||||
|
||||
There is also a `style-guide.md` file containing the information you'll need, such as color palette and fonts.
|
||||
|
||||
@@ -53,7 +53,7 @@ Feel free to use any workflow that you feel comfortable with. Below is a suggest
|
||||
|
||||
## Deploying your project
|
||||
|
||||
As mentioned above, there are many ways to host your project for free. Our recommend hosts are:
|
||||
As mentioned above, there are many ways to host your project for free. Our recommended hosts are:
|
||||
|
||||
- [GitHub Pages](https://pages.github.com/)
|
||||
- [Vercel](https://vercel.com/)
|
||||
@@ -79,7 +79,7 @@ Remember, if you're looking for feedback on your solution, be sure to ask questi
|
||||
|
||||
There are multiple places you can share your solution:
|
||||
|
||||
1. Share your solution page in the **#finished-projects** channel of the [Slack community](https://www.frontendmentor.io/slack).
|
||||
1. Share your solution page in the **#finished-projects** channel of the [community](https://www.frontendmentor.io/community).
|
||||
2. Tweet [@frontendmentor](https://twitter.com/frontendmentor) and mention **@frontendmentor**, including the repo and live URLs in the tweet. We'd love to take a look at what you've built and help share it around.
|
||||
3. Share your solution on other social channels like LinkedIn.
|
||||
4. Blog about your experience building your project. Writing about your workflow, technical choices, and talking through your code is a brilliant way to reinforce what you've learned. Great platforms to write on are [dev.to](https://dev.to/), [Hashnode](https://hashnode.com/), and [CodeNewbie](https://community.codenewbie.org/).
|
||||
30
18-expenses-chart/data.json
Normal file
@@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"day": "mon",
|
||||
"amount": 17.45
|
||||
},
|
||||
{
|
||||
"day": "tue",
|
||||
"amount": 34.91
|
||||
},
|
||||
{
|
||||
"day": "wed",
|
||||
"amount": 52.36
|
||||
},
|
||||
{
|
||||
"day": "thu",
|
||||
"amount": 31.07
|
||||
},
|
||||
{
|
||||
"day": "fri",
|
||||
"amount": 23.39
|
||||
},
|
||||
{
|
||||
"day": "sat",
|
||||
"amount": 43.28
|
||||
},
|
||||
{
|
||||
"day": "sun",
|
||||
"amount": 25.48
|
||||
}
|
||||
]
|
||||
BIN
18-expenses-chart/design/active-states.jpg
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
18-expenses-chart/design/desktop-design.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
18-expenses-chart/design/desktop-preview.jpg
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
18-expenses-chart/design/mobile-design.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
211
18-expenses-chart/flake.lock
generated
Normal file
@@ -0,0 +1,211 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709126324,
|
||||
"narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "d465f4819400de7c8d874d50b982301f28a84605",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694529238,
|
||||
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_3": {
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"templ",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694102001,
|
||||
"narHash": "sha256-vky6VPK1n1od6vXbqzOXnekrQpTL4hbPAwUhT5J9c9E=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "9e21c80adf67ebcb077d75bd5e7d724d21eeafd6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gomod2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": [
|
||||
"templ",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1705314449,
|
||||
"narHash": "sha256-yfQQ67dLejP0FLK76LKHbkzcQqNIrux6MFe32MMFGNQ=",
|
||||
"owner": "nix-community",
|
||||
"repo": "gomod2nix",
|
||||
"rev": "30e3c3a9ec4ac8453282ca7f67fca9e1da12c3e6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "gomod2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1710097495,
|
||||
"narHash": "sha256-B7Ea7q7hU7SE8wOPJ9oXEBjvB89yl2csaLjf5v/7jr8=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d40e866b1f98698d454dad8f592fe7616ff705a4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1701282334,
|
||||
"narHash": "sha256-MxCVrXY6v4QmfTwIysjjaX0XUhqBbxTWWB4HXtDYsdk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "057f9aecfb71c4437d2b27d3323df7f93c010b7e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "23.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"templ": "templ"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"templ": {
|
||||
"inputs": {
|
||||
"gitignore": "gitignore",
|
||||
"gomod2nix": "gomod2nix",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"xc": "xc"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709917943,
|
||||
"narHash": "sha256-zDQxUFSmG/VX+xtK+nZ3ObRMVcMjjx+EUAxHLNcHHF8=",
|
||||
"owner": "a-h",
|
||||
"repo": "templ",
|
||||
"rev": "df2a32403bb4a8e4745ac10ddc8b3e77386d8045",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "a-h",
|
||||
"repo": "templ",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"xc": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_3",
|
||||
"nixpkgs": [
|
||||
"templ",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1703164129,
|
||||
"narHash": "sha256-kCcCqqwvjN07H8FPG4tXsRVRcMqT8dUNt9pwW1kKAe8=",
|
||||
"owner": "joerdav",
|
||||
"repo": "xc",
|
||||
"rev": "0655cccfcf036556aeaddfb8f45dc7e8dd1b3680",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "joerdav",
|
||||
"repo": "xc",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
80
18-expenses-chart/flake.nix
Normal file
@@ -0,0 +1,80 @@
|
||||
rec {
|
||||
description = "expenses chart exercise";
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
templ.url = "github:a-h/templ";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, templ }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
templPkg = templ.packages.${system}.templ;
|
||||
pname = "expenses-chart";
|
||||
version = "0.0.1";
|
||||
in rec {
|
||||
thePackage = pkgs.buildGoModule {
|
||||
inherit pname version;
|
||||
src = pkgs.nix-gitignore.gitignoreSource [ ] ./.;
|
||||
vendorHash = "sha256-sKuP3TsRD3MXBGtdSRnX62eXBQE1ASWFQu0kLlXSlFA=";
|
||||
|
||||
preBuild = ''
|
||||
# Adding the Tailwind build step to preBuild
|
||||
${pkgs.nodePackages.tailwindcss}/bin/tailwindcss -i ./input.css -o static/output.css
|
||||
# Adding generation of go code from templ files
|
||||
${templPkg}/bin/templ generate
|
||||
'';
|
||||
|
||||
};
|
||||
packages = rec {
|
||||
"${pname}" = thePackage;
|
||||
"${pname}-image" = pkgs.dockerTools.buildLayeredImage {
|
||||
name = pname;
|
||||
tag = "latest";
|
||||
created = "now";
|
||||
config = {
|
||||
Cmd = [ "${thePackage}/bin/templ-exercise" ];
|
||||
ExposedPorts = { "3000/tcp" = { }; };
|
||||
};
|
||||
};
|
||||
image-hello = pkgs.dockerTools.buildLayeredImage { # so, wow, this works
|
||||
name = pname;
|
||||
tag = "latest";
|
||||
config.Cmd = [ "${pkgs.hello}/bin/hello" ];
|
||||
};
|
||||
|
||||
image = pkgs.dockerTools.buildImage {
|
||||
name = pname;
|
||||
tag = "latest";
|
||||
created = "now";
|
||||
copyToRoot = pkgs.buildEnv {
|
||||
name = "image-root";
|
||||
paths = [ thePackage pkgs.dockerTools.binSh pkgs.coreutils ];
|
||||
pathsToLink = [ "/bin" "/dist" "/public" ];
|
||||
};
|
||||
config = {
|
||||
Cmd = [ "/bin/templ-exercise" ];
|
||||
ExposedPorts = { "8080/tcp" = { }; };
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.go
|
||||
pkgs.wgo
|
||||
pkgs.semgrep
|
||||
pkgs.gopls
|
||||
pkgs.gnumake
|
||||
templPkg
|
||||
pkgs.nodePackages.tailwindcss
|
||||
];
|
||||
shellHook = ''
|
||||
export GOPATH=$PWD/.go
|
||||
export PATH=$GOPATH/bin:$PATH
|
||||
'';
|
||||
};
|
||||
});
|
||||
# see https://serokell.io/blog/practical-nix-flakes
|
||||
}
|
||||
0
18-expenses-chart/flake.nix~
Normal file
5
18-expenses-chart/go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module sunshine.industries/temp-exercise
|
||||
|
||||
go 1.21.7
|
||||
|
||||
require github.com/a-h/templ v0.2.598
|
||||
4
18-expenses-chart/go.sum
Normal file
@@ -0,0 +1,4 @@
|
||||
github.com/a-h/templ v0.2.598 h1:6jMIHv6wQZvdPxTuv87erW4RqN/FPU0wk7ZHN5wVuuo=
|
||||
github.com/a-h/templ v0.2.598/go.mod h1:SA7mtYwVEajbIXFRh3vKdYm/4FYyLQAtPH1+KxzGPA8=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
7
18-expenses-chart/input.css
Normal file
@@ -0,0 +1,7 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html {
|
||||
font-size: 18px;
|
||||
}
|
||||
33
18-expenses-chart/main.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
|
||||
"sunshine.industries/temp-exercise/templates"
|
||||
)
|
||||
|
||||
func randomDailyExpense() float32 {
|
||||
return 10 + rand.Float32()*100
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
component := templates.IndexPage(templates.PageData{
|
||||
Balance: 500 + rand.Float32()*1000,
|
||||
Expenses: []float32{randomDailyExpense(), randomDailyExpense(), randomDailyExpense(), randomDailyExpense(), randomDailyExpense(), randomDailyExpense(), randomDailyExpense()},
|
||||
TotalThisMonth: 50 + rand.Float32()*600,
|
||||
PercentComparedToLastMonth: -5 + 10*rand.Float32(),
|
||||
})
|
||||
component.Render(context.Background(), w)
|
||||
})
|
||||
|
||||
staticFs := http.FileServer(http.Dir("./static"))
|
||||
http.Handle("/static/", http.StripPrefix("/static/", staticFs))
|
||||
|
||||
fmt.Println("starting to serve on :3000")
|
||||
http.ListenAndServe(":3000", nil)
|
||||
}
|
||||
107
18-expenses-chart/notes.org
Normal file
@@ -0,0 +1,107 @@
|
||||
* new exersice after long time
|
||||
now will be learning https://github.com/a-h/templ
|
||||
it's a templating solution for go, more similar to jsx
|
||||
but it also needs a pre-processing step, so i'm adding it like tailwind
|
||||
|
||||
with Makefile for make run/live
|
||||
and will need to add as a step in project build
|
||||
|
||||
* setting up
|
||||
https://git.sunshine.industries/efim/golang-templ-and-tailwind
|
||||
have separate repo, but i guess i'll remove .git from this dir,
|
||||
to have single commits across Learning HTMX?
|
||||
|
||||
or maybe magit will just allow for simple integration?
|
||||
just not pushing into origin, committing, and then committing from overall repository?
|
||||
|
||||
** not so simple, but still quite OK!
|
||||
|
||||
there's now 'subtree' which is separate concept from submodule,
|
||||
and it is maybe simpler:
|
||||
|
||||
https://magit.vc/manual/magit/Subtree.html
|
||||
|
||||
i'm using the repo url
|
||||
ssh://gitea@git.sunshine.industries:65433/efim/golang-templ-and-tailwind.git
|
||||
|
||||
and the command magit subtree add,
|
||||
specify the directory, and it gets embedded it seems.
|
||||
i have one level magit dispatch and my notes would be committed to the main repo,
|
||||
i would potentially be able to pull in commits from the parent?
|
||||
|
||||
and maybe somehow contribute, but yeah, this is still confusing 🙂
|
||||
how would i commit to subtree upstream if there are conflicting changes?
|
||||
maybe i'll just get denied, as simple as that.
|
||||
|
||||
allright! let's add exercise specific data (again)
|
||||
|
||||
* so, starting dev
|
||||
|
||||
old process:
|
||||
1. open second browser and open png with style
|
||||
first mobile
|
||||
2. enable view C-S-m in firefox
|
||||
the responsive mode
|
||||
|
||||
i suppose next is adding colors to tailwind configuration?
|
||||
from the styleguide file
|
||||
|
||||
also
|
||||
3. check desktop style, whether any big rearrangements are required
|
||||
so that i'd start mobile with way to rearrange things
|
||||
|
||||
do i have a separate note with all of these things?
|
||||
i've also used some commands to vertically center my stuff
|
||||
|
||||
** DONE allright, tailwind config go
|
||||
font, size, colors
|
||||
|
||||
** well, i don't really want to go on.
|
||||
but yeah, i'll need hierarchy of the elements.
|
||||
|
||||
** ok. thoughs:
|
||||
the componets would be
|
||||
- my balance bubble
|
||||
because it is spacially separate
|
||||
- spending summary
|
||||
- graph inside spending summary
|
||||
or maybe don't even need that
|
||||
|
||||
the component should take up all space provided to it.
|
||||
if parent would want to add space around - that's easy to do,
|
||||
but if component insists on adding space around itself - harder to reuse?
|
||||
because then if parent would want to have child take up all space - negative padding?
|
||||
|
||||
*** allright, @ to include sub templates
|
||||
|
||||
** well, last part? preparing the docker container for deployment on Render.com?
|
||||
|
||||
which port should be used?
|
||||
also, this would be first time i'll add templ as a build step
|
||||
|
||||
** for some reason image doesn't work well
|
||||
|
||||
podman run -d -p 9090:8080 localhost/expenses-chart:latest /nix/store/l4r6glmzbvkhg97lp4dn7nm76w6hz41g-expenses-chart-0.0.1/bin/temp-exercise
|
||||
|
||||
this runs,
|
||||
but podman run -it expenses-chart:latest
|
||||
Error: crun: executable file `/nix/store/l4r6glmzbvkhg97lp4dn7nm76w6hz41g-expenses-chart-0.0.1/bin/templ-exercise` not found in $PATH: No such file or directory: OCI runtime attempted to invoke a command that was not found
|
||||
|
||||
oh, well
|
||||
|
||||
** you know what - i give up
|
||||
|
||||
for some reason when i open url to app running in container,
|
||||
and it's on the 8080 port of the host machine - the static files are served
|
||||
|
||||
if i use any other port of local machine, to forward into container,
|
||||
the static files are not served
|
||||
|
||||
maybe this is bug in go? i don't know
|
||||
StripPrefix doesn't seem to do anything
|
||||
|
||||
and for some reason in this specific exercise CMD doesn't work in the container
|
||||
so let's just give up!
|
||||
|
||||
i could try to write a docker file and check with that, but yeah
|
||||
|
||||
BIN
18-expenses-chart/static/images/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
1
18-expenses-chart/static/images/logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="72" height="48" viewBox="0 0 72 48" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle fill="#382314" cx="48" cy="24" r="24"/><circle stroke="#FFF" stroke-width="2" cx="24" cy="24" r="23"/></g></svg>
|
||||
|
After Width: | Height: | Size: 238 B |
37
18-expenses-chart/style-guide.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Front-end Style Guide
|
||||
|
||||
## Layout
|
||||
|
||||
The designs were created to the following widths:
|
||||
|
||||
- Mobile: 375px
|
||||
- Desktop: 1440px
|
||||
|
||||
> 💡 These are just the design sizes. Ensure content is responsive and meets WCAG requirements by testing the full range of screen sizes from 320px to large screens.
|
||||
|
||||
## Colors
|
||||
|
||||
### Primary
|
||||
|
||||
- Soft red: hsl(10, 79%, 65%)
|
||||
- Cyan: hsl(186, 34%, 60%)
|
||||
|
||||
### Neutral
|
||||
|
||||
- Dark brown: hsl(25, 47%, 15%)
|
||||
- Medium brown: hsl(28, 10%, 53%)
|
||||
- Cream: hsl(27, 66%, 92%)
|
||||
- Very pale orange: hsl(33, 100%, 98%)
|
||||
|
||||
## Typography
|
||||
|
||||
### Body Copy
|
||||
|
||||
- Font size: 18px
|
||||
|
||||
### Font
|
||||
|
||||
- Family: [DM Sans](https://fonts.google.com/specimen/DM+Sans)
|
||||
- Weights: 400, 700
|
||||
|
||||
> 💎 [Upgrade to Pro](https://www.frontendmentor.io/pro?ref=style-guide) for design file access to see all design details and get hands-on experience using a professional workflow with tools like Figma.
|
||||
29
18-expenses-chart/tailwind.config.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./templates/index.templ"],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
'sans': ['DM Sans', 'sans-serif'],
|
||||
},
|
||||
fontWeight: {
|
||||
'normal': 400,
|
||||
'bold': 700,
|
||||
}
|
||||
},
|
||||
colors: {
|
||||
primary: {
|
||||
'soft-red': 'hsl(10, 79%, 65%)',
|
||||
cyan: 'hsl(186, 34%, 60%)',
|
||||
},
|
||||
neutral: {
|
||||
'dark-brown': 'hsl(25, 47%, 15%)',
|
||||
'medium-brown': 'hsl(28, 10%, 53%)',
|
||||
cream: 'hsl(27, 66%, 92%)',
|
||||
'very-pale-orange': 'hsl(33, 100%, 98%)',
|
||||
},
|
||||
}
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
133
18-expenses-chart/templates/index.templ
Normal file
@@ -0,0 +1,133 @@
|
||||
package templates
|
||||
|
||||
import "fmt"
|
||||
import "slices"
|
||||
|
||||
type PageData struct {
|
||||
Balance float32
|
||||
Expenses []float32
|
||||
TotalThisMonth float32
|
||||
PercentComparedToLastMonth float32
|
||||
}
|
||||
|
||||
templ myBalanceComponent(balance float32) {
|
||||
<div class="flex flex-row rounded-xl text-neutral-very-pale-orange text-xs md:text-base bg-primary-soft-red p-4 shadow md:rounded-[1rem] md:p-7">
|
||||
<div class="grow ">
|
||||
My balance
|
||||
<p class="text-xl md:text-2xl font-bold">${ fmt.Sprintf("%.2f", balance) }</p>
|
||||
</div>
|
||||
<img
|
||||
src="./static/images/logo.svg"
|
||||
alt="App logo: a white circle overlapping from the left a filled in black circle"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
var days []string = []string{"mon", "tue", "wed", "thu", "fri", "sat", "sun"}
|
||||
|
||||
// returns templ SummaryComponent
|
||||
// with all attributes computed from the expenses slice
|
||||
// the percentages of the columns, current day number, etc
|
||||
// NOTE: this seems to be the way to mix go calculations and templates
|
||||
func prepareSummaryComponent(expenses []float32, totalThisMonth, percentComparedToLastMonth float32) templ.Component {
|
||||
fmt.Println("hello, preparing expenses: ", expenses)
|
||||
max := slices.Max(expenses)
|
||||
percentages := make([]float32, 0, len(expenses))
|
||||
for _, price := range expenses {
|
||||
percentages = append(percentages, price/max)
|
||||
}
|
||||
|
||||
currentDayNum := 2
|
||||
|
||||
return spendingSummaryComponent(expenses, percentages, currentDayNum, percentComparedToLastMonth, totalThisMonth)
|
||||
}
|
||||
|
||||
css expenseBarVars(percentage float32) {
|
||||
--height-percentage: { fmt.Sprintf("%.2f", percentage) };
|
||||
height: calc(var(--height-percentage) * 8rem);
|
||||
}
|
||||
|
||||
templ dayExpenseColumn(expense, percentage float32, day string, isCurrentDay bool) {
|
||||
<div class="flex flex-col justify-end items-center h-32">
|
||||
<div
|
||||
class={ "rounded group relative w-full h-12",
|
||||
expenseBarVars(percentage),
|
||||
templ.KV("bg-primary-soft-red hover:bg-primary-soft-red/70", !isCurrentDay),
|
||||
templ.KV("bg-primary-cyan hover:bg-primary-cyan/70", isCurrentDay),
|
||||
"hover:cursor-pointer" }
|
||||
>
|
||||
<span class="absolute left-1/2 -translate-x-1/2 -translate-y-7 opacity-0 group-hover:opacity-100 bg-neutral-dark-brown text-neutral-cream rounded text-xs p-1">${ fmt.Sprintf("%.2f", expense ) }</span>
|
||||
</div>
|
||||
<p class="text-neutral-medium-brown text-xs py-2">{ day }</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
// The 7 vertical bars of the per-day expenses
|
||||
templ expensesChart(expenses, percentages []float32, currentDayNum int) {
|
||||
<div class="grid grid-cols-7 place-content-between gap-x-3">
|
||||
for i := 0; i < 7; i++ {
|
||||
@dayExpenseColumn(expenses[i], percentages[i], days[i], currentDayNum == i)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
// Big container with chard and total expenses of the week
|
||||
templ spendingSummaryComponent(expenses, percentages []float32, currentDayNum int, totalThisMonth, percentComparedToLastMonth float32) {
|
||||
<div class="bg-neutral-very-pale-orange rounded-xl shadow p-4 py-6 flex flex-col gap-y-4 md:p-7 md:px-10 md:rounded-[1rem] md:gap-y-5">
|
||||
<p2 class="text-neutral-dark-brown text-xl font-bold pb-6 md:text-2xl md:pb-12 ">Spending - Last 7 days</p2>
|
||||
@expensesChart(expenses, percentages, currentDayNum)
|
||||
<hr class="bg-neutral-cream border-t-0 h-0.5"/>
|
||||
<div class="grid grid-cols-2 text-sm text-neutral-medium-brown">
|
||||
<p class="col-span-full">Total this month</p>
|
||||
<div class="grow">
|
||||
<p class="grid items-center text-neutral-dark-brown text-2xl font-bold h-full md:text-4xl md:py-2">$ { fmt.Sprintf("%.2f", totalThisMonth) }</p>
|
||||
</div>
|
||||
<div class="text-right grid content-center">
|
||||
<div class="text-neutral-dark-brown font-bold ">
|
||||
if (percentComparedToLastMonth > 0) {
|
||||
<span class="inline">+</span>
|
||||
}
|
||||
{ fmt.Sprintf("%.2f", percentComparedToLastMonth) }%
|
||||
</div>
|
||||
from last month
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ WidgetsComponend(pageData PageData) {
|
||||
<div class="flex flex-col gap-4">
|
||||
@myBalanceComponent(pageData.Balance)
|
||||
@prepareSummaryComponent(pageData.Expenses, pageData.PercentComparedToLastMonth, pageData.TotalThisMonth)
|
||||
</div>
|
||||
}
|
||||
|
||||
templ IndexPage(pageData PageData) {
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" rel="stylesheet"/>
|
||||
<link rel="stylesheet" href="/styles/templ.css"/>
|
||||
<link href="/static/output.css" rel="stylesheet"/>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <!-- displays site properly based on user's device -->
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="./static/images/favicon-32x32.png"/>
|
||||
<title>Frontend Mentor | Expenses chart component</title>
|
||||
<!-- Feel free to remove these styles or customise in your own stylesheet 👍 -->
|
||||
<style>
|
||||
.attribution { font-size: 11px; text-align: center; }
|
||||
.attribution a { color: hsl(228, 45%, 44%); }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-neutral-cream text-bold p-4 h-full grid place-items-center">
|
||||
<main class="w-full md:w-1/4">
|
||||
@WidgetsComponend(pageData)
|
||||
</main>
|
||||
<div class="attribution fixed bottom-0 inset-x-0">
|
||||
Challenge by <a href="https://www.frontendmentor.io?ref=challenge" target="_blank">Frontend Mentor</a>.
|
||||
Coded by <a href="#">Your Name Here</a>.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||