I am using React JS + Typescript for my app. For styling I am using styled-components. I am really new in styled components. I have created one dropdown. The logic works fine but the UI looks horrible. I uploaded my code in Code sand box. I want design my Dropdown like Tailwind. But since I am new styled-components, I just don't know how to do that.
This is my dropdown component
import React, { useState } from "react";
import styled from "styled-components";
import Arrow from './Arrow.svg'
const Wrapper = styled.div<
{
active: boolean;
}
>`
text-align: left;
width: 100%;
color: #bfc5cd;
font-size: 16px;
font-weight: 300;
position: relative;
margin: 2em 0;
#media (min-width: 400px) {
max-width: 300px;
}
svg {
fill: #798697;
transition: all 0.2s ease;
}
${props =>
props.active
? `
svg {
transform: rotate(180deg);
}
`
: ``}
`;
const MenuLabel = styled.span`
display:inline-block;
color: grey;
border: 1px solid green;
background: white;
box-shadow: 0 0 5px -1px rgba(0,0,0,0.2);
cursor:pointer;
vertical-align:middle;
max-width: 100px;
padding: 40px 40px;
font-size: 12px;
text-align: center;
border: 1px solid ${({ theme }) => theme.inputBorderColor};
&:focus {
outline: none;
box-shadow: 0px 0px 0px 1px ${({ theme }) => theme.inputBorderColorActive};
border: 1px solid ${({ theme }) => theme.inputBorderColorActive};
}
`;
const ItemList = styled.div`
color: #798697;
background: white;
line-height: 30px;
padding: .25em 2em .25em 2em;
cursor: defaul;
user-select: none;
transition: all .25s ease;
&:hover,
&.selected {
background: #F7F7F7;
color: #4A4A4A;
}
`;
export interface IOptions {
label: string;
value: number;
}
export interface IDropdown {
labelDefault: string;
options: IOptions[];
}
const Dropdown = ({ labelDefault, options }: IDropdown) => {
const [isOpened, setIsOpened] = useState(false);
const [selectedOption, setSelectedOption] = useState("");
const [label, setLabel] = useState("");
const handleSelectedItem = (obj: any) => {
setSelectedOption(obj.value);
setLabel(obj.label);
setIsOpened(!isOpened);
};
return (
<Wrapper active={isOpened}>
<MenuLabel onClick={() => setIsOpened(!isOpened)}>
{selectedOption ? label : labelDefault}
</MenuLabel>
<ul
style={
isOpened
? {
display: "block",
listStyleType: "none"
}
: { display: "none" }
}
>
{options.map(el => (
<ItemList
key={el.value.toString()}
onClick={() => handleSelectedItem(el)}
>
{el.label}
</ItemList>
))}
</ul>
</Wrapper>
);
}
export default Dropdown;
This is the parent component
import * as React from "react";
import Dropdown from "./dropdown";
const MockData = [
{ label: "one", value: 1 },
{ label: "two", value: 2 },
{ label: "three", value: 3 }
];
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<Dropdown labelDefault="Select a label" options={MockData} />
</div>
);
}
Related
I'm new to next and I have been trying to make the game flappy bird. I used useEffect and styled components to animate the player(or bird) and pipes too. Basically when I run my app on chrome, it works fine but it flickers in safari. And after I play it a few times in safari it starts to work almost fine. For state management, I'm using redux.
Can someone help me to solve the problem and explain what is actually going on?
From what I think it is because of the re-rendering of the images but why is it working properly in chrome? And is there a better way to animate this?
This is my main code and I used the useEffect inside Bird and Pipes file to move them across the GameBox
import styled from 'styled-components'
import Pipes from './Pipes'
import { startGame, setBirdPosition, resetGame } from './features/app-slice'
import { store, constants } from './store'
import { useSelector } from 'react-redux'
import Bird from './Bird'
import { useEffect, useState } from 'react'
export default function GameBox() {
const [jumpAudio, setAudio] = useState(null)
useEffect(() => {
setAudio(new Audio('/sound-effects/jump.wav'))
// only run once on the first render on the client
}, [])
const birdPosition = useSelector((state) => state.birdPosition)
const score = useSelector((state) => state.score)
const gameStarted = useSelector(state => state.gameStarted)
const isGameOver = useSelector(state => state.isGameOver)
function jump() {
const JUMP = constants.JUMP
if (isGameOver) {
store.dispatch(resetGame())
return
}
else if (!gameStarted) {
// store.dispatch(resetGame())
store.dispatch(startGame())
}
else if (birdPosition - JUMP >= 0)
store.dispatch(setBirdPosition(-JUMP))
else store.dispatch(setBirdPosition(0))
jumpAudio.pause();
jumpAudio.currentTime = 0;
jumpAudio.play()
}
return (
<Box onClick={jump}>
{isGameOver ? <GameOver /> : null}
{gameStarted || isGameOver ? <Score>{score}</Score> : null}
{true ? <Bird /> : null}
<Pipes height={200} />
{!gameStarted && !isGameOver ? <GameStart /> : null}
{/* <Pipes height={200} position={props.width + 300} wh={props.height} /> */}
</Box>
)
}
const Box = styled.div`
user-select: none; /* supported by Chrome and Opera */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Firefox */
-ms-user-select: none;
background: no-repeat center/100% url('/img/background-day.png');
overflow: hidden;
position: relative;
width: ${constants.WINDOW_WIDTH}px;
height: ${constants.WINDOW_HEIGHT}px
`
const GameStart = styled.div`
background: no-repeat center/70% url('/img/gamestart.png');
text-align: center;
width: 100%;
height: 100%;
`
const GameOver = styled.div`
position: relative;
z-index: 10;
background: no-repeat center/70% url('/img/gameover.png');
text-align: center;
width: 100%;
height: 100%;
`
const Score = styled.div`
font-family: 'Gamja Flower', cursive;
color: white;
text-shadow: black 2px 2px;
position: absolute;
font-size: 3rem;
z-index:1;
right: 10%;
top: 0;
`
I am using Laravel Log Reader
for viewing log files. It works fine.
But I also have other log files, how can I view them usig this viewer?
This package matching specific file pattern logs/laravel-*.log .So your mentioned log file doesn't match. This package doesn't have configuration to change it at present. But still if you want to view your own log files then you can override method and create your own view file.
I can provide you some basic idea and make sure i haven't written code in super clean.This is to get some idea
Custom class which is extended
<?php
namespace App\Helper;
class LaravelLogReader extends \Haruncpi\LaravelLogReader\LaravelLogReader
{
public function getLogFileDates()
{
$dates = [];
$files = glob(storage_path('logs/*.log'));
$files = array_reverse($files);
foreach ($files as $path) {
$fileName = basename($path);
array_push($dates, $fileName);
}
return $dates;
}
public function get()
{
$availableDates = $this->getLogFileDates();
if (count($availableDates) == 0) {
return response()->json([
'success' => false,
'message' => 'No log available'
]);
}
$configDate = $this->config['date'];
if ($configDate == null) {
$configDate = $availableDates[0];
}
if (!in_array($configDate, $availableDates)) {
return response()->json([
'success' => false,
'message' => 'No log file found with selected date ' . $configDate
]);
}
$pattern = "/^\[(?<date>.*)\]\s(?<env>\w+)\.(?<type>\w+):(?<message>.*)/m";
$fileName = $configDate;
$content = file_get_contents(storage_path('logs/' . $fileName));
preg_match_all($pattern, $content, $matches, PREG_SET_ORDER, 0);
$logs = [];
foreach ($matches as $match) {
$logs[] = [
'timestamp' => $match['date'],
'env' => $match['env'],
'type' => $match['type'],
'message' => trim($match['message'])
];
}
$date = $fileName;
$data = [
'available_log_dates' => $availableDates,
'date' => $date,
'filename' => $fileName,
'logs' => $logs
];
return response()->json(['success' => true, 'data' => $data]);
}
}
and view file which is copied from library view .i have named it as log.blade.php
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Log Reader</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<script>
var angularUrl = '{{asset('laravel-log-reader/angular.min.js')}}';
window.angular || document.write('<script src="' + angularUrl + '">\x3C/script>')
</script>
<style>
body {
margin: 0;
padding: 0;
background: #f4f4f4;
font-family: sans-serif;
}
.btn {
text-decoration: none;
background: antiquewhite;
padding: 5px 12px;
border-radius: 25px;
}
header {
min-height: 30px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background: #3F51B5;
position: fixed;
left: 0;
right: 0;
top: 0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
}
header .btn_clear_all {
background: #de4f4f;
color: #fff;
}
header .name {
font-size: 25px;
font-weight: 500;
color: white;
}
.content {
margin-top: 65px;
padding: 15px;
background: #fff;
min-height: 100px;
}
.content .date_selector {
min-height: 26px;
min-width: 130px;
border: 1px solid #ddd;
border-radius: 4px;
}
.top_content {
display: flex;
justify-content: space-between;
align-items: center;
}
.top_content .top_content_left {
display: flex;
}
.top_content .top_content_left .log_filter {
display: flex;
align-items: center;
margin-left: 15px;
}
.top_content .top_content_left .log_filter .log_type_item {
margin-right: 4px;
background: #eae9e9;
max-height: 20px;
font-size: 11px;
box-sizing: border-box;
padding: 4px 6px;
cursor: pointer;
}
.top_content .top_content_left .log_filter .log_type_item.active {
background: #2f2e2f;
color: white;
}
.top_content .top_content_left .log_filter .log_type_item.clear {
background: #607D8B;
color: white;
}
table {
border: 1px solid #ccc;
border-collapse: collapse;
margin: 0;
padding: 0;
width: 100%;
}
table tr {
border: 1px solid #e8e8e8;
padding: 5px;
}
table tr:hover {
background: #f4f4f4;
}
thead tr td {
background: #717171;
color: #fff;
}
table th,
table td {
padding: 5px;
font-size: 14px;
color: #666;
}
table th {
font-size: 14px;
letter-spacing: 1px;
text-transform: uppercase;
}
#media screen and (max-width: 700px) {
.top_content {
flex-direction: column;
}
.top_content .top_content_left {
flex-direction: column;
}
.top_content .log_filter {
flex-wrap: wrap;
}
.top_content .log_filter .log_type_item {
margin-bottom: 3px;
}
}
#media screen and (max-width: 600px) {
header {
flex-direction: column;
}
header .name {
margin-bottom: 20px;
}
.content {
margin-top: 90px;
}
.btn {
font-size: 13px;
}
.dt_box,
.selected_date {
text-align: center;
}
.responsive_table {
max-width: 100%;
overflow-x: auto;
}
table {
border: 0;
}
table thead {
display: none;
}
table tr {
border-bottom: 2px solid #ddd;
display: block;
margin-bottom: 10px;
}
table td {
border-bottom: 1px dotted #ccc;
display: block;
font-size: 15px;
}
table td:last-child {
border-bottom: 0;
}
table td:before {
content: attr(data-label);
float: left;
font-weight: bold;
text-transform: uppercase;
}
}
.badge {
padding: 2px 8px;
-webkit-border-radius: 25px;
-moz-border-radius: 25px;
border-radius: 25px;
font-size: 11px;
}
.badge.info {
background: #6bb5b5;
color: #fff;
}
.badge.warning {
background: #f7be57;
}
.badge.critical {
background: #de4f4f;
color: #fff;
}
.badge.emergency {
background: #ff6060;
color: white;
}
.badge.notice {
background: bisque;
}
.badge.debug {
background: #8e8c8c;
color: white;
}
.badge.alert {
background: #4ba4ea;
color: white;
}
.badge.error {
background: #c36a6a;
color: white;
}
</style>
</head>
<body ng-controller="LogCtrl">
<header>
<div class="name">#{{ title }}</div>
<div class="actions">
<a class="btn btn_clear_all" href="#" ng-click="clearAll()">Clear All</a>
<a class="btn" href="{{url(config('laravel-log-reader.admin_panel_path'))}}">Goto Admin Panel</a>
<a class="btn" href="https://laravelarticle.com/laravel-log-reader" title="Laravel Log Reader">Doc</a>
</div>
</header>
<section class="content">
<div class="top_content">
<div class="top_content_left">
<div>
<p class="selected_date" style="font-size: 14px;"><strong>
<span ng-show="response.success">Showing Logs: #{{data.date}}</span>
<span ng-hide="response.success">#{{response.message}}</span>
</strong></p>
</div>
<div class="log_filter">
<div class="log_type_item" ng-class="selectedType==tp?'active':''"
ng-repeat="tp in logTypes track by $index"
ng-click="filterByType(tp)">#{{ tp }}
</div>
<div class="log_type_item clear" ng-show="selectedType" ng-click="selectedType=undefined">CLEAR FILTER
</div>
</div>
</div>
<div class="top_content_right">
<p class="dt_box">Select Date: <select class="date_selector" ng-model="selectedDate"
ng-change="init(selectedDate)">
<option ng-repeat="dt in data.available_log_dates"
value="#{{ dt }}">#{{ dt }}
</option>
</select>
</p>
</div>
</div>
<div>
<div class="responsive_table">
<table>
<thead>
<tr>
<td width="140">Timestamp</td>
<td width="120">Env</td>
<td width="120">Type</td>
<td>Message</td>
</tr>
</thead>
<tr ng-repeat="log in data.logs |filter: selectedType track by $index">
<td>#{{ log.timestamp }}</td>
<td>#{{log.env}}</td>
<td><span class="badge #{{ log.type.toLowerCase() }}">#{{ log.type }}</span></td>
<td>#{{ log.message }}</td>
</tr>
</table>
</div>
</div>
<script>
var myApp = angular.module("myApp", []);
myApp.controller("LogCtrl", function ($scope, $http) {
$scope.title = "Log Reader";
$scope.selectedType = undefined;
$scope.logTypes = ['INFO', 'EMERGENCY', 'CRITICAL', 'ALERT', 'ERROR', 'WARNING', 'NOTICE', 'DEBUG'];
var originalData = null;
$scope.init = function (date) {
var url = '';
if (date !== '' && date !== undefined) {
url = '{{url(config('laravel-log-reader.api_route_path'))}}?date=' + date
} else {
url = '{{url("custom-logger")}}'
}
alert(url);
$http.get(url)
.success(function (data) {
$scope.response = data;
$scope.data = data.data;
originalData = data.data;
})
};
$scope.init();
$scope.filterByType = function (tp) {
$scope.selectedType = tp
};
$scope.clearAll = function () {
if (confirm("Are you sure?")) {
var url = '{{url(config('laravel-log-reader.view_route_path'))}}'
$http.post(url, {'clear': true})
.success(function (data) {
if (data.success) {
alert(data.message);
$scope.init();
}
})
}
}
})
</script>
</section>
</body>
</html>
And add two routes
Route::get('custom-logger', function () {
$laravelLogReader=new \App\Helper\LaravelLogReader();
return $laravelLogReader->get();
});
Route::get('/log-viewer', function () {
return view('log');
});
Note: this not fully optimized code but you can write it in better way.This is only to show you can override the package
Another package you can use
Ref:https://github.com/rap2hpoutre/laravel-log-viewer
This package will read all log files and i have tested it it works fine
I'm using a React-bootstrap (v1.5.2) dropdown component passing in a custom toggle component, and the dropdown menu renders correctly upon the first click, but subsequent renderings of the dropdown menu causes the menu to appear way out of alignment with the toggle button. It appears that a CSS property, translate3D, mis-calculates the position for the dropdown menu beginning with the second render of the dropdown's menu. I've also noticed that the CSS data attribute, data-popper-placement, changes from its initial value of "bottom-end" to "bottom-start" after the first click on the dropdown button. I'm following the code pattern suggested in the React-bootstrap dropdown docs for custom components, and like I said before, the dropdown menu renders correctly for the first button click.
<Header>
{(headerFixed) => {
return (
<Fragment>
<HomePageNav />
<AssembledDropdownMenu headerFixed={headerFixed}>
{(toggle, setToggleState) => {
return (
<Dropdown
className="d-none d-lg-inline"
onToggle={setToggleState}
>
<Dropdown.Toggle as={toggle} />
<Dropdown.Menu
style={{
borderRadius: "16px",
boxShadow: "0 0 12px 0 rgba(0, 0, 0, 0.2)",
}}
align="right"
popperConfig={{
placement: "bottom-end",
modifiers: [
{
name: "offset",
options: {
offset: [0, 10],
},
},
],
}}
>
<Dropdown.Item href="#/action-1">Action</Dropdown.Item>
<Dropdown.Item href="#/action-2">
Another action
</Dropdown.Item>
<Dropdown.Item href="#/action-3">
Something else
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
);
}}
</AssembledDropdownMenu>
</Fragment>
);
}}
</Header>
import React, { Fragment, useState } from "react";
import Image from "next/image";
import styled from "styled-components";
export default function AssembledDropdownMenu({ headerFixed, children }) {
let [toggleState, setToggleState] = useState(false);
let toggle = React.forwardRef(({ children, onClick }, ref) => {
return (
<StyledButton
ref={ref}
onClick={(evt) => {
evt.preventDefault();
onClick(evt);
}}
className={
headerFixed ? (toggleState ? "fixed selected" : "fixed") : void 0
}
id="dropdown-custom"
data-display="static"
>
<div>
<Image
layout="fixed"
width={16}
height={16}
src={headerFixed ? "/icons-menu_b.svg" : "/icons-menu_w.svg"}
/>
</div>
<div>
<Image
layout="fixed"
width={24}
height={24}
src={headerFixed ? "/icons-profile_b.svg" : "/icons-profile_w.svg"}
/>
</div>
</StyledButton>
);
});
return children(toggle, setToggleState);
}
const StyledButton = styled.button`
display: flex;
flex-flow: row nowrap;
flex: 0 0 auto;
width: 88px;
height: 40px;
margin: 3px 165px 12px 0px;
padding: 8px 18px;
border-radius: 32px;
box-shadow: 0 3px 12px 0 rgba(0, 0, 0, 0.5);
border: solid 1px rgba(255, 255, 255, 0.5);
background-color: rgba(255, 255, 255, 0.25);
&.fixed {
box-shadow: none;
border: solid 1px #cccccc;
background-color: #ffffff;
}
&.fixed.selected {
box-shadow: 0 3px 12px 0 rgba(0, 0, 0, 0.2);
/*border: solid 1px #cccccc;
background-color: #ffffff;*/
}
& > div {
width: 16px;
height: 16px;
margin: -6px 12px 4px 0;
object-fit: contain;
}
& div + div {
width: 24px;
height: 24px;
margin: -2px 0 0 3px;
object-fit: contain;
}
`;
Built a portfolio page with Gatsby / GraphQL, using styled components. On my local machine, it looks and functions exactly as I want it to. When deployed, there seem to be button style issues and one of my images isn't rendering at all. I tried deploying on both Netlify and Surge.sh, but had the same result both times. What am I doing incorrectly? Edit: could my button styles be affected by how Link is used?
Here are how my button styles look locally:
Here's how they look deployed ('View Repo' doesn't even work deployed):
My 'About Me' section locally (2 columns):
My 'About Me' section deployed (only column 1 shows):
gatsby-config.js
module.exports = {
siteMetadata: {
title: `Portfolio`,
description: `Lorem ipsum.`,
author: `#jordanwhunter`,
},
plugins: [
`gatsby-plugin-react-helmet`,
`gatsby-plugin-image`,
`gatsby-plugin-styled-components`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/assets/images`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `videos`,
path: `${__dirname}/src/assets/videos`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/assets/images/memoji.jpeg`, // This path is relative to the root of the site.
},
},
`gatsby-plugin-gatsby-cloud`,
`gatsby-transformer-json`,
{
resolve: `gatsby-source-filesystem`,
options: {
path: `./src/data/`,
},
},
],
}
package.json
{
"name": "gatsby-starter-default",
"private": true,
"description": "A simple starter to get up and developing quickly with Gatsby",
"version": "0.1.0",
"author": "Kyle Mathews <mathews.kyle#gmail.com>",
"dependencies": {
"babel-plugin-styled-components": "^1.12.0",
"gatsby": "^3.0.1",
"gatsby-image": "^3.0.0",
"gatsby-plugin-gatsby-cloud": "^2.0.0",
"gatsby-plugin-image": "^1.0.0",
"gatsby-plugin-manifest": "^3.0.0",
"gatsby-plugin-offline": "^4.0.0",
"gatsby-plugin-react-helmet": "^4.0.0",
"gatsby-plugin-sharp": "^3.0.0",
"gatsby-plugin-styled-components": "^4.0.0",
"gatsby-source-filesystem": "^3.0.0",
"gatsby-transformer-json": "^3.0.0",
"gatsby-transformer-sharp": "^3.0.0",
"gh-pages": "^3.1.0",
"prop-types": "^15.7.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-helmet": "^6.1.0",
"react-icons": "^4.2.0",
"styled-components": "^5.2.1",
"surge": "^0.22.1"
},
"devDependencies": {
"prettier": "2.2.1"
},
"keywords": [
"gatsby"
],
"license": "0BSD",
"scripts": {
"build": "gatsby build",
"develop": "gatsby develop",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
"start": "npm run develop",
"serve": "gatsby serve",
"clean": "gatsby clean",
"test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://github.com/gatsbyjs/gatsby-starter-default"
},
"bugs": {
"url": "https://github.com/gatsbyjs/gatsby/issues"
}
}
Portfolio.js (button code starts on line 226)
import React from 'react'
import { useStaticQuery, graphql } from "gatsby"
import { Button } from "./Button"
import Img from "gatsby-image"
import styled from "styled-components"
const PortfolioContainer = styled.div`
min-height: 100vh;
padding: 5rem calc((100vw - 1300px) / 2);
background: #fff;
color: #fff;
`
const PortfolioHeading = styled.div`
font-size: clamp(1.5rem, 5vw, 2.5rem);
text-align: center;
margin-bottom: 5rem;
color: #000;
`
const PortfolioImg = styled(Img)`
height: 100%;
max-width: 100%;
position: absolute;
border-radius: 10px;
filter: brightness(100%);
transition: 0.4s cubic-bezier(0.075, 0.82, 0.165, 1);
`
const PortfolioWrapper = styled.div`
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 10px;
justify-items: center;
padding: 0 2rem;
#media screen and (max-width: 1200px) {
grid-template-columns: 1fr 1fr;
}
#media screen and (max-width: 868px) {
grid-template-columns: 1fr;
}
`
const PortfolioInfo = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-content: center;
align-items: center;
justify-items: center;
padding: 0 2rem;
visibility: hidden;
opacity: 0;
transition: opacity .2s, visibility .2s;
#media screen and (max-width: 280px) {
padding: 0 1rem;
justify-content: center;
align-content: center;
}
`
const PortfolioCard = styled.div`
line-height: 2;
width: 100%;
height: 500px;
position: relative;
border-radius: 10px;
transition: 0.2s ease;
&:hover ${PortfolioInfo}{
visibility: visible;
opacity: 1;
}
&:hover ${PortfolioImg}{
filter: brightness(50%)
}
`
const TextWrap = styled.div`
display: flex;
align-items: center;
text-align: center;
position: absolute;
top: 375px;
flex-wrap: wrap;
justify-content: center;
align-content: center;
width: 87%;
`
const PortfolioTitle = styled.div`
font-weight: 400;
font-size: 1rem;
margin-left: 0.5rem;
#media screen and (max-width: 280px) {
font-size: 12px;
}
`
const PortfolioDescription = styled.div`
font-size: 1rem;
#media screen and (max-width: 280px) {
font-size: 12px;
}
`
const PortfolioTechnologies = styled.div`
font-size: 1rem;
#media screen and (max-width: 280px) {
font-size: 12px;
}
`
const ButtonLink = styled.a`
text-decoration: none;
cursor: pointer;
`
const ButtonWrap = styled.div`
display: flex;
flex-direction: row;
position: absolute;
justify-content: center;
align-content: center;
align-items: center;
width: 100%;
height: -400px;
z-index: 1;
gap: 10px;
#media screen and (max-width: 280px) {
padding: 0 1rem;
justify-content: center;
align-content: center;
}
`
const CustomButton = styled(Button)`
display: flex;
align-items: center;
position: relative;
font-size: 14px;
width: 100%;
cursor: pointer;
top: -60px;
#media screen and (max-width: 480px) {
background: none;
border: none;
padding: 0 !important;
font-family: arial, sans-serif;
color: #fff;
text-decoration: underline;
cursor: pointer;
width: 100%;
font-size: 12px;
justify-content: center;
}
`
const CustomP = styled.p`
font-size: 12px;
`
export default function Portfolio({ heading }) {
const data = useStaticQuery(graphql`
query PortfolioQuery {
allPortfolioJson {
edges {
node {
alt
button1
button2
description
name
technologies
demo
repo
img {
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
}
}
}
}
`)
function getPortfolio(data) {
const portfolioArray = []
data.allPortfolioJson.edges.forEach((item, index) => {
portfolioArray.push(
<PortfolioCard key={index}>
<PortfolioImg
src={item.node.img.childImageSharp.fluid.src}
fluid={item.node.img.childImageSharp.fluid}
alt={item.node.alt}
/>
<PortfolioInfo>
<TextWrap>
<PortfolioTitle
css={`
margin-top: -500px;
`}
>
<strong><u>Project:</u></strong> <br />
{item.node.name}
</PortfolioTitle>
<PortfolioDescription
css={`
margin-top: -300px;
`}
>
<strong><u>Description:</u></strong> <br />
{item.node.description}
</PortfolioDescription>
<PortfolioTechnologies
css={`
margin-top: -100px;
`}
>
<strong><u>Technologies:</u></strong> <br />
{item.node.technologies}
</PortfolioTechnologies>
</TextWrap>
<ButtonWrap>
<ButtonLink
href={`${item.node.demo}`}
target="_blank"
>
<CustomButton
primary="true"
round="true"
>
{item.node.button1}
</CustomButton>
</ButtonLink>
<ButtonLink
href={`${item.node.repo}`}
target="_blank"
>
<CustomButton
primary="true"
round="true"
>
{item.node.button2}
</CustomButton>
</ButtonLink>
</ButtonWrap>
</PortfolioInfo>
</PortfolioCard>
)
})
return portfolioArray;
}
return (
<PortfolioContainer id="portfolio">
<PortfolioHeading>
{heading}
<CustomP>(Tap on Mobile)</CustomP>
</PortfolioHeading>
<PortfolioWrapper>
{getPortfolio(data)}
</PortfolioWrapper>
</PortfolioContainer>
)
};
About.js (column 2 code starts on line 137):
import React from 'react'
import { useStaticQuery, graphql } from 'gatsby';
import { GrCircleInformation, GrCode, GrDocumentImage } from "react-icons/gr";
import styled from "styled-components"
import Img from "gatsby-image"
const AboutContainer = styled.div`
width: 100%;
background: #fcfcfc;
color: #000;
padding: 5rem calc((100vw - 1300px) / 2);
height: 100%;
border-top: 1px solid gray;
border-bottom: 1px solid gray;
`
const TopLine = styled.div`
color: #077bf1;
font-size: 1rem;
padding-left: 2rem;
margin-bottom: 0.75rem;
`
const Description = styled.p`
text-align: start;
padding-left: 2rem;
margin-bottom: 4rem;
font-size: clamp(1.5rem, 5vw, 2rem);
font-weight: bold;
`
const ContentWrapper = styled.div`
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
#media screen and (max-width: 768px) {
grid-template-columns: 1fr;
}
`
const ColumnOne = styled.div`
display: grid;
grid-template-rows: 1fr 1fr;
`
const Biography = styled.div`
padding-top: 1rem;
padding-right: 2rem;
h3 {
margin-bottom: 1rem;
font-size: 1.5rem;
font-style: italic;
}
p {
color: #3b3b3b;
}
`
const ColumnTwo = styled.div`
display: grid;
grid-template-columns: 1fr;
margin-top: 2rem;
grid-gap: 10px;
#media screen and (max-width: 500px) {
grid-template-columns: 1fr;
}
`
const Image = styled(Img)`
border-radius: 10px;
height: 100%;
margin-top: -50px;
#media screen and (max-width: 375px) {
margin-top: 0;
}
`
export default function About() {
const data = useStaticQuery(graphql`
query MyQuery {
allFile(filter: {ext: {regex: "/(jpg)|(png)|(jpeg)/"}, name: {in: ["profile-photo"]}}) {
edges {
node {
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`)
return (
<AboutContainer id="about">
<TopLine>
About Me
</TopLine>
<Description>
<div css={`display: flex;`}>
<GrDocumentImage /><h4 css={`font-style: italic;`}>Resumes</h4>
</div>
<div
css={`
display: flex;
font-size: 14px;
margin-bottom: -40px;
`}
>
<p>
Resume 2021 (ATS Version)<br />
Resume 2021 (Styled Version)
</p>
</div>
</Description>
<ContentWrapper>
<ColumnOne>
<Biography>
<div css={`display: flex;`}>
<GrCircleInformation /><h3>Brand Statement</h3>
</div>
<p>
Full stack web/software developer with an entrepreneurial spirit, and keen sense of efficiency and time management. A passionate, goal-oriented team player that strives to always write clean, precise code focused on mobile responsive themes. Maintains a problem solving, can-do attitude and exhibits consistent eagerness to learn new technologies/techniques.
</p>
</Biography>
<Biography>
<div css={`display: flex;`}>
<GrCode />
<h3>Technologies</h3>
</div>
<p>
JavaScript, React, Preact, Next, Gatsby, Svelte, Node, Express, Firebase, Vercel, MongoDB, MySQL, Handlebars, jQuery, D3, GraphQL, Material-UI, CSS3, Bootstrap, Materialize, Bulma, HTML5
</p>
</Biography>
</ColumnOne>
<ColumnTwo>
{data.allFile.edges.map((image, key) => (
<Image
key={key}
src={image.node.childImageSharp.src}
fluid={image.node.childImageSharp.fluid}
/>
))}
</ColumnTwo>
</ContentWrapper>
</AboutContainer>
)
};
Inspect generated html:
<div class="Portfolio__ButtonWrap-zwd7jb-14 boxtca">
<a href="https://filmapi.vercel.app/" target="_blank" class="Portfolio__ButtonLink-zwd7jb-13 dHdqal">
<a primary="true" round="true" class="Button-sc-1t76fnu-0 Portfolio__CustomButton-zwd7jb-15 iA-DhcJ cycuPQ">View App</a>
</a>
<a primary="true" round="true" class="Button-sc-1t76fnu-0 Portfolio__CustomButton-zwd7jb-15 iA-DhcJ cycuPQ">
<a primary="true" round="true" class="Button-sc-1t76fnu-0 Portfolio__CustomButton-zwd7jb-15 iA-DhcJ cycuPQ">View Repo</a>
</a>
</div>
Observation #1:
bad structure for a second button (not a ButtonLink);
Why:
messed virtual DOM [and as a result real DOM];
Possible reason:
multiple, the same kind children rendered without key prop (the most frequent bad React rendering reason?);
Solution #1: always use a key prop for [elements of] lists/arrays/groups!
'About me' missing column problem:
Observation #2:
before (at first render) and during [image] loading html/DOM structure is OK;
React update (rerendering) breaks child divs [of AboutContainer] - no keys used (then no unique identifiers for multiple divs in VDOM), result - the update breaks the content structure.
But this is not the only reason - the real one was using <Img/>-based component - outdated for new gatsby image plugins [versions].
Solution #2:
You should use <GatsbyImage/> components.
Always make sure the documentation you're referencing is current!
There are significant syntax changes with graphql and using the GatsbyImage / StaticImage components from gatsby-plugin-image instead of the Img component from gatsby-image.
To fix Portfolio.js - the image rendering was affecting the button links - code had to be refactored for the current syntax referencing the GatsbyImage component from gatsby-plugin-image. graphql and useStaticQuery syntax also had to be refactored. For more information, visit Gatsby's documentation (thanks to #xadm for pointing me in the right direction).
About.js was also fixed by updating syntax...except this Img tag was changed to StaticImage and graphql and useStaticQuery were removed entirely.
I am using bootstrap-tags input (Objects as tags - https://bootstrap-tagsinput.github.io/bootstrap-tagsinput/examples/) for my tags input.
It is working fine with Objects as tags for auto complete suggestions.
But I am trying to active Free input too. So, if tags not in the autosuggest list then it allow to add new tags.
Here is my code:
var tags = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('text'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: {
ttl: 1,
url:'[Json File Path]'
}
});
tags.initialize();
var elt = $('input#tags');
elt.tagsinput({
tagClass: 'badge badge-primary',
itemValue: function(item) {
return item.id;
},
itemText: 'text',
freeInput: true,
typeaheadjs: {
name: 'tags',
displayKey: 'text',
source: tags.ttAdapter()
}
});
Sample Json:
[{"id":15,"text":"money"},{"id":14,"text":"startup"},{"id":13,"text":"inspiration"},{"id":12,"text":"success"},{"id":11,"text":"challenge"}]
jS
var citynames = new Bloodhound({
datumTokenizer:
Bloodhound.tokenizers.obj.whitespace('name'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: {
url: 'https://bootstrap-tagsinput.github.io/bootstrap-tagsinput/examples/assets/citynames.json',
filter: function(list) {
return $.map(list, function(cityname) {
return { name: cityname }; });
}
}
});
citynames.initialize();
$('input').tagsinput({
typeaheadjs: {
name: 'citynames',
displayKey: 'name',
valueKey: 'name',
source: citynames.ttAdapter()
}
});
html
<div class="container">
<div class="col-12-xs">
<div class="">
<input type="text" value="Amsterdam,Washington" />
</div>
</div>
</div>
CSS
.icon-github {
background: no-repeat url('../img/github-16px.png');
width: 16px;
height: 16px;
}
.bootstrap-tagsinput {
width: 100%;
}
.accordion {
margin-bottom:-3px;
}
.accordion-group {
border: none;
}
.twitter-typeahead .tt-query,
.twitter-typeahead .tt-hint {
margin-bottom: 0;
}
.twitter-typeahead .tt-hint
{
display: none;
}
.tt-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
display: none;
float: left;
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0;
list-style: none;
font-size: 14px;
background-color: #ffffff;
border: 1px solid #cccccc;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 4px;
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
background-clip: padding-box;
cursor: pointer;
}
.tt-suggestion {
display: block;
padding: 3px 20px;
clear: both;
font-weight: normal;
line-height: 1.428571429;
color: #333333;
white-space: nowrap;
}
.tt-suggestion:hover,
.tt-suggestion:focus {
color: #ffffff;
text-decoration: none;
outline: 0;
background-color: #428bca;
}
Codepen
Codepen