Repository: exadel-inc/esl
Pull request #1358 (2022-12-01 18:53:20)

epic (esl-share): initial implementation


Patch:
diff --git a/package.json b/package.json
index 56276c0eb..97863197b 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,9 @@
     "modules/lib.js",
     "src/modules/lib.ts",
     "modules/esl-media/providers/**/*.js",
+    "modules/esl-share/actions/**/*.js",
     "src/modules/esl-media/providers/**/*.ts",
+    "src/modules/esl-share/actions/**/*.ts",
     "modules/draft/esl-carousel/core/view/*.js",
     "src/modules/draft/esl-carousel/core/view/*.ts",
     "polyfills/**/*.js",
diff --git a/pages/src/esl-share/esl-share.less b/pages/src/esl-share/esl-share.less
new file mode 100644
index 000000000..f75af649f
--- /dev/null
+++ b/pages/src/esl-share/esl-share.less
@@ -0,0 +1,12 @@
+esl-share-list esl-share-button[unavailable] {
+  display: inline-block;
+  opacity: .3;
+  cursor: not-allowed;
+  filter: grayscale(1);
+}
+
+esl-share-button {
+  path, circle {
+    fill: #fff;
+  }
+}
diff --git a/pages/src/localdev.less b/pages/src/localdev.less
index ee41f6492..6b3b86a33 100644
--- a/pages/src/localdev.less
+++ b/pages/src/localdev.less
@@ -33,6 +33,7 @@
 
 @import "./collection-grid/collection-grid.less";
 @import "./esl-media-demo/test-media.less";
+@import "./esl-share/esl-share.less";
 
 @import "../../src/modules/all.less";
 @import "../../src/modules/draft/all.less";
diff --git a/pages/src/localdev.ts b/pages/src/localdev.ts
index abd431ab3..207aa69e4 100644
--- a/pages/src/localdev.ts
+++ b/pages/src/localdev.ts
@@ -32,6 +32,7 @@ import {
   ESLFootnotes,
   ESLTooltip,
   ESLAnimate,
+  ESLShareList,
   ESLRelatedTarget
 } from '../../src/modules/all';
 
@@ -41,6 +42,12 @@ import '../../src/modules/esl-media/providers/html5/video-provider';
 import '../../src/modules/esl-media/providers/youtube-provider';
 import '../../src/modules/esl-media/providers/brightcove-provider';
 
