How to check which icons are available in svg file? - css

In my vuejs2/html app based on external template svg icons
are used, like :
<svg class="icon icon-plus">
<use xlink:href="/img/icons.svg#plus" />
</svg>
<svg class="icon icon-basket">
<use xlink:href="/img/icons.svg#basket" />
</svg>
File icons.svg has 20 KiB in size and I wonder
if there is a way to check which icons are available in this
file? How can I check it? I need to of close, like ”x", but making
icons.svg#close
I failed.
Can I modify color/background color of these icons? I need to modify it with css propoties and set custom colors.
Are there some available resource easy to use to create such svg files?
Thanks!

Most modern svg icon libraries (feather-icons, fontAwesome etc.) provide something like a "cheat sheet" showing all available icons.
Besides, they will most likely provide an api for embedding and styling (like changing icon colors).
If you can't find any documentation, you could try to create an visual overview like so:
let svgIcons = document.querySelector('#svgIcons');
let symbols = svgIcons.querySelectorAll('symbol[id]');
let symbolUseHtml = '';
symbols.forEach(function(el, i){
let id = el.id;
let viewBox = el.getAttribute('viewBox');
let viewBoxAtt = viewBox ? 'viewBox="'+viewBox+'"' : '';
symbolUseHtml +=
'<div class="icon-wrp"><svg class="icon icon-'+id+'" id="icon-'+id+'" '+viewBoxAtt+'><use href="#'+id+'"></svg><p>'+id+'</p></div>';
});
symbolUseHtml+='';
document.body.insertAdjacentHTML('beforeend', symbolUseHtml);
.icon-wrp{
display:inline-block;
font-size:20px;
padding:0.5em;
border:1px solid #ccc
}
#icon-home{
fill: red;
}
#icon-close{
fill: green;
}
<svg id="svgIcons" viewBox="0 0 100 100" style="display:none" xmlns="http://www.w3.org/2000/svg">
<symbol id="home" viewBox="0 0 34 48">
<path d="M33.16,28.12h-5.2v13h-3.44v-16.72l-7.72-8.72l-7.72,8.72v16.72h-3.44v-13h-5.24l16.4-17.4Z" />
</symbol>
<symbol id="close" viewBox="0 0 27 48">
<path d="M26.16,17.92l-10.44,10.44l10.44,10.44l-2.44,2.44l-10.44-10.44l-10.44,10.44l-2.44-2.44l10.44-10.44l-10.44-10.44l2.44-2.44l10.44,10.44l10.44-10.44Z" />
</symbol>
</svg>
Most likely querying for <symbol> elements will do the trick – as #chrwahl recommended.
Unfortunately, there is no standardized concept, how icon libraries are referencing icon assets:
Some might use nested svg elements or elements wrapped in a <def>.
Here is a draft of a visual "icon Inspector" to illustrate the different flavors:
Icon inspector example
let svgIcons = document.querySelector(".svgIcons");
let svgMarkup = document.querySelector("#svgMarkup");
let svgUseCode = document.querySelector("#svgUseCode");
let svgUseCodeID = document.querySelector("#svgUseCodeID");
let svgIconAssets = document.querySelector(".svgIconAssets");
let iconType = "";
let iconHtml = "";
// default example svg
let svgSrc = `<svg id="svgIcons" class="svgIcons" viewBox="0 0 100 100" style="display:none" xmlns="http://www.w3.org/2000/svg">
<symbol id="home" viewBox="0 0 34 48">
<path d="M33.16,28.12h-5.2v13h-3.44v-16.72l-7.72-8.72l-7.72,8.72v16.72h-3.44v-13h-5.24l16.4-17.4Z" />
</symbol>
<symbol id="close" viewBox="0 0 27 48">
<path d="M26.16,17.92l-10.44,10.44l10.44,10.44l-2.44,2.44l-10.44-10.44l-10.44,10.44l-2.44-2.44l10.44-10.44l-10.44-10.44l2.44-2.44l10.44,10.44l10.44-10.44Z" />
</symbol>
</svg>`;
svgMarkup.value = svgSrc;
loadSvg(svgSrc);
let svgSrcSelect = document.querySelectorAll(".trackChange");
svgSrcSelect.forEach(function (el, i) {
el.addEventListener("change", function (e) {
svgSrc = e.target.value;
loadSvg(svgSrc);
});
});
function loadSvg(src) {
// if external src
if (src.indexOf("<svg") == -1) {
fetch(src)
.then((response) => response.text())
.then((data) => {
// add temporary svg to DOM
svgIcons.innerHTML = data;
renderIcons(data);
})
.catch((error) => {
let errorData ='<svg class="icon" viewBox="0 0 100 100"><symbol id="error" viewBox="0 0 100 100" ><text x="50" y="50" width="100" text-anchor="middle" style="font-size:25%; fill:red">Not available</text></symbol></svg>';
svgIcons.innerHTML = errorData;
renderIcons(errorData);
})
}
// if src is svg markup
else {
svgIcons.innerHTML = src;
renderIcons(src);
}
}
function renderIcons(data) {
let svgOuter = svgIcons.querySelector("svg");
let nested = svgOuter.querySelectorAll("svg");
let symbols = svgOuter.querySelectorAll("symbol");
let icons = symbols.length ? symbols : nested;
let nonPathEls = svgIcons.querySelectorAll("polyline, line");
iconType = icons == symbols ? "symbols" : "nested svgs";
iconHtml = "";
icons.forEach(function (el, i) {
let id = el.id ? el.id : "";
let idAtt = id ? ' id="icon-'+id+'" ' : '';
/**
* if an icon doesn't have an id –
* try to fetch the last class name
**/
let classAtt = !id ? el.classList : "";
let classLast = classAtt ? [].slice.call(classAtt).pop() : "";
let viewBox = el.getAttribute("viewBox");
let viewBoxAtt = viewBox ? 'viewBox="' + viewBox + '"' : "";
let iconDef = id ? document.querySelector("#" + id) : "";
let iconAsset = iconDef ? iconDef : el;
let iconUseHtml = "";
/**
* if icon defs contain other elements
* than paths – like polyline or line –
* maybe they're intended to have outlines/strokes
* but no fill (like in feather-icons)
**/
if (iconAsset) {
let children = iconAsset.childNodes;
if (children.length && nonPathEls.length) {
children.forEach(function (sub, i) {
sub.classList.add("icon-inner-stroke");
});
}
let iconInner = iconAsset ? iconAsset.innerHTML : "";
let iconSelector = !id ? "class: " + '<strong>'+classLast+'</strong>' : "id: " + '<strong>'+id+'</strong>';
if (id) {
iconUseHtml =
'<svg class="icon-use icon-use-' +
id +
idAtt+
viewBoxAtt +
">\n" +
'<use href="' +
id +
'" />\n' +
"</svg>\n";
}
let dataUse = iconUseHtml
? 'data-use="' + encodeURIComponent(iconUseHtml) + '" '
: 'data-use=""';
iconHtml +=
'<div class="icon-wrp" ' +
dataUse +
" >" +
'<svg class="icon icon-' +
id + '" '+
idAtt+
viewBoxAtt +
">" +
iconInner +
"</svg>" +
'<p class="icon-label">' +
iconSelector +
"<br>type: " +
iconType +
"</p>" +
"</div>";
//console.log(svgIconAssets)
}
});
svgIconAssets.innerHTML = iconHtml;
let svgIconWrp = svgIconAssets.querySelectorAll(".icon-wrp");
svgIconWrp.forEach(function (el, i) {
el.addEventListener("click", function (e) {
let currentIcon = e.target.closest(".icon-wrp");
let iconCode = '<!-- not selectable by id -->\n'+currentIcon.innerHTML;
let useCode = currentIcon.getAttribute("data-use");
useCode = useCode ? decodeURIComponent(useCode) : iconCode;
svgUseCode.value = useCode;
svgUseCodeID.innerHTML =
"<strong>" + currentIcon.querySelector("svg").id + "</strong>";
});
});
}
body {
margin: 0;
font-family: "Segoe UI";
}
* {
box-sizing: border-box;
font-family: inherit;
}
:root {
--svgHover: red;
}
::-webkit-scrollbar {
width: 0.45em;
border-radius: 0.5em;
background: #ddd;
}
::-webkit-scrollbar-track {
border-radius: 0.5em;
}
::-webkit-scrollbar-thumb {
background: #aaa;
border-radius: 0.5em;
}
.layout {
display: flex;
height: 100vh;
overflow: hidden;
}
.svgIconAssets {
display: flex;
flex-wrap: wrap;
}
.icon-wrp {
width: 10vw;
font-size: 4vw;
margin-bottom: 0.5em;
text-align: center;
}
.icon-wrp * {
text-align: center;
}
.icon-label {
font-size: 12px;
text-align: center;
margin: 0;
}
.icon {
display: inline-block;
height: 1em;
font-size: 1em;
color: #999;
fill: #999;
cursor:pointer;
&:hover {
color: var(--svgHover);
fill: var(--svgHover);
}
}
.icon-inner-stroke {
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
}
input {
width: 100%;
}
label{
font-size:0.8rem;
line-height: 1.2em;
margin-bottom:0.25rem;
}
.svgDisplay {
width: 75%;
height: 100%;
position: relative;
overflow: auto;
}
.frm-wrp {
padding: 1em;
margin-left: auto;
width: 20%;
background-color: #ccc;
display: flex;
flex-direction: column;
flex: 1;
* {
width: 100%;
}
textarea {
min-height: 5em;
height: 90%;
white-space: pre-wrap;
font-family: monospace;
}
input, textarea{
margin-bottom: 0.5rem;
}
}
<div class="layout">
<div class="svgDisplay">
<div class="svgIcons" style="display:none"></div>
<div class="svgIconAssets"></div>
</div>
<div class="frm-wrp">
<label>Svg URL</label>
<input type="text" id="svgSrcSelect" class="svgSrcSelect trackChange" list="dataSrc" placeholder="select example or paste svg url" />
<datalist id="dataSrc">
<option value="https://unpkg.com/feather-icons#4.28.0/dist/feather-sprite.svg">Feather icons</option>
<option value="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/sprites/regular.svg">FontAwesome (6.0)</option>
<option value="https://cdn.jsdelivr.net/npm/font-awesome-svg-icons#0.1.0/svg-sprite.svg">FontAwesome (0.1)</option>
</datalist>
<label>Svg markup</label>
<textarea id="svgMarkup" class="svgMarkup trackChange" placeholder="enter svg code"></textarea>
<label>Svg use code: <span id="svgUseCodeID"></span></label>
<textarea id="svgUseCode" class="svgUseCode" placeholder="enter svg code"></textarea>
</div>
</div>
This example contains 3 icon library svgs (you can select them in the URL field as datalist options).
Significant differences:
1. Feather icons
icons are defined as symbols
contain different element types like circle, polyline etc. (in addition to <path>)
all icons are styled by stroke attributes (stroke, stroke-width, stroke-linecap)
2. FontAwesome (6.0)
icons are defined as symbols
all assets are solid/closed <path> shapes
all icons are styled by fill attribute
3. FontAwesome (0.1)
icons are grouped in nested svg elements
using class names instead of IDs
(probably supposed to be just a test file)

