diff --git a/components/parts/common/post-sharing.php b/components/parts/common/post-sharing.php new file mode 100644 index 00000000..ba85b7d3 --- /dev/null +++ b/components/parts/common/post-sharing.php @@ -0,0 +1,41 @@ + + +
+
+ + +
+

+
diff --git a/languages/beapi-frontend-framework.pot b/languages/beapi-frontend-framework.pot index 6d9bc44c..7de87f97 100644 --- a/languages/beapi-frontend-framework.pot +++ b/languages/beapi-frontend-framework.pot @@ -9,7 +9,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2026-05-07T14:30:39+00:00\n" +"POT-Creation-Date: 2026-09-18T09:57:30+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.12.0\n" "X-Domain: beapi-frontend-framework\n" @@ -39,6 +39,26 @@ msgstr "" msgid "Breadcrumb" msgstr "" +#: components/parts/common/post-sharing.php:25 +msgid "Link copied!" +msgstr "" + +#: components/parts/common/post-sharing.php:26 +msgid "Unable to copy the link" +msgstr "" + +#: components/parts/common/post-sharing.php:27 +msgid "Unable to share" +msgstr "" + +#: components/parts/common/post-sharing.php:33 +msgid "Copy link" +msgstr "" + +#: components/parts/common/post-sharing.php:37 +msgid "Share" +msgstr "" + #: footer.php:2 msgid "Footer" msgstr "" @@ -92,6 +112,10 @@ msgstr "" msgid "Close menu" msgstr "" +#: inc/Helpers/Formatting/Datetime.php:53 +msgid "Published on" +msgstr "" + #: inc/Helpers/Formatting/Link.php:144 msgid "New window" msgstr "" @@ -120,46 +144,28 @@ msgstr "" msgid "Share by email" msgstr "" -#: inc/Services/Editor.php:144 +#: inc/Services/Editor.php:168 msgid "Small" msgstr "" -#: inc/Services/Editor.php:152 +#: inc/Services/Editor.php:176 msgid "Large" msgstr "" -#: inc/Services/Editor.php:160 +#: inc/Services/Editor.php:184 msgid "Huge" msgstr "" #. translators: %s: heading number -#: inc/Services/Editor.php:168 +#: inc/Services/Editor.php:192 #, php-format msgid "Style H%s" msgstr "" -#: inc/Services/Editor_Patterns.php:40 +#: inc/Services/Editor_Patterns.php:45 msgid "Common" msgstr "" -#. translators: %s: file name. -#: inc/Services/Editor_Patterns.php:125 -#, php-format -msgid "Could not register file \"%s\" as a block pattern (\"Slug\" field missing)" -msgstr "" - -#. translators: %1s: file name; %2s: slug value found. -#: inc/Services/Editor_Patterns.php:138 -#, php-format -msgid "Could not register file \"%1$s\" as a block pattern (invalid slug \"%2$s\")" -msgstr "" - -#. translators: %1s: file name; %2s: slug value found. -#: inc/Services/Editor_Patterns.php:155 -#, php-format -msgid "Could not register file \"%s\" as a block pattern (\"Title\" field missing)" -msgstr "" - #: inc/Services/Menu.php:37 msgid "Main menu" msgstr "" diff --git a/languages/fr_FR.mo b/languages/fr_FR.mo index c62c0ed6..c22b928a 100644 Binary files a/languages/fr_FR.mo and b/languages/fr_FR.mo differ diff --git a/languages/fr_FR.po b/languages/fr_FR.po index 4f5562c0..2b28850f 100644 --- a/languages/fr_FR.po +++ b/languages/fr_FR.po @@ -5,8 +5,8 @@ msgstr "" "Project-Id-Version: Be API Frontend Framework 1.0.0\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/theme/beapi-frontend-" "framework\n" -"POT-Creation-Date: 2026-05-07T14:30:39+00:00\n" -"PO-Revision-Date: 2026-05-07 16:41+0200\n" +"POT-Creation-Date: 2026-09-18T09:57:30+00:00\n" +"PO-Revision-Date: 2026-09-18 12:00+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: fr\n" @@ -42,6 +42,26 @@ msgstr "" msgid "Breadcrumb" msgstr "Fil d’ariane" +#: components/parts/common/post-sharing.php:25 +msgid "Link copied!" +msgstr "Lien copié !" + +#: components/parts/common/post-sharing.php:26 +msgid "Unable to copy the link" +msgstr "Impossible de copier le lien" + +#: components/parts/common/post-sharing.php:27 +msgid "Unable to share" +msgstr "Impossible de partager" + +#: components/parts/common/post-sharing.php:33 +msgid "Copy link" +msgstr "Copier le lien" + +#: components/parts/common/post-sharing.php:37 +msgid "Share" +msgstr "Partager" + #: footer.php:2 msgid "Footer" msgstr "Pied de Page" @@ -94,6 +114,10 @@ msgstr "Ouvrir le menu" msgid "Close menu" msgstr "Fermer le menu" +#: inc/Helpers/Formatting/Datetime.php:53 +msgid "Published on" +msgstr "Publié le" + #: inc/Helpers/Formatting/Link.php:144 msgid "New window" msgstr "Nouvelle fenêtre" @@ -122,55 +146,28 @@ msgstr "Partager sur Bluesky" msgid "Share by email" msgstr "Partager par email" -#: inc/Services/Editor.php:144 +#: inc/Services/Editor.php:168 msgid "Small" msgstr "Petit" -#: inc/Services/Editor.php:152 +#: inc/Services/Editor.php:176 msgid "Large" msgstr "Grand" -#: inc/Services/Editor.php:160 +#: inc/Services/Editor.php:184 msgid "Huge" msgstr "Très grand" #. translators: %s: heading number -#: inc/Services/Editor.php:168 +#: inc/Services/Editor.php:192 #, php-format msgid "Style H%s" msgstr "" -#: inc/Services/Editor_Patterns.php:40 +#: inc/Services/Editor_Patterns.php:45 msgid "Common" msgstr "Général" -#. translators: %s: file name. -#: inc/Services/Editor_Patterns.php:125 -#, php-format -msgid "" -"Could not register file \"%s\" as a block pattern (\"Slug\" field missing)" -msgstr "" -"Impossible de déclarer la composition de blocs à partir du fichier \"%s\" " -"(Le champ \"Slug\" est manquant)" - -#. translators: %1s: file name; %2s: slug value found. -#: inc/Services/Editor_Patterns.php:138 -#, php-format -msgid "" -"Could not register file \"%1$s\" as a block pattern (invalid slug \"%2$s\")" -msgstr "" -"Impossible de déclarer la composition de blocs à partir du fichier \"%1$s\" " -"(Le slug \"%2$s\" n'est pas valide)" - -#. translators: %1s: file name; %2s: slug value found. -#: inc/Services/Editor_Patterns.php:155 -#, php-format -msgid "" -"Could not register file \"%s\" as a block pattern (\"Title\" field missing)" -msgstr "" -"Impossible de déclarer la composition de blocs à partir du fichier \"%s\" " -"(Le champ \"Title\" est manquant)" - #: inc/Services/Menu.php:37 msgid "Main menu" msgstr "Menu principal" @@ -264,6 +261,29 @@ msgctxt "Space size name" msgid "lg (64px)" msgstr "" +#, php-format +#~ msgid "" +#~ "Could not register file \"%s\" as a block pattern (\"Slug\" field missing)" +#~ msgstr "" +#~ "Impossible de déclarer la composition de blocs à partir du fichier \"%s\" " +#~ "(Le champ \"Slug\" est manquant)" + +#, php-format +#~ msgid "" +#~ "Could not register file \"%1$s\" as a block pattern (invalid slug " +#~ "\"%2$s\")" +#~ msgstr "" +#~ "Impossible de déclarer la composition de blocs à partir du fichier " +#~ "\"%1$s\" (Le slug \"%2$s\" n'est pas valide)" + +#, php-format +#~ msgid "" +#~ "Could not register file \"%s\" as a block pattern (\"Title\" field " +#~ "missing)" +#~ msgstr "" +#~ "Impossible de déclarer la composition de blocs à partir du fichier \"%s\" " +#~ "(Le champ \"Title\" est manquant)" + #~ msgid "Dark" #~ msgstr "Sombre" diff --git a/src/img/icons/sprite/check-circle.svg b/src/img/icons/sprite/check-circle.svg new file mode 100644 index 00000000..51a0b78e --- /dev/null +++ b/src/img/icons/sprite/check-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/img/icons/sprite/link.svg b/src/img/icons/sprite/link.svg new file mode 100644 index 00000000..1ba22c65 --- /dev/null +++ b/src/img/icons/sprite/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/img/icons/sprite/logo-beapi.svg b/src/img/icons/sprite/logo-beapi.svg deleted file mode 100644 index 5cf0fca4..00000000 --- a/src/img/icons/sprite/logo-beapi.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/img/icons/sprite/share.svg b/src/img/icons/sprite/share.svg new file mode 100644 index 00000000..9adcdcc2 --- /dev/null +++ b/src/img/icons/sprite/share.svg @@ -0,0 +1 @@ + diff --git a/src/js/classes/PostSharing.js b/src/js/classes/PostSharing.js new file mode 100644 index 00000000..3f32cec9 --- /dev/null +++ b/src/js/classes/PostSharing.js @@ -0,0 +1,148 @@ +import AbstractDomElement from './AbstractDomElement' + +/** + * Post sharing — copy link (desktop) and native share (mobile). + * Button visibility is handled in CSS. + */ +class PostSharing extends AbstractDomElement { + constructor(element, options) { + const instance = super(element, options) + + // avoid double init : + if (!instance.isNewInstance()) { + return instance + } + + const el = this._element + const copyButton = el.querySelector('[data-action="copy"]') + const shareButton = el.querySelector('[data-action="share"]') + const status = el.querySelector('.post-sharing__status') + + if (!el.dataset.url || !copyButton || !status) { + return instance + } + + this._copyButton = copyButton + this._shareButton = shareButton + this._status = status + + copyButton.addEventListener('click', onClickCopy.bind(this)) + + // No Web Share API: CSS shows the copy button as mobile fallback. + if (!shareButton || typeof navigator.share !== 'function') { + el.classList.add('post-sharing--no-native-share') + return instance + } + + shareButton.addEventListener('click', onClickShare.bind(this)) + + return instance + } +} + +// ---- +// defaults +// ---- + +PostSharing.defaults = { + resetDelay: 3000, +} + +// ---- +// utils +// ---- + +/** + * Announce a message to screen readers, then clear it. + * + * @param {HTMLElement} el Status element. + * @param {string} message Message to announce. + * @param {number} delay Duration before clearing (ms). + */ +function showTemporaryMessage(el, message, delay) { + // Keep the live region in the accessibility tree (no `hidden`): + // `sr-only` already hides it visually; toggling `hidden` would block announcements. + el.textContent = message + + window.setTimeout(() => { + el.textContent = '' + }, delay) +} + +/** + * Swap the copy button label and icon, then restore the default state. + * + * @param {HTMLButtonElement} copyButton + * @param {HTMLElement} statusEl + * @param {string} copiedLabel + * @param {number} delay + */ +function showCopySuccess(copyButton, statusEl, copiedLabel, delay) { + const labelEl = copyButton.querySelector('.post-sharing__label') + + if (!labelEl || copyButton.classList.contains('is-copied')) { + return + } + + const originalLabel = labelEl.textContent + + labelEl.textContent = copiedLabel + copyButton.classList.add('is-copied') + showTemporaryMessage(statusEl, copiedLabel, delay) + + window.setTimeout(() => { + labelEl.textContent = originalLabel + copyButton.classList.remove('is-copied') + }, delay) +} + +// ---- +// events +// ---- + +async function onClickCopy() { + const { url, copiedLabel, errorCopy } = this._element.dataset + const { resetDelay } = this._settings + + try { + await navigator.clipboard.writeText(url) + showCopySuccess(this._copyButton, this._status, copiedLabel, resetDelay) + } catch { + showTemporaryMessage(this._status, errorCopy, resetDelay) + } +} + +async function onClickShare() { + const { url, title, errorShare } = this._element.dataset + const { resetDelay } = this._settings + + try { + const shareData = { url } + + if (title) { + shareData.title = title + } + + if (navigator.canShare?.(shareData) === false) { + showTemporaryMessage(this._status, errorShare, resetDelay) + return + } + + await navigator.share(shareData) + } catch (error) { + // User dismissed the native share sheet — not an error. + if (error?.name !== 'AbortError') { + showTemporaryMessage(this._status, errorShare, resetDelay) + } + } +} + +// ---- +// init +// ---- +PostSharing.init('.post-sharing') + +// ---- +// export +// ---- +export default PostSharing diff --git a/src/js/index.js b/src/js/index.js index b2cfa361..dc67e40f 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -3,3 +3,4 @@ import './classes/ScrollDirection' import './classes/ButtonSeoClick' import './classes/Header' import './classes/Animation' +import './classes/PostSharing' diff --git a/src/scss/05-components/_post-sharing.scss b/src/scss/05-components/_post-sharing.scss new file mode 100644 index 00000000..89c6020e --- /dev/null +++ b/src/scss/05-components/_post-sharing.scss @@ -0,0 +1,97 @@ +@use "../01-abstract/theme-json" as *; +@use "../02-tools/m-breakpoint" as *; +@use "../02-tools/m-hover" as *; + +/** + * Post sharing — copy link (desktop) / native share (mobile). + * Without navigator.share, copy is also shown on small screens as fallback. + */ + +.post-sharing { + $el: &; + + &__actions { + display: flex; + flex-wrap: wrap; + align-items: center; + } + + &__button { + --post-sharing-color: #{$settings-color-black}; + --post-sharing-bg: #{$settings-color-grey-100}; + --post-sharing-hover-color: #{$settings-color-black}; + --post-sharing-hover-bg: #{$settings-color-grey-200}; + --post-sharing-icon-size: 20px; + + display: inline-flex; + gap: 8px; + align-items: center; + padding: 8px 12px; + color: var(--post-sharing-color); + cursor: pointer; + background-color: var(--post-sharing-bg); + border: 0; + border-radius: 50px; + transition: background-color .3s ease, color .3s ease; + appearance: none; + + @include hover { + color: var(--post-sharing-hover-color); + background-color: var(--post-sharing-hover-bg); + } + + &--copy { + &.is-copied { + #{$el}__icon--copy { + display: none; + } + + #{$el}__icon--check { + display: block; + } + } + + @include breakpoints(sm) { + display: inline-flex; + } + + @include breakpoints(sm, max) { + display: none; + } + } + + &--share { + @include breakpoints(sm) { + display: none; + } + + @include breakpoints(sm, max) { + display: inline-flex; + } + } + } + + &__icon { + width: var(--post-sharing-icon-size); + height: var(--post-sharing-icon-size); + + &--check { + display: none; + } + } + + // Fallback when Web Share API is unavailable: show copy on small screens. + &--no-native-share { + #{$el}__button--share { + @include breakpoints(sm, max) { + display: none; + } + } + + #{$el}__button--copy { + @include breakpoints(sm, max) { + display: inline-flex; + } + } + } +} diff --git a/src/scss/style.scss b/src/scss/style.scss index 486bce0a..cd29f267 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -49,6 +49,7 @@ variables.$entry-file-name: "style"; @use "05-components/btn"; @use "05-components/searchform"; @use "05-components/skip-links"; +@use "05-components/post-sharing"; /** * Gutenberg