+import '../../src/modules/esl-share/actions/copy-action';
+import '../../src/modules/esl-share/actions/external-action';
+import '../../src/modules/esl-share/actions/media-action';
+import '../../src/modules/esl-share/actions/native-action';
+import '../../src/modules/esl-share/actions/print-action';
+
 import {
   ESLCarousel,
   ESLCarouselPlugins
@@ -106,5 +113,8 @@ ESLCarouselPlugins.Link.register();
 ESLCarouselPlugins.Touch.register();
 ESLCarouselPlugins.Autoplay.register();
 
+ESLShareList.config(() => fetch('/assets/share/config.json').then((response) => response.json()));
+ESLShareList.register();
+
 // Register ESL Mixins
 ESLRelatedTarget.register();
diff --git a/pages/static/assets/examples/share.svg b/pages/static/assets/examples/share.svg
new file mode 100644
index 000000000..277e15c9a
--- /dev/null
+++ b/pages/static/assets/examples/share.svg
@@ -0,0 +1,10 @@
+<svg viewBox="0 0 483 483" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <g fill="#add1fa">
+    <path d="M395.72,0c-48.204,0-87.281,39.078-87.281,87.281c0,2.036,0.164,4.03,0.309,6.029l-161.233,75.674
+      c-15.668-14.971-36.852-24.215-60.231-24.215c-48.204,0.001-87.282,39.079-87.282,87.282c0,48.204,39.078,87.281,87.281,87.281
+      c15.206,0,29.501-3.907,41.948-10.741l69.789,58.806c-3.056,8.896-4.789,18.396-4.789,28.322c0,48.204,39.078,87.281,87.281,87.281
+      c48.205,0,87.281-39.078,87.281-87.281s-39.077-87.281-87.281-87.281c-15.205,0-29.5,3.908-41.949,10.74l-69.788-58.805
+      c3.057-8.891,4.789-18.396,4.789-28.322c0-2.035-0.164-4.024-0.308-6.029l161.232-75.674c15.668,14.971,36.852,24.215,60.23,24.215
+      c48.203,0,87.281-39.078,87.281-87.281C482.999,39.079,443.923,0,395.72,0z"/>
+  </g>
+</svg>
diff --git a/pages/static/assets/share/config.json b/pages/static/assets/share/config.json
new file mode 100644
index 000000000..0ba0dce84
--- /dev/null
+++ b/pages/static/assets/share/config.json
@@ -0,0 +1,177 @@
+{
+  "buttons": [
+    {
+      "action": "media",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 27.99 28\"\u003e\u003cpath d\u003d\"M23 17.11l.55-4.24h-4.2v-2.71c0-1.23.34-2.06 2.1-2.06h2.25V4.29a31.62 31.62 0 00-3.28-.16c-3.24 0-5.46 2-5.46 5.61v3.13h-3.63v4.24H15V28h4.39V17.11z\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#3c5996",
+      "link": "//www.facebook.com/sharer.php?u\u003d{u}",
+      "name": "facebook",
+      "title": "Facebook"
+    },
+    {
+      "action": "media",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 27.99 27.99\"\u003e\u003cpath d\u003d\"M6.37 11.33h3.25v10.45H6.37zM8 6.13A1.88 1.88 0 116.12 8 1.88 1.88 0 018 6.13M11.65 11.33h3.12v1.43h.05a3.4 3.4 0 013.07-1.69c3.29 0 3.9 2.17 3.9 5v5.73h-3.25v-5.11c0-1.21 0-2.77-1.69-2.77s-1.94 1.32-1.94 2.69v5.17h-3.25V11.33z\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#0b77b3",
+      "link": "//www.linkedin.com/sharing/share-offsite/?url\u003d{u}",
+      "name": "linkedin",
+      "title": "LinkedIn"
+    },
+    {
+      "action": "media",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 27.99 27.99\"\u003e\u003cpath d\u003d\"M21.84 9.14a6.34 6.34 0 01-1.84.51 3.25 3.25 0 001.4-1.78 6.22 6.22 0 01-2 .78 3.22 3.22 0 00-5.57 2.2 2.92 2.92 0 00.09.73 9.11 9.11 0 01-6.67-3.36 3.16 3.16 0 00-.43 1.62 3.21 3.21 0 001.43 2.68 3.25 3.25 0 01-1.46-.41 3.23 3.23 0 002.58 3.16 3 3 0 01-.84.11 3 3 0 01-.61-.05 3.22 3.22 0 003 2.23 6.39 6.39 0 01-4 1.38 6.19 6.19 0 01-.76-.05 9.14 9.14 0 0014.08-7.71v-.41a6.36 6.36 0 001.6-1.63z\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#26a1ef",
+      "link": "//twitter.com/intent/tweet?text\u003d{t}\u0026url\u003d{u}",
+      "name": "twitter",
+      "title": "Twitter"
+    },
+    {
+      "action": "media",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 32 32\"\u003e\u003cpath d\u003d\"M27 15.5a2.452 2.452 0 01-1.338 2.21c.098.38.147.777.147 1.19 0 1.283-.437 2.47-1.308 3.563-.872 1.092-2.06 1.955-3.567 2.588-1.506.634-3.143.95-4.91.95-1.768 0-3.403-.316-4.905-.95-1.502-.632-2.69-1.495-3.56-2.587-.872-1.092-1.308-2.28-1.308-3.562 0-.388.045-.777.135-1.166a2.47 2.47 0 01-1.006-.912c-.253-.4-.38-.842-.38-1.322 0-.678.237-1.26.712-1.744a2.334 2.334 0 011.73-.726c.697 0 1.29.26 1.78.782 1.785-1.258 3.893-1.928 6.324-2.01l1.424-6.467a.42.42 0 01.184-.26.4.4 0 01.32-.063l4.53 1.006c.147-.306.368-.553.662-.74a1.78 1.78 0 01.97-.278c.508 0 .94.18 1.302.54.36.36.54.796.54 1.31 0 .512-.18.95-.54 1.315-.36.364-.794.546-1.302.546-.507 0-.94-.18-1.295-.54a1.793 1.793 0 01-.533-1.308l-4.1-.92-1.277 5.86c2.455.074 4.58.736 6.37 1.985a2.315 2.315 0 011.757-.757c.68 0 1.256.242 1.73.726.476.484.713 1.066.713 1.744zm-16.868 2.47c0 .513.178.95.534 1.315.356.365.787.547 1.295.547.508 0 .942-.182 1.302-.547.36-.364.54-.802.54-1.315 0-.513-.18-.95-.54-1.31-.36-.36-.794-.54-1.3-.54-.5 0-.93.183-1.29.547a1.79 1.79 0 00-.54 1.303zm9.944 4.406c.09-.09.135-.2.135-.323a.444.444 0 00-.44-.447c-.124 0-.23.042-.32.124-.336.348-.83.605-1.486.77a7.99 7.99 0 01-1.964.248 7.99 7.99 0 01-1.964-.248c-.655-.165-1.15-.422-1.486-.77a.456.456 0 00-.32-.124.414.414 0 00-.306.124.41.41 0 00-.135.317.45.45 0 00.134.33c.352.355.837.636 1.455.843.617.207 1.118.33 1.503.366a11.6 11.6 0 001.117.056c.36 0 .733-.02 1.117-.056.385-.037.886-.16 1.504-.366.62-.207 1.104-.488 1.456-.844zm-.037-2.544c.507 0 .938-.182 1.294-.547.356-.364.534-.802.534-1.315 0-.505-.18-.94-.54-1.303a1.75 1.75 0 00-1.29-.546c-.506 0-.94.18-1.3.54-.36.36-.54.797-.54 1.31s.18.95.54 1.315c.36.365.794.547 1.3.547z\" fill-rule\u003d\"evenodd\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#ff5700",
+      "link": "//www.reddit.com/submit?url\u003d{u}\u0026title\u003d{t}",
+      "name": "reddit",
+      "title": "Reddit"
+    },
+    {
+      "action": "media",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 32 32\"\u003e\u003cpath d\u003d\"M19.996 11.757c1.905 0 3.45-1.513 3.45-3.38C23.445 6.513 21.9 5 19.995 5c-1.903 0-3.448 1.512-3.448 3.378s1.545 3.38 3.448 3.38zm4.995 5.233c-.09-2.574-2.242-4.638-4.893-4.638a4.934 4.934 0 00-3.24 1.206 3.62 3.62 0 00-3.318-2.133c-.944 0-1.8.356-2.443.935a2.596 2.596 0 00-2.494-1.82c-1.407 0-2.55 1.093-2.6 2.462H6v4.783h3.92v3.712h5.276V26H25v-9.01h-.01zm-11.526-6.006c1.405 0 2.545-1.116 2.545-2.492C16.01 7.115 14.87 6 13.463 6 12.06 6 10.92 7.114 10.92 8.49c0 1.376 1.14 2.492 2.544 2.492zm-4.914-.762c1.012 0 1.83-.803 1.83-1.794 0-.992-.818-1.795-1.83-1.795-1.01 0-1.83.804-1.83 1.795 0 .99.82 1.794 1.83 1.794z\" fill-rule\u003d\"evenodd\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#282828",
+      "link": "//myspace.com/post?u\u003d{u}",
+      "name": "myspace",
+      "title": "Myspace"
+    },
+    {
+      "action": "media",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 32 32\"\u003e\u003cpath d\u003d\"M26 6v11.077C26 18.123 25.105 19 24 19s-2-.877-2-1.923v-1a2 2 0 00-2-2 2 2 0 00-2 2v2.846c-.02 1.046-.915 1.923-2 1.923-1.12 0-2.013-.877-2-1.923V11.23a2.036 2.036 0 00-2-2c-1.062.024-1.922.822-2 1.847-.003.025-.003 4.275 0 12.75 0 1.045-.895 1.923-2 1.923s-2-.878-2-1.923V6h20z\" opacity\u003d\".8\"/\u003e\u003cpath d\u003d\"M6 6v10.77c2.135-.006 3.878-1.648 4-3.693V11.23a.702.702 0 010-.153c.078-1.025.938-1.823 2-1.846 1.092.024 1.986.902 2 2v7.693c-.013 1.046.88 1.923 2 1.923 1.085 0 1.98-.877 2-1.923v-5.23C17.98 9.458 21.502 6 25.846 6H6\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#eb4924",
+      "link": "//mix.com/add?url\u003d{u}",
+      "name": "mix",
+      "title": "MIX"
+    },
+    {
+      "action": "media",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 32 32\"\u003e\u003cpath d\u003d\"M14.722 19.784l-3.48-6.832-2.667 1.36 3.82 7.497.056-.03.313.61 10.608-5.404-.48-.944-3.65-7.165-2.667 1.36 3.48 6.83-1.332.68-3.48-6.832-2.666 1.358 3.48 6.832-1.332.68z\"/\u003e\u003cpath d\u003d\"M7.372 12.77c0-2.38 1.86-4.308 4.152-4.308h8.952c2.294 0 4.152 1.928 4.152 4.308v6.46c0 2.38-1.86 4.308-4.152 4.308h-8.952c-2.294 0-4.152-1.928-4.152-4.308v-6.46zM5 12.77v6.46C5 22.97 7.92 26 11.524 26h8.952C24.08 26 27 22.97 27 19.23v-6.46C27 9.03 24.08 6 20.476 6h-8.952C7.92 6 5 9.03 5 12.77z\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#fb803f",
+      "link": "//www.wykop.pl/dodaj?url\u003d{u}\u0026title\u003d{t}",
+      "name": "wykop",
+      "title": "Wykop"
+    },
+    {
+      "action": "media",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 24 24\"\u003e\u003cpath d\u003d\"M12.486 4.771c-4.23 0-6.363 3.033-6.363 5.562 0 1.533.581 2.894 1.823 3.401.205.084.387.004.446-.221l.182-.717c.061-.221.037-.3-.127-.495-.359-.422-.588-.972-.588-1.747 0-2.25 1.683-4.264 4.384-4.264 2.392 0 3.706 1.463 3.706 3.412 0 2.568-1.137 4.734-2.824 4.734-.932 0-1.629-.77-1.405-1.715.268-1.13.786-2.347.786-3.16 0-.729-.392-1.336-1.2-1.336-.952 0-1.718.984-1.718 2.304 0 .841.286 1.409.286 1.409L8.728 16.79c-.34 1.44-.051 3.206-.025 3.385.013.104.149.131.21.051.088-.115 1.223-1.517 1.607-2.915.111-.396.627-2.445.627-2.445.311.589 1.213 1.108 2.175 1.108 2.863 0 4.804-2.608 4.804-6.103-.003-2.64-2.24-5.1-5.64-5.1z\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#e60023",
+      "link": "//www.pinterest.com/pin/create/button/?url\u003d{u}\u0026description\u003d{t}",
+      "name": "pinterest",
+      "title": "Pinterest"
+    },
+    {
+      "action": "media",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 455.731 455.731\"\u003e\u003cpath d\u003d\"M358.844 100.6 54.091 219.359c-9.871 3.847-9.273 18.012.888 21.012l77.441 22.868 28.901 91.706c3.019 9.579 15.158 12.483 22.185 5.308l40.039-40.882 78.56 57.665c9.614 7.057 23.306 1.814 25.747-9.859l52.031-248.76c2.548-12.185-9.44-22.337-21.039-17.817zm-38.208 55.206L179.08 280.984c-1.411 1.248-2.309 2.975-2.519 4.847l-5.45 48.448c-.178 1.58-2.389 1.789-2.861.271l-22.423-72.253c-1.027-3.308.312-6.892 3.255-8.717l167.163-103.676c3.844-2.386 7.78 2.906 4.391 5.902z\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#61a8de",
+      "link": "//t.me/share/url?url\u003d{u}\u0026text\u003d{t}",
+      "name": "telegram",
+      "title": "Telegram"
+    },
+    {
+      "action": "media",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 32 32\"\u003e\u003cpath d\u003d\"M19.864 21.38H11.84a1.712 1.712 0 010-3.425h8.024a1.712 1.712 0 010 3.425zm-7.542-11.27l4.012.063a1.712 1.712 0 01-.054 3.424l-4.012-.064a1.712 1.712 0 01.054-3.424zm13.4 9.404c-.007-.374-.008-.71-.01-1.014-.006-1.58-.012-2.83-1.016-3.803-.716-.694-1.565-.914-2.855-.962.176-.747.226-1.575.145-2.47-.02-2.973-2.234-5.18-5.304-5.264h-.043l-4.692.072c-1.844-.007-3.3.53-4.332 1.606-.638.666-1.362 1.83-1.45 3.72H6.16v.057a8.6 8.6 0 00-.006.393l-.12 7.125c-.008.143-.015.288-.016.437-.12 2.088.372 3.728 1.463 4.876 1.078 1.132 2.664 1.706 4.715 1.706h7.32c1.84-.017 3.393-.624 4.494-1.757 1.1-1.132 1.692-2.743 1.713-4.66v-.06z\" fill-rule\u003d\"evenodd\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#f57d00",
+      "link": "//blogger.com/blog-this.g?n\u003d{t}\u0026u\u003d{u}",
+      "name": "blogger",
+      "title": "Blogger"
+    },
+    {
+      "action": "media",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 32 32\"\u003e\u003cpath d\u003d\"M29.27 22.19V8.07l-12.06 6.85 3.84 2.33C15.72 24.14 5.9 29.31 0 31.96V32h19.64c3.68-4.86 7.03-11.46 7.03-11.46l2.6 1.65z\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#0878ba",
+      "link": "//www.pusha.se/posta?url\u003d{u}\u0026title\u003d{t}",
+      "name": "pusha",
+      "title": "Pusha"
+    },
+    {
+      "action": "media",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 32 32\"\u003e\u003cpath d\u003d\"M20.826 6h-9.652C10.524 6 10 6.51 10 7.138v9.336c0 .63.525 1.14 1.174 1.14h4.45c-.03 1.11-.507 2.318-1.196 3.39-.775 1.207-2.426 2.543-2.44 2.555-.13.12-.226.26-.23.456 0 .15.08.263.167.385l3.104 3.395s.15.154.274.183c.14.033.3.037.41-.045 5.374-4.032 6.15-9.085 6.285-11.82V7.137C22 6.508 21.475 6 20.826 6\" fill-rule\u003d\"evenodd\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#fab900",
+      "link": "//story.kakao.com/share?url\u003d{u}",
+      "name": "kakao",
+      "title": "Kakao"
+    },
+    {
+      "action": "media",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 32 32\"\u003e\u003cpath d\u003d\"M27 14.927c0 1.97-.764 3.744-2.358 5.492-2.307 2.655-7.468 5.89-8.642 6.385-1.174.495-1-.315-.953-.593l.157-.94c.037-.282.075-.718-.035-.996-.125-.306-.612-.465-.97-.542-5.287-.7-9.2-4.395-9.2-8.807C5 10.005 9.935 6 16 6c6.066 0 11 4.005 11 8.927zm-13.235-2.38h-.772a.214.214 0 00-.214.215v4.793c0 .118.095.214.213.214h.772a.214.214 0 00.214-.215v-4.793a.214.214 0 00-.215-.214zm5.31 0h-.77a.214.214 0 00-.215.215v2.848l-2.197-2.967a.235.235 0 00-.017-.023l-.014-.013-.003-.004c-.005-.003-.01-.006-.013-.01-.002 0-.003-.002-.005-.004l-.01-.008a.262.262 0 00-.018-.01c-.004 0-.006-.003-.008-.004a.605.605 0 00-.013-.005l-.007-.002a.106.106 0 00-.02-.005.098.098 0 00-.012-.003h-.01c-.003-.002-.006-.002-.01-.003-.004 0-.007 0-.01-.002h-.78a.214.214 0 00-.214.214v4.793c0 .118.095.214.214.214h.77a.214.214 0 00.216-.215v-2.847l2.2 2.97c.014.022.033.04.053.053 0 .002.002.002.002.003l.014.008c0 .002.003.003.005.004l.01.005c.003 0 .007.002.01.004.003 0 .004 0 .007.002l.014.005s.002 0 .003.002a.206.206 0 00.054.007h.772a.214.214 0 00.214-.215v-4.793a.214.214 0 00-.214-.214zm-7.17 4.022H9.81V12.76a.214.214 0 00-.216-.214h-.77a.214.214 0 00-.216.214v4.793c0 .058.023.11.06.148l.003.003c.002 0 .003.002.005.003.038.036.09.06.147.06h3.083a.214.214 0 00.214-.215v-.772a.214.214 0 00-.215-.214zm11.432-2.822a.214.214 0 00.214-.214v-.77a.214.214 0 00-.213-.216h-3.082a.213.213 0 00-.15.06s0 .002-.002.003c0 .002-.003.003-.004.005a.214.214 0 00-.06.147v4.793c0 .057.023.11.06.148l.003.003.003.003c.04.036.09.06.148.06h3.083a.214.214 0 00.214-.215v-.772a.214.214 0 00-.213-.214H21.24v-.812h2.097a.214.214 0 00.214-.214v-.77a.214.214 0 00-.213-.216H21.24v-.81h2.097z\" fill-rule\u003d\"evenodd\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#00b900",
+      "link": "//social-plugins.line.me/lineit/share?url\u003d{u}",
+      "name": "line",
+      "title": "LINE"
+    },
+    {
+      "action": "media",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 32 32\"\u003e\u003cpath d\u003d\"M14.24 23.808c-3.64.367-6.785-1.307-7.022-3.734-.236-2.43 2.525-4.693 6.164-5.06 3.642-.367 6.786 1.307 7.02 3.734.24 2.43-2.522 4.696-6.16 5.06m7.28-8.063c-.31-.096-.523-.157-.362-.57.352-.898.39-1.672.006-2.227-.713-1.036-2.667-.98-4.907-.028 0 0-.705.312-.523-.253.343-1.125.29-2.065-.243-2.61-1.214-1.238-4.446.045-7.216 2.86C6.205 15.023 5 17.26 5 19.192c0 3.694 4.664 5.942 9.226 5.942 5.98 0 9.96-3.53 9.96-6.333.003-1.695-1.402-2.657-2.665-3.055m3.973-6.763a5.76 5.76 0 00-5.542-1.823.855.855 0 00-.646 1.015.84.84 0 001 .657c1.398-.303 2.912.138 3.938 1.295a4.254 4.254 0 01.865 4.113c-.144.45.1.93.542 1.076a.84.84 0 001.06-.55v-.002a5.973 5.973 0 00-1.218-5.78\"/\u003e\u003cpath d\u003d\"M23.276 11.018a2.8 2.8 0 00-2.698-.885.74.74 0 00-.56.876c.086.396.472.65.86.563.467-.102.977.046 1.32.432.343.388.437.915.29 1.378a.742.742 0 00.466.928.724.724 0 00.913-.474c.3-.947.113-2.026-.59-2.818M14.44 19.41c-.126.223-.408.328-.627.235-.218-.09-.285-.34-.16-.555.127-.215.397-.32.612-.234.22.08.298.33.176.555m-1.16 1.512c-.353.57-1.11.82-1.676.558-.56-.26-.726-.922-.374-1.48.35-.555 1.078-.802 1.642-.56.57.25.753.905.407 1.482m1.322-4.04c-1.733-.46-3.69.42-4.443 1.97-.77 1.583-.025 3.34 1.723 3.914 1.815.595 3.95-.318 4.695-2.023.734-1.67-.182-3.39-1.976-3.86\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#e6162d",
+      "link": "//service.weibo.com/share/share.php?url\u003d{u}\u0026title\u003d{t}",
+      "name": "sina-weibo",
+      "title": "Sina Weibo"
+    },
+    {
+      "action": "media",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 32 32\"\u003e\u003cpath d\u003d\"M16.087 5.36c-6.414 0-11.97 3.93-11.97 11.265 0 6.865 7.628 10.092 12.635 8.918v2.348S27.88 24.958 27.88 15.04c0-6.043-4.458-9.68-11.793-9.68zm6.768 14.63h-1.643v-5.634c0-.703-.506-1.565-1.566-1.565-.89 0-2.504.374-2.504 2.06v5.14H15.5v-5.202c0-1.57-.782-2.073-1.486-2.073-1.14 0-2.698.802-2.698 2.384v4.89H9.673v-8.92h1.643v1.02c.654-.538 1.548-1.02 2.698-1.02 1.21 0 2.07.428 2.58 1.27a4.444 4.444 0 013.052-1.19c1.947 0 3.21 1.772 3.21 3.172v5.667z\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#cfab59",
+      "link": "//mixi.jp/share.pl?u\u003d{u}",
+      "name": "mixi",
+      "title": "Mixi"
+    },
+    {
+      "action": "media",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 32 32\"\u003e\u003cpath d\u003d\"M6.96 8.22h7.33c1.25 0 2.21.37 2.88 1.1s1 1.64 1 2.72c0 .91-.24 1.69-.72 2.34-.32.43-.78.77-1.4 1.02.93.27 1.61.72 2.05 1.37.44.65.66 1.46.66 2.43 0 .8-.16 1.51-.47 2.15-.31.64-.74 1.14-1.28 1.51-.34.23-.84.4-1.52.5-.9.14-1.5.21-1.79.21H6.96V8.22zm3.88 6.02h1.74c.62 0 1.06-.13 1.3-.38.24-.26.37-.62.37-1.1 0-.44-.12-.8-.37-1.05-.24-.25-.67-.38-1.27-.38h-1.77v2.91zm0 6.03h2.04c.69 0 1.18-.15 1.46-.43s.43-.68.43-1.17c0-.45-.14-.82-.42-1.09-.28-.28-.77-.41-1.47-.41h-2.03c-.01-.01-.01 3.1-.01 3.1zM21.21 8.41h3.58v9.58h-3.58z\"/\u003e\u003ccircle cx\u003d\"23\" cy\u003d\"21.53\" r\u003d\"2.04\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#08aed9",
+      "link": "//b.hatena.ne.jp/entry/panel/?url\u003d{u}\u0026btitle\u003d{t}",
+      "name": "hatena",
+      "title": "Hatena"
+    },
+    {
+      "action": "external",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 28 28\"\u003e\u003cpath d\u003d\"M4.63 6.94v14.1h18.8V6.94H4.63zm2.44 1.78h13.9l-6.93 5.67-6.93-5.67zm14.6 1.72v8.82H6.47v-8.79l7.59 6.21 7.63-6.24z\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#ff1493",
+      "link": "mailto:?subject\u003d{title}\u0026body\u003d{url}",
+      "name": "mail",
+      "title": "Mail"
+    },
+    {
+      "action": "copy",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 28 28\"\u003e\u003cpath d\u003d\"M17 9.69l-7.43 7.43 1.18 1.18 7.43-7.43L17 9.69z\"/\u003e\u003cpath d\u003d\"M4.31 17.8c-.481.481-.48 1.29.00138 1.77l4.02 4.02c.481.481 1.29.483 1.77.00138l4.95-4.95c.481-.481.481-1.29-7e-7-1.78l-4.02-4.02c-.481-.481-1.29-.481-1.78 0l-4.95 4.95zm1.47.887l4.36-4.36 3.44 3.44-4.36 4.36-3.44-3.44zm7-9.37c-.481.481-.481 1.29 2.8e-7 1.78l4.02 4.02c.481.481 1.29.481 1.78 0l4.95-4.95c.481-.481.48-1.29-.00138-1.77l-4.02-4.02c-.481-.481-1.29-.483-1.77-.00138l-4.95 4.95zm1.47.889l4.36-4.36 3.44 3.44-4.36 4.36-3.44-3.44z\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#a0522d",
+      "link": "",
+      "name": "copy",
+      "title": "Copy",
+      "additional": {
+        "alertText": "Copied to clipboard"
+      }
+    },
+    {
+      "action": "native",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 24 24\"\u003e\u003cpath d\u003d\"m21.7 10.2-6.6-6c-.5-.5-1.1 0-1.1.8v3c-4.7 0-8.7 2.9-10.6 6.8-.7 1.3-1.1 2.7-1.4 4.1-.2 1 1.3 1.5 1.9.6C6.1 16 9.8 13.7 14 13.7V17c0 .8.6 1.3 1.1.8l6.6-6c.4-.4.4-1.2 0-1.6z\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#ff6347",
+      "link": "",
+      "name": "native-share",
+      "title": "Share page"
+    },
+    {
+      "action": "print",
+      "icon": "\u003csvg xmlns\u003d\"http://www.w3.org/2000/svg\" aria-hidden\u003d\"true\" focusable\u003d\"false\" role\u003d\"presentation\" viewBox\u003d\"0 0 247.1 247.1\"\u003e\u003cpath d\u003d\"M75.4 175.5h97v6.5h-97zM75.4 194.9h97v6.5h-97z\"/\u003e\u003ccircle stroke-width\u003d\"2\" stroke-miterlimit\u003d\"10\" cx\u003d\"178.9\" cy\u003d\"117.3\" r\u003d\"6.5\"/\u003e\u003cpath d\u003d\"M198.3 84.9h-12.9V39.6c0-7.1-5.8-12.9-12.9-12.9h-97c-7.1 0-12.9 5.8-12.9 12.9v45.3H49.5c-11 0-19.4 8.4-19.4 19.4v58.2c0 11 8.4 19.4 19.4 19.4h12.9v25.9c0 7.1 5.8 12.9 12.9 12.9h97c7.1 0 12.9-5.8 12.9-12.9v-25.9h12.9c11 0 19.4-8.4 19.4-19.4v-58.2c.2-11-8.2-19.4-19.2-19.4zM68.9 39.6c0-3.2 2.6-6.5 6.5-6.5h97c3.2 0 6.5 2.6 6.5 6.5v45.3h-110V39.6zm110 168.2c0 3.2-2.6 6.5-6.5 6.5h-97c-3.2 0-6.5-2.6-6.5-6.5v-45.3h110v45.3zm32.3-45.3c0 7.1-5.8 12.9-12.9 12.9h-12.9V156h-123v19.4H49.5c-7.1 0-12.9-5.8-12.9-12.9v-58.2c0-7.1 5.8-12.9 12.9-12.9h148.8c7.1 0 12.9 5.8 12.9 12.9v58.2z\"/\u003e\u003c/svg\u003e",
+      "iconBackground": "#2f4f4f",
+      "link": "",
+      "name": "print",
+      "title": "Print page"
+    }
+  ],
+  "groups": [
+    {
+      "id": "demo",
+      "list": "facebook,twitter,linkedin,wykop,copy"
+    },
+    {
+      "id": "alternative",
+      "list": "kakao,hatena,mixi,line,mail"
+    }
+  ]
+}
diff --git a/pages/views/examples/share.njk b/pages/views/examples/share.njk
new file mode 100644
index 000000000..28f34330a
--- /dev/null
+++ b/pages/views/examples/share.njk
@@ -0,0 +1,71 @@
+---
+layout: content
+title: Share
+name: Share
+tags: [examples, beta]
+icon: examples/share.svg
+aside:
+  components:
+    - esl-share
+---
+{% import 'lorem.njk' as lorem %}
+
+<section>
+  <h2>Share example</h2>
+
+  <h3>Just a standalone share button</h3>
+  <esl-share-button action="media" link="//www.linkedin.com/sharing/share-offsite/?url={u}" name="linkedin" data-share-title="Standalone share button" data-share-url="/standalone.html" tabindex="0" role="button" title="LinkedIn">
+    <span title="Linkedin" class="esl-share-icon" style="background-color:#0b77b3;">
+      <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" role="presentation" viewBox="0 0 27.99 27.99"><path d="M6.37 11.33h3.25v10.45H6.37zM8 6.13A1.88 1.88 0 116.12 8 1.88 1.88 0 018 6.13M11.65 11.33h3.12v1.43h.05a3.4 3.4 0 013.07-1.69c3.29 0 3.9 2.17 3.9 5v5.73h-3.25v-5.11c0-1.21 0-2.77-1.69-2.77s-1.94 1.32-1.94 2.69v5.17h-3.25V11.33z"></path></svg>
+    </span>
+  </esl-share-button>
+  <hr>
+
+  <h3>Display list of share buttons of group 'demo'</h3>
+  <p>Shows buttons from a specified group.</p>
+  <esl-share-list group="demo"></esl-share-list>
+  <hr>
+
+  <h3>Display list of share buttons of group 'alternative'</h3>
+  <p>Shows buttons from a specified group.</p>
+  <esl-share-list group="alternative"></esl-share-list>
+  <hr>
+
+  <h3>Display list of share buttons on nonexistent group</h3>
+  <p>Shows buttons from a specified group. Displays all buttons from config when a nonexistent group is specified.</p>
+  <esl-share-list group="foo"></esl-share-list>
+  <hr>
+
+  <h3>Display list of share buttons via list of buttons id</h3>
+  <p>Shows specified buttons. Displays nothing in the case when specified wrong button id.</p>
+  <esl-share-list list="kakao,pusha,alladin,mix,myspace"></esl-share-list>
+  <hr>
+
+  <h3>List of share buttons which presents buttons with all action types</h3>
+  <p>This example shows a button with the native share mechanism on the device which will be inactive on the desktop browser.</p>
+  <esl-share-list list="copy,linkedin,mail,native-share,print"></esl-share-list>
+  <hr>
+
+  <h3>Display list of share buttons via incorrect list</h3>
+  <p>Shows buttons only with the correct id.</p>
+  <esl-share-list list="twittor , copi"></esl-share-list>
+  <hr>
+
+  <h3>Display list of share buttons (without group or list)</h3>
+  <p>Shows all buttons from config.</p>
+  <esl-share-list data-share-url="https://esl-ui.com/" data-share-title="Exadel Smart Library"></esl-share-list>
+  <hr>
+
+  <h3>Trigger icon with share popup</h3>
+  <p>Displays trigger icon. Hovering or clicking on the icon shows a popup with the list of share icons.</p>
+  <esl-trigger target="#demo-share-popup" mode="toggle" track-click track-hover style="display: inline-block; width: 40px; height: 40px;">
+    <svg viewBox="0 0 483 483" fill="none" xmlns="http://www.w3.org/2000/svg">
+      <g fill="#add1fa">
+        <path d="M395.72,0c-48.204,0-87.281,39.078-87.281,87.281c0,2.036,0.164,4.03,0.309,6.029l-161.233,75.674 c-15.668-14.971-36.852-24.215-60.231-24.215c-48.204,0.001-87.282,39.079-87.282,87.282c0,48.204,39.078,87.281,87.281,87.281 c15.206,0,29.501-3.907,41.948-10.741l69.789,58.806c-3.056,8.896-4.789,18.396-4.789,28.322c0,48.204,39.078,87.281,87.281,87.281 c48.205,0,87.281-39.078,87.281-87.281s-39.077-87.281-87.281-87.281c-15.205,0-29.5,3.908-41.949,10.74l-69.788-58.805 c3.057-8.891,4.789-18.396,4.789-28.322c0-2.035-0.164-4.024-0.308-6.029l161.232-75.674c15.668,14.971,36.852,24.215,60.23,24.215 c48.203,0,87.281-39.078,87.281-87.281C482.999,39.079,443.923,0,395.72,0z"/>
+      </g>
+    </svg>
+  </esl-trigger>
+  <esl-popup id="demo-share-popup" position="top" default-params="{hideDelay: 500}" close-on-esc close-on-outside-action>
+    <esl-share-list group="alternative" style="margin: 10px;"></esl-share-list><span class="esl-popup-arrow"></span>
+  </esl-popup>
+</section>
diff --git a/src/modules/all.less b/src/modules/all.less
index 3e592ab87..05fab970e 100644
--- a/src/modules/all.less
+++ b/src/modules/all.less
@@ -23,3 +23,5 @@
 @import "./esl-tooltip/core.less";
 
 @import "./esl-animate/core.less";
+
+@import "./esl-share/core.less";
diff --git a/src/modules/all.ts b/src/modules/all.ts
index 0449baa81..a6dde9383 100644
--- a/src/modules/all.ts
+++ b/src/modules/all.ts
@@ -42,3 +42,6 @@ export * from './esl-animate/core';
 
 // Related Target Mixin
 export * from './esl-related-target/core';
+
+// Share
+export * from './esl-share/core';
diff --git a/src/modules/esl-share/README.md b/src/modules/esl-share/README.md
new file mode 100644
index 000000000..1348190d3
--- /dev/null
+++ b/src/modules/esl-share/README.md
@@ -0,0 +1,15 @@
+# [ESL](../../../) Share
+
+Version: *1.0.0-beta*.
+
+Authors: *Dmytro Shovchko*.
+
+***Important Notice: the component is under beta version, it is tested and ready to use but be aware of its potential critical API changes.***
+
+<a name="intro"></a>
+
+The ESL Share component allows the user to share the page on social media platforms.
+
+**ESLShareButton** is a custom element that is used to show the "Share on social media" button.
+
+**ESLShareList** is a custom element that is used to show the list of social media buttons.
diff --git a/src/modules/esl-share/actions/copy-action.ts b/src/modules/esl-share/actions/copy-action.ts
new file mode 100644
index 000000000..f8b8cf60d
--- /dev/null
+++ b/src/modules/esl-share/actions/copy-action.ts
@@ -0,0 +1,31 @@
+import {ESLShareBaseAction} from '../core/esl-share-action';
+import {ESLEventUtils} from '../../esl-utils/dom/events';
+
+import type {ESLShareButton} from '../core/esl-share-button';
+
+@ESLShareBaseAction.register
+export class ESLShareCopyAction extends ESLShareBaseAction {
+  public static override readonly is: string = 'copy';
+
+  public override get isAvailable(): boolean {
+    return navigator.clipboard !== undefined;
+  }
+
+  public share($button: ESLShareButton): void {
+    const shareData = this.getShareData($button);
+    const {url} = shareData;
+    if (!this.isAvailable || !url) return;
+
+    navigator.clipboard.writeText(url);
+    this.showCopyAlert($button.additional?.alertText);
+  }
+
+  protected showCopyAlert(alertText: string): void {
+    if (!alertText) return;
+    const detail = {
+      cls: 'esl-share-alert',
+      html: `<span>${alertText}</span>`
+    };
+    ESLEventUtils.dispatch(document.body, 'esl:alert:show', {detail});
+  }
+}
diff --git a/src/modules/esl-share/actions/external-action.ts b/src/modules/esl-share/actions/external-action.ts
new file mode 100644
index 000000000..e6c385185
--- /dev/null
+++ b/src/modules/esl-share/actions/external-action.ts
@@ -0,0 +1,18 @@
+import {ESLShareUrlGenericAction} from './url-generic-action';
+
+import type {ESLShareButton} from '../core/esl-share-button';
+
+@ESLShareUrlGenericAction.register
+export class ESLShareExternalAction extends ESLShareUrlGenericAction {
+  public static override readonly is: string = 'external';
+
+  public share($button: ESLShareButton): void {
+    const {link} = $button;
+    if (!link) return;
+
+    const shareData = this.getShareData($button);
+    const a = document.createElement('a');
+    a.href = this.buildURL(link, shareData);
+    a.click();
+  }
+}
diff --git a/src/modules/esl-share/actions/media-action.ts b/src/modules/esl-share/actions/media-action.ts
new file mode 100644
index 000000000..0612d41fa
--- /dev/null
+++ b/src/modules/esl-share/actions/media-action.ts
@@ -0,0 +1,33 @@
+import {ESLShareUrlGenericAction} from './url-generic-action';
+
+import type {ESLShareButton} from '../core/esl-share-button';
+
+@ESLShareUrlGenericAction.register
+export class ESLShareMediaAction extends ESLShareUrlGenericAction {
+  public static override readonly is: string = 'media';
+
+  public static FEATURES: Record<string, number> = {
+    scrollbars: 0,
+    resizable: 1,
+    menubar: 0,
+    left: 100,
+    top: 100,
+    width: 750,
+    height: 500,
+    toolbar: 0,
+    status: 0
+  };
+
+  protected get windowFeatures(): string {
+    const features = (this.constructor as typeof ESLShareMediaAction).FEATURES;
+    return Object.entries(features).map(([key, value]) => `${key}=${value}`).join(',');
+  }
+
+  public share($button: ESLShareButton): void {
+    const {link} = $button;
+    if (!link) return;
+
+    const shareData = this.getShareData($button);
+    window.open(this.buildURL(link, shareData), '_blank', this.windowFeatures);
+  }
+}
diff --git a/src/modules/esl-share/actions/native-action.ts b/src/modules/esl-share/actions/native-action.ts
new file mode 100644
index 000000000..7539104da
--- /dev/null
+++ b/src/modules/esl-share/actions/native-action.ts
@@ -0,0 +1,19 @@
+import {ESLShareBaseAction} from '../core/esl-share-action';
+
+import type {ESLShareButton} from '../core/esl-share-button';
+
+@ESLShareBaseAction.register
+export class ESLShareNativeAction extends ESLShareBaseAction {
+  public static override readonly is: string = 'native';
+
+  public override get isAvailable(): boolean {
+    return navigator.share !== undefined;
+  }
+
+  public share($button: ESLShareButton): void {
+    if (!this.isAvailable) return;
+
+    const shareData = this.getShareData($button);
+    navigator.share(shareData);
+  }
+}
diff --git a/src/modules/esl-share/actions/print-action.ts b/src/modules/esl-share/actions/print-action.ts
new file mode 100644
index 000000000..26fd5a56d
--- /dev/null
+++ b/src/modules/esl-share/actions/print-action.ts
@@ -0,0 +1,12 @@
+import {ESLShareBaseAction} from '../core/esl-share-action';
+
+import type {ESLShareButton} from '../core/esl-share-button';
+
+@ESLShareBaseAction.register
+export class ESLSharePrintAction extends ESLShareBaseAction {
+  public static override readonly is: string = 'print';
+
+  public share($button: ESLShareButton): void {
+    window.print();
+  }
+}
diff --git a/src/modules/esl-share/actions/url-generic-action.ts b/src/modules/esl-share/actions/url-generic-action.ts
new file mode 100644
index 000000000..bc7c69618
--- /dev/null
+++ b/src/modules/esl-share/actions/url-generic-action.ts
@@ -0,0 +1,20 @@
+import {ESLShareBaseAction} from '../core/esl-share-action';
+import {format} from '../../esl-utils/misc/format';
+
+export abstract class ESLShareUrlGenericAction extends ESLShareBaseAction {
+
+  protected getFormatSource(shareData: ShareData): Record<string, string> {
+    const title = encodeURIComponent(shareData.title || '');
+    const url = encodeURIComponent(shareData.url || '');
+    return {
+      u: url,
+      t: title,
+      url,
+      title
+    };
+  }
+
+  protected buildURL(link: string, shareData: ShareData): string {
+    return format(link, this.getFormatSource(shareData));
+  }
+}
diff --git a/src/modules/esl-share/core.less b/src/modules/esl-share/core.less
new file mode 100644
index 000000000..bf8988ec0
--- /dev/null
+++ b/src/modules/esl-share/core.less
@@ -0,0 +1,2 @@
+@import "./core/esl-share-button.less";
+@import "./core/esl-share-list.less";
diff --git a/src/modules/esl-share/core.ts b/src/modules/esl-share/core.ts
new file mode 100644
index 000000000..599dee6b3
--- /dev/null
+++ b/src/modules/esl-share/core.ts
@@ -0,0 +1,4 @@
+export * from './core/esl-share-list';
+export * from './core/esl-share-action';
+export * from './core/esl-share-action-registry';
+export * from './core/esl-share-button';
diff --git a/src/modules/esl-share/core/esl-share-action-registry.ts b/src/modules/esl-share/core/esl-share-action-registry.ts
new file mode 100644
index 000000000..0a4b2d03d
--- /dev/null
+++ b/src/modules/esl-share/core/esl-share-action-registry.ts
@@ -0,0 +1,36 @@
+import {memoize} from '../../esl-utils/decorators';
+
+import type {ActionType, ESLShareBaseAction} from './esl-share-action';
+import type {ESLShareButton} from './esl-share-button';
+
+export class ESLShareActionRegistry {
+  private actionsMap: Map<string, ESLShareBaseAction> = new Map();
+
+  @memoize()
+  public static get instance(): ESLShareActionRegistry {
+    return new ESLShareActionRegistry();
+  }
+
+  /** Register action */
+  public register(action: ActionType): void {
+    if (!action.is) throw new Error('Action should have a name');
+    this.actionsMap.set(action.is, new action());
+  }
+
+  /** Checks if action is registered for passed name */
+  public has(name: string): boolean {
+    return this.actionsMap.has(name);
+  }
+
+  /** Get action by name */
+  public get(name: string): ESLShareBaseAction | null {
+    if (!name) return null;
+    return this.actionsMap.get(name.toLowerCase()) || null;
+  }
+
+  /** Do the share action at passed Share button */
+  public share(button: ESLShareButton): void {
+    const action = this.get(button.action);
+    action && action.share(button);
+  }
+}
diff --git a/src/modules/esl-share/core/esl-share-action.ts b/src/modules/esl-share/core/esl-share-action.ts
new file mode 100644
index 000000000..2c690c01c
--- /dev/null
+++ b/src/modules/esl-share/core/esl-share-action.ts
@@ -0,0 +1,32 @@
+import {ESLShareActionRegistry} from './esl-share-action-registry';
+
+import type {ESLShareButton} from './esl-share-button';
+
+export type ActionType = (new() => ESLShareBaseAction) & typeof ESLShareBaseAction;
+
+export abstract class ESLShareBaseAction {
+  public static readonly is: string;
+
+  /** Register this action. Can be used as a decorator */
+  public static register(this: ActionType): void;
+  public static register(this: unknown, action?: ActionType): void;
+  public static register(this: any, action?: ActionType): void {
+    action = action || this;
+    if (action === ESLShareBaseAction) throw new Error('`ESLShareBaseAction` can\'t be registered.');
+    if (!(action?.prototype instanceof ESLShareBaseAction)) throw new Error('Action should be instanceof `ESLShareBaseAction`');
+    ESLShareActionRegistry.instance.register(action);
+  }
+
+  public get isAvailable(): boolean {
+    return true;
+  }
+
+  protected getShareData($button: ESLShareButton): ShareData {
+    return {
+      url: $button.urlToShare,
+      title: $button.titleToShare
+    };
+  }
+
+  public abstract share($button: ESLShareButton): void;
+}
diff --git a/src/modules/esl-share/core/esl-share-button.less b/src/modules/esl-share/core/esl-share-button.less
new file mode 100644
index 000000000..d17d50f5e
--- /dev/null
+++ b/src/modules/esl-share/core/esl-share-button.less
@@ -0,0 +1,22 @@
+esl-share-button {
+  display: inline-block;
+  width: 40px;
+  height: 40px;
+  cursor: pointer;
+
+  &:hover {
+    opacity: .6;
+  }
+
+  &[unavailable] {
+    display: none;
+  }
+
+  .esl-share-icon {
+    display: inline-block;
+    overflow: hidden;
+    background-color: #0097e7; // TODO: consider creating common variables.less file
+    width: 100%;
+    height: 100%;
+  }
+}
diff --git a/src/modules/esl-share/core/esl-share-button.ts b/src/modules/esl-share/core/esl-share-button.ts
new file mode 100644
index 000000000..018038540
--- /dev/null
+++ b/src/modules/esl-share/core/esl-share-button.ts
@@ -0,0 +1,88 @@
+import {ESLBaseElement} from '../../esl-base-element/core';
+import {attr, boolAttr, jsonAttr, listen} from '../../esl-utils/decorators';
+import {ENTER, SPACE} from '../../esl-utils/dom/keys';
+import {toAbsoluteUrl} from '../../esl-utils/misc/url';
+import {ESLShareActionRegistry} from './esl-share-action-registry';
+
+import type {ESLShareBaseAction} from './esl-share-action';
+import type {ESLShareList} from './esl-share-list';
+
+function getProp<T>(name: string, targets: Record<string, any>[], fallback: T, predicate: (val: T) => boolean): T {
+  const find = targets.find((target) => predicate(target[name]));
+  return find ? find[name] : fallback;
+}
+
+export class ESLShareButton extends ESLBaseElement {
+  public static override is = 'esl-share-button';
+  public static observedAttributes = ['action'];
+
+  @attr() public action: string;
+  @attr() public link: string;
+  @attr() public name: string;
+
+  @attr({dataAttr: true}) public shareUrl: string;
+  @attr({dataAttr: true}) public shareTitle: string;
+
+  @jsonAttr({dataAttr: true}) public additional: Record<string, any>;
+
+  @boolAttr() public unavailable: boolean;
+
+  protected get actionInstance(): ESLShareBaseAction | null {
+    return ESLShareActionRegistry.instance.get(this.action);
+  }
+
+  public get host(): ESLShareList | null {
+    return this.closest('esl-share-list');
+  }
+
+  public get titleToShare(): string {
+    return this._getPropFromRelatedEls('shareTitle', document.title);
+  }
+
+  public get urlToShare(): string {
+    return toAbsoluteUrl(this._getPropFromRelatedEls('shareUrl', window.location.href));
+  }
+
+  protected attributeChangedCallback(attrName: string, oldVal: string, newVal: string): void {
+    if (!this.connected || oldVal === newVal) return;
+    if (attrName === 'action') this.updateAction();
+  }
+
+  protected override connectedCallback(): void {
+    super.connectedCallback();
+    this.initA11y();
+    this.updateAction();
+  }
+
+  public initA11y(): void {
+    if (!this.hasAttribute('role')) this.setAttribute('role', 'button');
+    if (this.getAttribute('role') === 'button' && !this.hasAttribute('tabindex')) {
+      this.tabIndex = 0;
+    }
+  }
+
+  public share(): void {
+    this.actionInstance?.share(this);
+  }
+
+  protected updateAction(): void {
+    this.$$attr('unavailable', !this.actionInstance?.isAvailable);
+  }
+
+  protected _getPropFromRelatedEls(name: string, fallback: string): string {
+    return getProp(name, [this, this.host ?? {}], fallback, (val: string) => !!val.length);
+  }
+
+  @listen('click')
+  protected _onClick(e: MouseEvent): void {
+    this.share();
+  }
+
+  @listen('keydown')
+  protected _onKeydown(e: KeyboardEvent): void {
+    if ([ENTER, SPACE].includes(e.key)) {
+      this.click();
+      e.preventDefault();
+    }
+  }
+}
diff --git a/src/modules/esl-share/core/esl-share-list.less b/src/modules/esl-share/core/esl-share-list.less
new file mode 100644
index 000000000..342e326a3
--- /dev/null
+++ b/src/modules/esl-share/core/esl-share-list.less
@@ -0,0 +1,3 @@
+esl-share-list {
+  display: block;
+}
diff --git a/src/modules/esl-share/core/esl-share-list.ts b/src/modules/esl-share/core/esl-share-list.ts
new file mode 100644
index 000000000..66ea329ed
--- /dev/null
+++ b/src/modules/esl-share/core/esl-share-list.ts
@@ -0,0 +1,121 @@
+import {ESLBaseElement} from '../../esl-base-element/core';
+import {attr, bind, boolAttr, prop} from '../../esl-utils/decorators';
+
+import {ESLShareButton} from './esl-share-button';
+
+export type ESLShareConfigProviderType = () => Promise<ShareConfig>;
+
+export interface ShareButtonConfig {
+  action: string;
+  icon: string;
+  iconBackground: string;
+  link: string;
+  name: string;
+  title: string;
+  additional?: Record<string, any>;
+}
+
+export interface ShareGroupConfig {
+  id: string;
+  list: string;
+}
+
+export interface ShareConfig {
+  buttons: ShareButtonConfig[];
+  groups: ShareGroupConfig[];
+}
+
+export class ESLShareList extends ESLBaseElement {
+  public static override is = 'esl-share-list';
+  protected static _config: Promise<ShareConfig> = Promise.reject('Configuration is not set');
+
+  public static override register(): void {
+    ESLShareButton.register();
+    super.register();
+  }
+
+  /** Event to dispatch on ready state of {@link ESLShareList} */
+  @prop('esl:share:ready') public SHARE_READY_EVENT: string;
+
+  @attr({readonly: true}) public list: string;
+  @attr({readonly: true}) public group: string;
+  @attr({dataAttr: true}) public shareUrl: string;
+  @attr({dataAttr: true}) public shareTitle: string;
+
+  @boolAttr({readonly: true}) public ready: boolean;
+
+  public get alias(): string {
+    return (this.constructor as typeof ESLBaseElement).is;
+  }
+
+  public static config(provider?: ESLShareConfigProviderType | ShareConfig): Promise<ShareConfig> {
+    if (typeof provider === 'function') ESLShareList._config = provider();
+    if (typeof provider === 'object') ESLShareList._config = Promise.resolve(provider);
+    return ESLShareList._config;
+  }
+
+  public get buttonsConfig(): Promise<ShareButtonConfig[]> {
+    return (this.constructor as typeof ESLShareList).config().then((config) => {
+      const list = this.getList(config);
+      return list.length ? this.getButtons(config, list) : config.buttons;
+    });
+  }
+
+  protected getButtons(config: ShareConfig, idList: string[]): ShareButtonConfig[] {
+    const {buttons} = config;
+    const res: ShareButtonConfig[] = [];
+    idList.forEach((name) => {
+      const btnConfig = buttons.find((btn) => btn.name === name);
+      btnConfig && res.push(btnConfig);
+    });
+    return res;
+  }
+
+  protected getList(config: ShareConfig): string[] {
+    const {group} = this;
+    let {list} = this;
+    if (group) {
+      const groupCfg = config.groups.find((e) => e.id === group);
+      if (groupCfg) {
+        list = groupCfg.list;
+      }
+    }
+    return list ? list.split(',').map((id) => id.trim()) : [];
+  }
+
+  public override connectedCallback(): void {
+    super.connectedCallback();
+    if (!this.ready) {
+      this.init();
+    }
+  }
+
+  protected init(): void {
+    this.buttonsConfig.then(this.build)
+      .then(() => this.$$fire(this.SHARE_READY_EVENT, {bubbles: false}))
+      .catch((e) => console.error(`[${this.alias}]: ${e}`));
+  }
+
+  @bind
+  public build(config: ShareButtonConfig[]): void {
+    this.innerHTML = '';
+    config.forEach((btnCfg) => {
+      const btn = this.buildButton(btnCfg);
+      btn && this.appendChild(btn);
+    });
+
+    this.toggleAttribute('ready', true);
+  }
+
+  protected buildButton(cfg: ShareButtonConfig): ESLShareButton | null {
+    const $button = ESLShareButton.create();
+    Object.assign($button, cfg);
+    const $icon = document.createElement('span');
+    $icon.title = cfg.title;
+    $icon.classList.add('esl-share-icon');
+    $icon.innerHTML = cfg.icon;
+    $icon.setAttribute('style', `background-color:${cfg.iconBackground};`);
+    $button.appendChild($icon);
+    return $button;
+  }
+}
diff --git a/src/modules/esl-utils/misc.ts b/src/modules/esl-utils/misc.ts
index e1bfe5e29..d8000180c 100644
--- a/src/modules/esl-utils/misc.ts
+++ b/src/modules/esl-utils/misc.ts
@@ -6,6 +6,7 @@ export * as ArrayUtils from './misc/array';
 export * as ObjectUtils from './misc/object';
 export * as FormatUtils from './misc/format';
 export * as FunctionUtils from './misc/functions';
+export * as UrlUtils from './misc/url';
 
 export {sequentialUID, randUID} from './misc/uid';
 export {range, tuple, wrap, uniq} from './misc/array';
diff --git a/src/modules/esl-utils/misc/url.ts b/src/modules/esl-utils/misc/url.ts
new file mode 100644
index 000000000..d673c0c7f
--- /dev/null
+++ b/src/modules/esl-utils/misc/url.ts
@@ -0,0 +1,8 @@
+/**
+ * Converts relative URL to absolute URL based on the base URL.
+ * @param relative - relative URL
+ * @param base - base URL (document.baseURI by default)
+ **/
+export function toAbsoluteUrl(relative: string, base: string = document.baseURI): string {
+  return new URL(relative, base).href;
+}


Test patch:
diff --git a/src/modules/esl-utils/misc/test/url.test.ts b/src/modules/esl-utils/misc/test/url.test.ts
new file mode 100644
index 000000000..7057c0437
--- /dev/null
+++ b/src/modules/esl-utils/misc/test/url.test.ts
@@ -0,0 +1,14 @@
+import {toAbsoluteUrl} from '../url';
+
+describe('misc/url helper tests', () => {
+  test('1', () => {
+    expect(toAbsoluteUrl('')).toBe('http://localhost/');
+    expect(toAbsoluteUrl('/pathname/page.html')).toBe('http://localhost/pathname/page.html');
+    expect(toAbsoluteUrl('?query=string')).toBe('http://localhost/?query=string');
+    expect(toAbsoluteUrl('#anchor')).toBe('http://localhost/#anchor');
+    expect(toAbsoluteUrl('/page.html?query=string#anchor')).toBe('http://localhost/page.html?query=string#anchor');
+    expect(toAbsoluteUrl('https://esl-ui.com/page.html')).toBe('https://esl-ui.com/page.html');
+    expect(toAbsoluteUrl('', 'https://esl-ui.com')).toBe('https://esl-ui.com/');
+    expect(toAbsoluteUrl('/page.html', 'https://esl-ui.com/home.html?query=string#anchor')).toBe('https://esl-ui.com/page.html');
+  });
+});