Related

How to Add a Space in Line Numbers in Textarea Word Wrap in Svelte

I have a text area with CSS generated line numbers for each new line.
Everything works great, except for when there is a word wrap. If I get a word wrap, it does not wrap the new line text. I would like to make my line numbers word wrap with the text.
Here is a notepad+ example:
And here is a GitHub example:
How can I automatically detect and add a word wrap to my line numbers as well? Here is my code:
<script lang="ts">
export let source = `jon
sup
me
fixin to do sumthin good`;
let numberOfLines = source.split('\n').length;
const KeyUp = (event: Event) => {
const textarea = event.target as HTMLInputElement;
numberOfLines = textarea.value.split('\n').length;
};
const KeyDown = (event: Event) => {
const textarea = event.target as HTMLInputElement;
if ((event as KeyboardEvent).key === 'Tab') {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
if (start && end) {
textarea.value = textarea.value.substring(0, start) + '\t'
+ textarea.value.substring(end);
event.preventDefault();
}
}
};
</script>
<center>
<div class="editor">
<div class="line-numbers">
{#each Array(numberOfLines) as _}
<span class="new-line" />
{/each}
</div>
<textarea class="text-content"
on:keyup={KeyUp} on:keydown={KeyDown} value={source} rows="15" />
</div>
</center>
<style>
.editor {
display: inline-flex;
gap: 10px;
font-family: monospace;
line-height: 21px;
background: #282a3a;
border-radius: 2px;
padding: 20px 10px;
}
.line-numbers {
width: 20px;
text-align: right;
}
.line-numbers .new-line {
counter-increment: linenumber;
}
.line-numbers .new-line::before {
content: counter(linenumber);
display: block;
color: #506882 !important;
}
.text-content {
line-height: 21px;
overflow-y: hidden;
padding: 0;
border: 0;
background: #282a3a;
color: #fff;
min-width: 500px;
outline: none;
resize: none;
}
</style>
And here is the StackBlitz Link:
(right click - open in new window)
Thanks!
J
Image for responding to comment below:
An approach could be to read the width of one character of the monospace font from an invisible element and set the textarea width to a multiple to avoid rounding errors. It then can be calculated for each substring between the line breaks how often it is wrapped and a margin-bottom added to the corresponding line number accordingly
REPL
<script>
import {onMount} from 'svelte'
export let source = `jon\nsup there is some other text here that I want to go across the lines and not word wrap another line etc boy!\nme\nfixin to do sumthin good`;
let fontWidthElem
let fontWidth
let charactersPerLine = 60
$: areaWidth = charactersPerLine * fontWidth
onMount(() => {
fontWidth = fontWidthElem.getBoundingClientRect().width
})
function wrapped(str, timesWrapped) {
if(str.length > charactersPerLine) {
const firstLine = str.substring(0, charactersPerLine)
const spaceOrDashInFirstLine = /[ -]/.test(firstLine)
const lastCharFirstLine = str[charactersPerLine-1]
const firstCharNextLine = str[charactersPerLine]
const wordIsCut = lastCharFirstLine !== '-' && lastCharFirstLine !== ' ' && firstCharNextLine !== ' ' && firstCharNextLine !== '-'
const nextLine = str.substring(charactersPerLine)
const nextLineNotOnlySpaces = nextLine.replaceAll(' ', '').length > 0
if(wordIsCut && spaceOrDashInFirstLine){
const lastIndexOfDivider = Math.max(firstLine.lastIndexOf(' '), firstLine.lastIndexOf('-'))
return wrapped(str.substring(lastIndexOfDivider+1), timesWrapped+1)
}else if(nextLineNotOnlySpaces){
return wrapped(nextLine.trimStart(), timesWrapped+1)
}else {
return timesWrapped
}
}else {
return timesWrapped
}
}
</script>
<div class="editor">
<div class="line-numbers">
{#each source.split('\n') as subStr, index}
<div class="line-nr"
style:margin-bottom="calc(var(--line-height) * {wrapped(subStr, 0)})"
>
{index + 1}
</div>
{/each}
</div>
<textarea bind:value={source}
style:width="{areaWidth}px"
rows="15"
/>
<div id="font-width"
bind:this={fontWidthElem}
>
a
</div>
</div>
<style>
:global(body) {
padding: 0;
}
.editor {
--line-height: 21px;
position: relative;
display: inline-flex;
gap: 10px;
padding: 20px 10px;
font-family: monospace;
line-height: var(--line-height);
background: #282a3a;
border-radius: 2px;
}
.line-nr {
width: 20px;
text-align: right;
color: #506882 !important;
}
textarea {
line-height: var(--line-height);
overflow-y: hidden;
margin: 0;
padding: 0;
border: 0;
background: #282a3a;
color: #fff;
outline: none;
resize: none;
}
#font-width {
position: absolute;
top: -1000px;
left: -1000px;
visibility: hidden;
}
</style>
To make the width responsive
additional editor wrapper for padding
watch editor width and calculate maximal possible charactersPerLine for textarea besides line numbers element
charactersPerLine added as argument to wrapped() for recalculation on change
REPL
<script>
import {onMount} from 'svelte'
export let source = `jon\nsup there is some other text here that I want to go across the lines and not word wrap another line etc boy!\nme\nfixin to do sumthin good`;
let fontWidthElem
let fontWidth
let editorWidth
onMount(() => {
fontWidth = fontWidthElem.getBoundingClientRect().width
})
let lineNumbersWidth = 30
$: charactersPerLine = Math.floor((editorWidth - lineNumbersWidth) / fontWidth)
function wrapped(str, charactersPerLine, timesWrapped) {
if(str.length > charactersPerLine) {
const firstLine = str.substring(0, charactersPerLine)
const spaceOrDashInFirstLine = /[ -]/.test(firstLine)
const lastCharFirstLine = str[charactersPerLine-1]
const firstCharNextLine = str[charactersPerLine]
const wordIsCut = lastCharFirstLine !== '-' && lastCharFirstLine !== ' ' && firstCharNextLine !== ' ' && firstCharNextLine !== '-'
const nextLine = str.substring(charactersPerLine)
const nextLineNotOnlySpaces = nextLine.replaceAll(' ', '').length > 0
if(wordIsCut && spaceOrDashInFirstLine){
const lastIndexOfDivider = Math.max(firstLine.lastIndexOf(' '), firstLine.lastIndexOf('-'))
return wrapped(str.substring(lastIndexOfDivider+1), charactersPerLine, timesWrapped+1)
}else if(nextLineNotOnlySpaces){
return wrapped(nextLine.trimStart(), charactersPerLine, timesWrapped+1)
}else {
return timesWrapped
}
}else {
return timesWrapped
}
}
</script>
<div class="editor-wrapper">
<div class="editor"
bind:clientWidth={editorWidth}
>
<div class="line-numbers"
style:width="{lineNumbersWidth}px"
>
{#each source.split('\n') as subStr, index}
<div class="line-nr"
style:margin-bottom="calc(var(--line-height) * {wrapped(subStr, charactersPerLine, 0)})"
>
{index + 1}
</div>
{/each}
</div>
<textarea bind:value={source}
style:width="{charactersPerLine}ch"
rows="15"
/>
<div id="font-width"
bind:this={fontWidthElem}
>
a
</div>
</div>
</div>
<style>
:global(body) {
padding: 0;
}
:global(*) {
box-sizing: border-box;
}
.editor-wrapper {
--line-height: 21px;
padding: 20px 10px;
background: #282a3a;
border-radius: 2px;
}
.editor {
position: relative;
display: flex;
width: 100%;
font-family: monospace;
line-height: var(--line-height);
}
.line-numbers {
padding-right: 10px;
text-align: right;
color: #506882 !important;
}
textarea {
line-height: var(--line-height);
overflow-y: hidden;
margin: 0;
padding: 0;
border: 0;
background: #282a3a;
color: #fff;
outline: none;
resize: none;
/* border seems to falsify calculation! */
/* box-shadow: 0 0 0 1px grey; */
}
#font-width {
position: absolute;
top: -1000px;
left: -1000px;
visibility: hidden;
}
</style>

Why does my styled components transitions are not working?

I'm working on a Next.js project where the menu opens with a click on a burger button. For this I toggle a class on the button, and the styles for this class use styled-components.
When I click on the button it changes like expected, but when I add a CSS transition it's still not animated. I tested the same code in vanilla HTML and it's working well.
Where did I go wrong?
This is my code:
Burger js:
<BurgerStyled
onClick={() => setIsBurgerOpen(!isBurgerOpen)}
className={BurgerOpen}
>
<span />
<span />
<span />
</BurgerStyled>
OnClick behaviour:
const [isBurgerOpen, setIsBurgerOpen] = useState(false);
const BurgerOpen = isBurgerOpen ? "BurgerOpen" : "";
Styled component:
const BurgerStyled = styled.div`
width: 32px;
height: 21px;
position: relative;
span {
position: absolute;
left: 0;
background-color: var(--gray);
width: 100%;
height: 3px;
transition: all 0.3s ease;
&:first-child {
top: 0;
}
&:nth-child(2) {
top: calc(50% - 3px / 2);
}
&:last-child {
bottom: 0;
}
}
&.BurgerOpen span {
&:first-child {
transform: rotate(45deg);
}
&:nth-child(2) {
width: 0;
}
&:last-child {
transform: rotate(-45deg);
}
}
`;
Thanks for your answers!
You can pass the property to your styled component like so:
JSX:
return (
<BurgerStyled
onClick={() => setIsBurgerOpen(!isBurgerOpen)}
open={isBurgerOpen}
>
<span />
<span />
<span />
</BurgerStyled>
);
Style:
const BurgerStyled = styled(({ open, ...restProps }) => <div {...restProps} />)`
${({ open }) => open ? ` styles open here ` : ` styles closed here `};
`;
What you do here: you pass the open prop to your styled component, and then pass all other props to the div. The open prop is now available in your style template string. This prevents the open prop to be passed to the DOM-element, because a <div> DOM-element has no open property, so passing it through to the DOM would result in warnings.
You can try svg and transition to have the desired output. Check my sample code below, it might help you.
import { motion } from './framer-motion'; //install it with npm
const [isBurgerOpen, setIsBurgerOpen] = useState(false);
const HamburgerMenuContainer = styled.div`
display:flex;
`;
const MenuContainer = styled.div`
min-width:300px;
width:100%;
max-width:44%;
height:100%;
background-color: #fff;
box-shadow: -2px 0 2px rgba(15,15,15,0.3);
z-index:90;
position:fixed;
top:0;
right:0;
user-select:none;
padding: 1em 2.5em;
`;
const Button = styled.button`
z-index:99;
cursor:pointer;
`;
const Path = props =>{
<motion.Path fill="transparent" strokeLinecap="round" strokeWidth="3" {...props}/>
}
const transition = {duration: 0.3};
return (
<>
<HamburgerMenuContainer>
<Button onClick={() => setIsBurgerOpen(true)}>
<svg width="23" height="23" viewBox="0 0 23 23">
<Path animate={isBurgerOpen ? "open" : "closed"} initial={false} variants={{
closed: {d: "M 2 2.5 L 20 2.5",stroke:"hsl(0, 0%, 100%)"},
open: {d: "M 3 16.5 L 17 2.5",stroke:"hsl(0, 0%, 18%)"},
}}
transition={transition}
/>
<Path animate={isBurgerOpen ? "open" : "closed"} initial={false} variants={{
closed: {d: "M 2 2.5 L 20 2.5",stroke:"hsl(0, 0%, 100%)"},
open: {d: "M 3 16.5 L 17 2.5",stroke:"hsl(0, 0%, 18%)"},
}}
transition={transition}
/>
<Path animate={isBurgerOpen ? "open" : "closed"} initial={false} variants={{
closed: {d: "M 2 2.5 L 20 2.5",stroke:"hsl(0, 0%, 100%)"},
open: {d: "M 3 16.5 L 17 2.5",stroke:"hsl(0, 0%, 18%)"},
}}
transition={transition}
/>
</svg>
</Button>
</HamburgerMenuContainer>
</>
);
}
I could fix the problem. My Burger component was outside the header, like this:
function Header() {
const Burger = () => {
// Burger component
}
return (
<Burger />
)
}
I just put it like this and it worked:
function Header() {
return (
<BurgerStyled ...>
<span />
<span />
<span />
</BurgerStyled>
// Content header
)
}
Now the animation works, thanks for your answers!

