<html> <head> <title>Pattern</title> </head> <body data-prismjs-copy-timeout="1500"> <h2>What is it</h2> <p>Sticky notes provide a way to attach information. They are a DS2 core pattern for making notes. </p> <h2>When to use it</h2> <p>Use a sticky when you want to make a note without adding it to the content. </p> <h2>How to use it</h2> <p>Uses absolute positioning. <sticky-note class="blue" float="right">This <strong>is</strong> a sample sticky. You can drag it out of the way if you need to see the content under it.</sticky-note> </p> <p>If you wish to create a custom element, that extends another HTML element, the native element has to be extended in customElements.define(). Custom elements that inherit native elements are also known as "type extension custom elements". </p> <sticky-note>You will notice when you hover over the sticky, it shows a dot in the colour of the sticky in the position where the sticky note refers to (assuming you haven't dragged it and scrolled that location off the screen).</sticky-note> <tabset id="sticky-note"> <pre class="language-html" tab="html"> <sticky-note class="blue" float="right">This <strong>is</strong> a sample sticky. You can drag it out of the way if you need to see the content under it.</sticky-note></pre> <pre class="language-pug" tab="pug">sticky-note(float="right").blue This #[strong is] a sample sticky. | You can drag it out of the way if you need to see the content under it.</pre> <pre class="language-css" data-tab="css"> @import url("https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&display=swap"); sticky-note-wrapper { -webkit-box-sizing: border-box; box-sizing: border-box; border: 1px solid transparent; border-radius: 50%; display: inline-block; height: 0.5rem; position: relative; width: 0.5rem; } sticky-note-wrapper * { -webkit-box-sizing: border-box; box-sizing: border-box; } sticky-note-wrapper sticky-note { cursor: -webkit-grab; cursor: grab; display: -ms-grid; display: grid; float: left; font-size: 1rem; height: 13rem; left: 0; place-items: stretch; position: absolute; top: 0; width: 13rem; z-index: 100; } sticky-note-wrapper sticky-note:active { cursor: -webkit-grabbing; cursor: grabbing; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } sticky-note-wrapper sticky-note.right { float: right; } sticky-note-wrapper sticky-note > div { -ms-grid-row: 1; grid-row: 1; -ms-grid-column: 1; grid-column: 1; } sticky-note-wrapper sticky-note > div:nth-child(1) { background-color: rgba(0, 0, 0, 0.25); -webkit-box-shadow: -2px 2px 15px 0 rgba(0, 0, 0, 0.5); box-shadow: -2px 2px 15px 0 rgba(0, 0, 0, 0.5); display: -ms-grid; display: grid; margin: 2rem 1rem 0.25rem 0.36rem; width: calc(100% - 1rem); } sticky-note-wrapper sticky-note > div:nth-child(2) { clip-path: url(#stickyClip); display: -ms-grid; display: grid; font-family: "Kalam", cursive; font-style: 1rem; font-weight: 300; line-height: 1.25rem; padding: 1rem; place-items: center; text-align: center; } sticky-note-wrapper sticky-note > div:nth-child(2) > * { font-family: "Kalam", cursive !important; font-style: normal !important; font-weight: 300 !important; } @media screen and (max-width: 1024px) { sticky-note-wrapper sticky-note > div:nth-child(2) { max-width: 13rem; min-width: 13rem; width: 1rem; } } @media screen and (max-width: 768px) { sticky-note-wrapper sticky-note > div:nth-child(2) { font-size: 1.75rem; max-width: 21rem; min-height: 21rem; } } @media screen and (max-width: 640px) { sticky-note-wrapper sticky-note > div:nth-child(2) { font-size: 2.5rem; max-width: 26rem; min-height: 26rem; } } sticky-note-wrapper sticky-note > div:nth-child(2) { background: -webkit-gradient(linear, left top, left bottom, from(#ffffdd), color-stop(2%, #ffffd3), color-stop(12%, #ffffd3), color-stop(75%, #ffffc9), to(#ffffba)); background: linear-gradient(180deg, #ffffdd 0%, #ffffd3 2%, #ffffd3 12%, #ffffc9 75%, #ffffba 100%); } sticky-note-wrapper sticky-note.blue > div:nth-child(2) { background: -webkit-gradient(linear, left top, left bottom, from(#9dddec), color-stop(2%, #94daea), color-stop(12%, #94daea), color-stop(75%, #8bd7e8), to(#7fd3e6)); background: linear-gradient(180deg, #9dddec 0%, #94daea 2%, #94daea 12%, #8bd7e8 75%, #7fd3e6 100%); } sticky-note-wrapper sticky-note.pink > div:nth-child(2) { background: -webkit-gradient(linear, left top, left bottom, from(#fa7c93), color-stop(2%, #fa728b), color-stop(12%, #fa728b), color-stop(75%, #fa6883), to(#f95977)); background: linear-gradient(180deg, #fa7c93 0%, #fa728b 2%, #fa728b 12%, #fa6883 75%, #f95977 100%); } sticky-note-wrapper sticky-note.green > div:nth-child(2) { background: -webkit-gradient(linear, left top, left bottom, from(#c5fcc9), color-stop(2%, #bbfbc0), color-stop(12%, #bbfbc0), color-stop(75%, #b1fab7), to(#a3faaa)); background: linear-gradient(180deg, #c5fcc9 0%, #bbfbc0 2%, #bbfbc0 12%, #b1fab7 75%, #a3faaa 100%); } sticky-note-wrapper:has(sticky-note:hover) { background-color: #ffffd3; border: 1px solid black; } sticky-note-wrapper:has(sticky-note.yellow:hover) { background-color: #ffffd3; } sticky-note-wrapper:has(sticky-note.blue:hover) { background-color: #94daea; } sticky-note-wrapper:has(sticky-note.pink:hover) { background-color: #fa728b; } sticky-note-wrapper:has(sticky-note.green:hover) { background-color: #bbfbc0; }</pre> <pre class="language-css" data-tab="scss"> //- DS2 core (c) 2024 Alexander McIlwraith //- Licensed under CC BY-SA 4.0 @use 'sass:color'; @use "sass:map"; @use 'sass:list'; @import url('https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&display=swap'); $sticky-colors: ( "yellow": #ffffd3, "blue": #94daea, "pink": #fa728b, "green": #bbfbc0 ) !default; @mixin sticky-note { sticky-note-wrapper { box-sizing: border-box; border: 1px solid transparent; border-radius: 50%; display: inline-block; height: .5rem; position: relative; width: .5rem; & * { box-sizing: border-box; } sticky-note { cursor: grab; display: grid; float: left; font-size: 1rem; height: 13rem; left: 0; place-items: stretch; position: absolute; top: 0; width: 13rem; z-index: 100; &:active { cursor: grabbing; user-select: none; } @media screen and (max-width: 1024px) { //width: 15rem; // background-color: green; } @media screen and (max-width: 768px) { //width: 20rem; // background-color: blue; } @media screen and (max-width: 640px) { //width: 30rem; // background-color: yellow; } &.right { float: right; } @content; > div { grid-row: 1; grid-column: 1; &:nth-child(1) { background-color: rgba(0, 0, 0, 0.25); box-shadow: -2px 2px 15px 0 rgba(0, 0, 0, 0.5); display: grid; margin: 2rem 1rem .25rem .36rem; width: calc(100% - 1rem); } &:nth-child(2) { clip-path: url(#stickyClip); display: grid; font-family: "Kalam", cursive; font-style: 1rem; font-weight: 300; line-height: 1.25rem; padding: 1rem; place-items: center; text-align: center; // not adding this because maybe we want the sticky note text to be able to be selected and copied. // user-select: none; > * { font-family: "Kalam", cursive !important; font-style: normal !important; font-weight: 300 !important; } @media screen and (max-width: 1024px) { max-width: 13rem; min-width: 13rem; width: 1rem; } @media screen and (max-width: 768px) { font-size: 1.75rem; max-width: 21rem; min-height: 21rem; } @media screen and (max-width: 640px) { font-size: 2.5rem; max-width: 26rem; min-height: 26rem; } } } // default colour > div:nth-child(2) { $sticky-color: nth(map.values($sticky-colors), 1); background: linear-gradient( 180deg, lighten($sticky-color, 2%) 0%, $sticky-color 2%, $sticky-color 12%, darken($sticky-color, 2%) 75%, darken($sticky-color, 5%) 100% ); } // all class colors @each $class, $sticky-color in $sticky-colors { &.#{$class} > div:nth-child(2) { @if $class != nth(map.keys($sticky-colors), 1) { background: linear-gradient( 180deg, lighten($sticky-color, 2%) 0%, $sticky-color 2%, $sticky-color 12%, darken($sticky-color, 2%) 75%, darken($sticky-color, 5%) 100% ); } } } } &:has(sticky-note:hover) { background-color: #{nth(map.values($sticky-colors), 1)}; border: 1px solid black; } @each $class, $sticky-color in $sticky-colors { &:has(sticky-note.#{$class}:hover) { background-color: $sticky-color; } } } }</pre> <pre class="language-js" data-tab="js"> //- DS2 core (c) 2024 Alexander McIlwraith //- Licensed under CC BY-SA 4.0 const font = { size: 0 } const pos = { x: 0, y: 0 } function drag(sticky) { sticky.onmousedown = event => { // get the position within the sticky pos.x = (event.clientX - sticky.offsetLeft); pos.y = (event.clientY - sticky.offsetTop); // on mouse move, move the sticky to the position, minus the offset, of the mouse document.onmousemove = documentEvent => { sticky.style.top = (documentEvent.clientY - pos.y) + 'px'; sticky.style.left = (documentEvent.clientX - pos.x) + 'px'; sticky.setAttribute("moved", "true"); } // when done, remove mouse move and mouse up handlers. document.onmouseup = () => { document.onmouseup = null; document.onmousemove = null; } } } function waitForElement(selector) { return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { observer.disconnect(); resolve(document.querySelector(selector)); } }); // If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336 observer.observe(document.body, { childList: true, subtree: true }); }); } const checkBottom = (attach) => { // check if top or bottom if (attach.y < 0) { attach.ys = "bottom"; attach.y = attach.y * -1; } } const setStickyPostions = (s, attach) => { // set s.style[attach.ys] = `${attach.y}px`; s.style[attach.xs] = `${attach.x}px`; s.style.display = "grid"; } const css = (el, attr) => { let st = "" Object.entries(attr).forEach(val => { const [key, value] = val; st += `${key}: ${value}; `; }) el.setAttribute("style",st.trim()); } const calculateStickyPosition = (s) => { let float = s.getAttribute("float"); let p = s.parentNode.getBoundingClientRect() switch (float) { case "left": css(s, {left: (p.left * -1) + "px"}) break; case "right": css(s, { left: (Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0) - p.left - s.offsetWidth - (font.size * 2)) + "px" }); break; } let offset = s.getAttribute("offset"); if (offset !== null) { offset = offset.trim().split(" "); css(s, {top: offset[0], left: offset[1] }) } } export function init(p = document){ font.size = parseFloat(getComputedStyle(document.documentElement).fontSize.replace("px","")); p.querySelectorAll("sticky-note").forEach((s) => { if (s.querySelectorAll("svg").length == 0) { let wrapper = document.createElement("sticky-note-wrapper"); s.parentNode.insertBefore(wrapper, s); wrapper.appendChild(s); s.setAttribute("content", s.innerHTML.replace(/"/g, "\"")); s.innerHTML = `<div><svg width='0' height='0'><defs><clipPath id='stickyClip' clipPathUnits='objectBoundingBox'><path d='M 0 0 Q 0 0.69, 0.03 0.96 0.03 0.96, 1 0.96 Q 0.96 0.69, 0.96 0 0.96 0, 0 0' stroke-linejoin='round' stroke-linecap='square' /></clipPath></defs></svg></div><div><div>${s.innerHTML}</div></div>`; } calculateStickyPosition(s); drag(s); s.ondblclick = (e) => { if (e.ctrlKey) { calculateStickyPosition(s); } else { // add one click select } } }) window.onresize = () => { font.size = parseFloat(getComputedStyle(document.documentElement).fontSize.replace("px","")); let stickies = p.querySelectorAll("sticky-note"); stickies.forEach((s) => { calculateStickyPosition(s); }); } }</pre> </tabset> </body> </html>