How can i make slider animation?

I need to make the vertical slider animation ( dots and line ) as in this pic
i managed to do the Accordion and the dots but i don't know how i will going to implement it ( i'm using pseudo )
**my accordion component Where i define the logic of my nested accordions as in images based on array of data **
function MultiLevelAccordion({
data,
bodyClass,
headerClass,
wrapperClass,
renderHeader,
renderContent,
}) {
const RootAccordionId = 'parent-0';
const [accordionsStates, setActiveCardsIndex] = useMergeState({});
const onAccordionToggled = (id, activeEventKey) => {
console.log(activeEventKey);
setActiveCardsIndex({
[id]: activeEventKey ? Number(activeEventKey) : activeEventKey
});
};
console.log('data', data);
const accordionGenerator = (data, parentId) => {
return map(data, (item, index) => {
const active = accordionsStates[parentId] === index;
const hasChildren = item.hasOwnProperty('children') && isArray(item.children) && !isEmpty(item.children);
const isRootAccordion = RootAccordionId === parentId;
const isLastNestedAccordion = !isRootAccordion && !hasChildren;
const accordion = (
<Card className={classNames(wrapperClass, {
'nested-root-accordion': !isRootAccordion,
'last-nested-root-accordion': isLastNestedAccordion,
'multi-level-accordion': !isLastNestedAccordion
})}
>
<Accordion.Toggle
{...{ ...item.id && { id: item.id } }}
onClick={() => this}
as={Card.Header}
eventKey={`${index}`}
className={'cursor-pointer d-flex flex-column justify-content-center'}
>
<div className="d-flex justify-content-between align-items-center">
{renderHeader(item, hasChildren)}
<img
style={{
transition: 'all .5s ease-in-out',
transform: `rotate(${active ? 180 : 0}deg)`
}}
src={setIcon('arrow-down')}
className="ml-2"
alt="collapse"
/>
</div>
</Accordion.Toggle>
<Accordion.Collapse eventKey={`${index}`}>
<Card.Body
className={`accordion-content-wrapper ${!hasChildren ? 'accordion-children-body' : ''} ${bodyClass}`}
>
{!hasChildren ? renderContent(item, hasChildren) : (
<Accordion onSelect={activeEventKey => onAccordionToggled(`${parentId}-${index}`, activeEventKey)}>
<Fade cascade top when={active}>
{accordionGenerator(item.children, `${parentId}-${index}`)}
</Fade>
</Accordion>
)}
</Card.Body>
</Accordion.Collapse>
</Card>
);
return isRootAccordion ? accordion : (
<div className={'d-flex align-items-center'}>
{accordion}
<div className="accordion-indicator-wrapper">
<div className="accordion-indicator" id={`indicator-${parentId}-${index}`} />
</div>
</div>
);
});
};
if (!isArray(data)) {
return;
}
return (
<Accordion onSelect={activeEventKey => onAccordionToggled(RootAccordionId, activeEventKey)}>
{accordionGenerator(data, RootAccordionId)}
</Accordion>
);
}
export default MultiLevelAccordion;
the styles used in scss
.faqs-questions-wrapper {
padding: 20px 10px
}
.faqs-q-count {
color: $black-color;
font-size: calc(1rem - 1rem/8)
}
.faqs-q-a-wrapper {
flex-basis: 95%;
}
.faq-child-title {
color: $black-color
}
.nested-root-accordion {
flex-basis: 90%;
}
.accordion-indicator-wrapper {
flex-basis: 10%;
width: 100%;
display: flex;
justify-content: center;
.accordion-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: $theme-color;
position: relative;
}
}
Any clue?
Thanks in Advance.
React JS is gonna make this easy
The lines expansion will need to be coded based on the height of the box window
For the dropdown container keep the vertical button lines in a separate div than the Accordian
Check out this pen for creating lines between buttons
https://codepen.io/cataldie/pen/ExVGjya
css part:
.status-container{
padding:10px;
margin:10px;
position:relative;
display: inline-block;
}
.bullet{
padding:0px;
margin:0px;
display: inline-block;
z-index: 10;
}
.bullet:before {
content: ' \25CF';
font-size: 5em;
}
.bullet-before{
/*position:relative;
right:-12px;*/
}
.bullet-after{
/*position:relative;
left:-30px;*/
}
.line{
stroke:blue;
stroke-width:0.3em;
padding:0px;
margin:0px;
display: inline-block;
}
.line-on{
stroke:red;
}
.line-off{
stroke:gray;
}
.color-on{
color: red;
}
.color-off{
color: gray;
}
https://codepen.io/emwing/pen/LgzJOx
I think you can use some inspiration here

How to postion a Div in the flow after a scaled Svg (without JS)?

I want to position a DIV just after a dynamically scaled SVG.
Unfortunately the DIV position doesn't change.
<svg style="transform:scale(x); transform-origin:0 0"></svg>
<div>Sould be just under the SVG whatever scale</div>
I've tried different techniques (position:relative, flex-direction:column, fit-content, ...) without success. I have to recalculate the DIV position after every scaling.
Is it possible to achieve this in CSS, whith no Javascript ?
Transform.onclick = function () {
scale = function ( ratio ) {
S1.style.transform = 'scale(' + ratio + ')'
//the calculation part I'd like to avoid
var ty = S1.height.baseVal.value * ( ratio - 1 )
DivIn.style.transform = 'translateY(' + ty + 'px)'
}
toggle( this )
}
Dims.onclick = function () {
var H = S1.height.baseVal.value
var W = S1.width.baseVal.value
scale = function ( ratio ) {
S1.setAttribute( "height", H * ratio )
S1.setAttribute( "width", W * ratio )
}
toggle( this )
}
Symb.onclick = function () {
scale = function ( ratio ) {
S1.style.fontSize = ratio + "em"
}
toggle( this )
}
Transform.click()
function toggle ( button ) {
Array.from( document.querySelectorAll( 'button' ) )
.forEach( b => b.style.fontWeight = button==b ? 700:500 )
}
svg {
transform-origin: 0 0;
}
div#DivOut {
position: relative;
}
div#Container {
position: flex;
flex-direction: column;
height: fit-content;
border: 1px solid blue;
}
<input type=number value=1 oninput="scale(this.value)">
<button id=Transform>1: Transform</button>
<button id=Dims>2: Relative Height</button>
<button id=Symb>3: Symbol</button>
<div id=Container>
<svg width=50 height=50 id=S1>
<use xlink:href="#SY"></use>
<symbol id=SY>
<rect x=0 y=0 width=100% height=100% fill=red />
</symbol>
</svg>
<div id=DivIn>Next line test 1</div>
</div>
<div id=DivOut>Next line test 2</div>
The best solution I found is to set a viewBox attribute to the <svg> element.
Then you can resize it by just changing the width and height via CSS style.
function resize()
{
SVG1.classList.toggle( 'double' )
}
.double {
width: 200px ;
height: 200px ;
}
<svg id=SVG1 viewBox="0 0 100 100" width=100 height=100>
<rect x=0 y=0 width=100 height=100 fill=red />
</svg>
<div>
<button onclick="resize()">Resize</button>
</div>

How to use clip-path in an animation?

I've read a bunch of posts and been playing around with trying to animate a clip-path. At this point I can only get it show the clip-path in Firefox and it always pins the path to 0,0.
Here is an example code -> http://jsfiddle.net/justintense/4torLok9/1/
I'm using a simple inline SVG as the path:
<clipPath id="clipping">
<polygon fill="#FFFFFF" points="40,35 40,15 20,35 "/>
<polygon fill="#FFFFFF" points="0,35 20,35 0,15 "/>
<circle id="white_overlay_9_" fill="#FFFFFF" cx="20" cy="18.77" r="7.393"/>
</clipPath>
I'm unsure if what I trying to do is even possible so am just looking for pointer so I can get this one track.
Edit:
I'm trying to recreate the behaviour similar to this site --------> http://uppymama.com/
Defining the clipPath:
You don't need three elements in your clipPath, you could simplify it to a single path element.
<path d="M0,0 L40,0 L20,15z M20,5 m-5,0 a5,5 0 1,0 10,0 a5,5 0 1,0 -10,0z" />
Applying the clipPath to the div:
For applying the clip-path, if you want maximum browser support, you could import the div into an svg element through svg's foreignObject element. Then, apply the clip-path to the foreignObject.
Here's the browser support, if you apply the clip-path through CSS.
Animating the clipPath:
Now, for aligning the pointer right in the middle of the menu item, you'll need JavaScript to do some calculations and animate the clipPath's transform property.
When page loads calculate the distance from the left window corner to the first menu-item and move the clip-path to that co-ordinate.
When the window resizes, calculate the distance again and move the pointer.
When the menu item is clicked, calculate the distance and animate the pointer using setTimeout() and synchronous code until it reaches the correct position.
Synchronous code means that the operations are performed one at a time and that, until one operation is over, the code execution is blocked from moving onto the next operation.
Exact replica of that site:
As you can see on that site, the pointer isn't centered.
My solution will always center the pointer depending on the width of the menu item.
Demo on CodePen
var items = document.getElementsByClassName('menu-item');
var clipPath = document.getElementById('clip-path');
var lastActive = items[0];
for (i = 0; i < items.length; i++) {
items[i].addEventListener('click', function() {
var x = this.getBoundingClientRect().left + (this.offsetWidth / 2) - 20;
animStart(x);
lastActive = this;
})
}
function animStart(end) {
for (start = 0; start <= end; start += 0.1) {
anim(start);
}
}
function anim(start) {
setTimeout(function() {
clipPath.setAttribute('transform', 'translate(' + start + ', 0)');
}, 1 * start);
}
function align() {
var x = lastActive.getBoundingClientRect().left + (lastActive.offsetWidth / 2) - 20;
animStart(x);
}
function resize() {
var x = lastActive.getBoundingClientRect().left + (lastActive.offsetWidth / 2) - 20;
clipPath.setAttribute('transform', 'translate(' + x + ', 0)');
}
window.onresize = resize;
window.onload = align;
body {
background: #222222;
margin: 0;
}
.nav-container {
top: 20px;
position: relative;
width: 100%;
height: 50px;
background: repeating-linear-gradient(90deg, palegoldenrod 5px, palegreen 10px, palegreen 15px, paleturquoise 15px, paleturquoise 20px, plum 20px, plum 25px);
}
.nav {
width: 100%;
height: 50px;
margin: 0 auto;
text-align: center;
}
.menu-item {
display: inline-block;
width: 70px;
padding: 17px 20px;
color: #222222;
font-size: 14px;
}
.menu-item:hover {
color: seagreen;
cursor: pointer;
}
#bottom {
width: 100%;
height: 35px;
background: repeating-linear-gradient(90deg, palegoldenrod 5px, palegreen 10px, palegreen 15px, paleturquoise 15px, paleturquoise 20px, plum 20px, plum 25px);
}
#clip-path {
transition: 0.5s transform;
}
<body>
<div class="nav-container">
<div class="nav">
<div id="menu-item-1" class="menu-item">Home</div>
<div id="menu-item-2" class="menu-item">About</div>
<div id="menu-item-3" class="menu-item">Services</div>
<div id="menu-item-4" class="menu-item">Locations</div>
<div id="menu-item-5" class="menu-item">Contact Us</div>
</div>
<svg style="position: relative; top: -1px;" class="svg-defs" height="35" width="100%">
<defs>
<clipPath id="clipping">
<path id="clip-path" transform="translate(0,0)" d="M0,0 L40,0 L20,15z M20,6 m-5,0 a5,5 0 1,0 10,0 a5,5 0 1,0 -10,0z" />
</clipPath>
</defs>
<foreignObject x="0" y="0" clip-path="url(#clipping)" height="35" width="100%">
<div id="bottom"></div>
</foreignObject>
</svg>
</div>
</body>
Even Better:
Retaining the last position of the pointer.
Demo on CodePen
var items = document.getElementsByClassName('menu-item');
var clipPath = document.getElementById('clip-path');
var last = items[0];
for (i = 0; i < items.length; i++) {
items[i].addEventListener('click', function() {
var x = this.getBoundingClientRect().left + (this.offsetWidth / 2) - 20;
var currentX = clipPath.getAttribute('transform').replace('translate(', '');
currentX = parseInt(currentX.replace(', 0)', ''), 10);
animStart(currentX, x);
last = this;
})
}
function animStart(begin, end) {
if (begin < end) {
for (start = begin; start <= end; start += 0.1) {
anim(start);
}
} else {
var c = end;
for (start = begin; end <= start; start -= 0.1) {
animD(start, c);
c += 0.1
}
}
}
function anim(start) {
setTimeout(function() {
clipPath.setAttribute('transform', 'translate(' + start + ', 0)');
}, 1 * start);
}
function animD(start, c) {
setTimeout(function() {
clipPath.setAttribute('transform', 'translate(' + start + ', 0)');
}, 1 * c);
}
function align() {
var x = last.getBoundingClientRect().left + (last.offsetWidth / 2) - 20;
animStart(0, x);
}
function resize() {
var x = last.getBoundingClientRect().left + (last.offsetWidth / 2) - 20;
clipPath.setAttribute('transform', 'translate(' + x + ', 0)');
}
window.onresize = resize;
window.onload = align;
body {
background: #222222;
margin: 0;
}
.nav-container {
top: 20px;
position: relative;
width: 100%;
height: 50px;
background: repeating-linear-gradient(90deg, palegoldenrod 5px, palegreen 10px, palegreen 15px, paleturquoise 15px, paleturquoise 20px, plum 20px, plum 25px);
}
.nav {
width: 600px;
height: 50px;
margin: 0 auto;
text-align: center;
}
.menu-item {
display: inline-block;
width: 70px;
padding: 17px 20px;
color: #222222;
font-size: 14px;
}
.menu-item:hover {
color: seagreen;
cursor: pointer;
}
#bottom {
width: 100%;
height: 35px;
background: repeating-linear-gradient(90deg, palegoldenrod 5px, palegreen 10px, palegreen 15px, paleturquoise 15px, paleturquoise 20px, plum 20px, plum 25px);
}
<body>
<div class="nav-container">
<div class="nav">
<div id="menu-item-1" class="menu-item">Home</div>
<div id="menu-item-2" class="menu-item">About</div>
<div id="menu-item-3" class="menu-item">Services</div>
<div id="menu-item-4" class="menu-item">Locations</div>
<div id="menu-item-5" class="menu-item">Contact Us</div>
</div>
<svg style="position: relative; top: -1px;" class="svg-defs" height="35" width="100%">
<defs>
<clipPath id="clipping">
<path id="clip-path" transform="translate(0,0)" d="M0,0 L40,0 L20,15z M20,6 m-5,0 a5,5 0 1,0 10,0 a5,5 0 1,0 -10,0z" />
</clipPath>
</defs>
<foreignObject x="0" y="0" clip-path="url(#clipping)" height="35" width="100%">
<div id="bottom"></div>
</foreignObject>
</svg>
</div>
</body>

Resources