Revert "DEV: FloatKit (#23312)" (#23540)

This reverts commit abcdd8d367e8db430119f847f029e9cc94e62cb6.
This commit is contained in:
Joffrey JAFFEUX
2023-09-12 15:37:16 +02:00
committed by GitHub
parent 93de8c8daa
commit b8cc1072cc
166 changed files with 2490 additions and 5361 deletions

View File

@ -1,24 +1,30 @@
{{#if @buttons.length}}
<DMenu
<Chat::Composer::Button
{{on "click" this.toggleExpand}}
@icon="plus"
title={{i18n "chat.composer.toggle_toolbar"}}
disabled={{@isDisabled}}
{{did-insert this.setupTrigger}}
class={{concat-class
"chat-composer-dropdown__trigger-btn"
"btn-flat"
(if @hasActivePanel "has-active-panel")
}}
@title={{i18n "chat.composer.toggle_toolbar"}}
@icon="plus"
@disabled={{@isDisabled}}
@arrow={{true}}
@placements={{array "top" "bottom"}}
aria-expanded={{if this.isExpanded "true" "false"}}
aria-controls={{this.ariaControls}}
...attributes
as |menu|
>
<ul class="chat-composer-dropdown__list">
/>
{{#if this.isExpanded}}
<ul
id="chat-composer-dropdown__list"
class="chat-composer-dropdown__list"
{{did-insert this.setupPanel}}
{{will-destroy this.teardownPanel}}
>
{{#each @buttons as |button|}}
<li class={{concat-class "chat-composer-dropdown__item" button.id}}>
<DButton
@icon={{button.icon}}
@action={{fn this.onButtonClick button menu.close}}
@action={{fn this.onButtonClick button}}
@label={{button.label}}
class={{concat-class
"chat-composer-dropdown__action-btn"
@ -28,5 +34,5 @@
</li>
{{/each}}
</ul>
</DMenu>
{{/if}}
{{/if}}

View File

@ -1,10 +1,68 @@
import Component from "@glimmer/component";
import { iconHTML } from "discourse-common/lib/icon-library";
import tippy from "tippy.js";
import { action } from "@ember/object";
import { hideOnEscapePlugin } from "discourse/lib/d-popover";
import { tracked } from "@glimmer/tracking";
export default class ChatComposerDropdown extends Component {
@tracked isExpanded = false;
@tracked tippyInstance = null;
trigger = null;
@action
onButtonClick(button, closeFn) {
closeFn();
setupTrigger(element) {
this.trigger = element;
}
get ariaControls() {
return this.tippyInstance?.popper?.id;
}
@action
toggleExpand() {
if (this.args.hasActivePanel) {
this.args.onCloseActivePanel?.();
} else {
this.isExpanded = !this.isExpanded;
}
}
@action
onButtonClick(button) {
this.tippyInstance.hide();
button.action();
}
@action
setupPanel(element) {
this.tippyInstance = tippy(this.trigger, {
theme: "chat-composer-dropdown",
trigger: "click",
zIndex: 1400,
arrow: iconHTML("tippy-rounded-arrow"),
interactive: true,
allowHTML: false,
appendTo: "parent",
hideOnClick: true,
plugins: [hideOnEscapePlugin],
content: element,
onShow: () => {
this.isExpanded = true;
return true;
},
onHide: () => {
this.isExpanded = false;
return true;
},
});
this.tippyInstance.show();
}
@action
teardownPanel() {
this.tippyInstance?.destroy();
}
}

View File

@ -35,6 +35,13 @@
<ChatComposerDropdown
@buttons={{this.dropdownButtons}}
@isDisabled={{this.disabled}}
@hasActivePanel={{eq
this.chatEmojiPickerManager.picker.context
this.context
}}
@onCloseActivePanel={{this.chatEmojiPickerManager.close}}
{{on "focus" (fn this.computeIsFocused true)}}
{{on "blur" (fn this.computeIsFocused false)}}
/>
<div

View File

@ -425,7 +425,7 @@ export default class ChatComposer extends Component {
user.cssClasses = "is-online";
}
});
initUserStatusHtml(getOwner(this), result.users);
initUserStatusHtml(result.users);
}
return result;
});

View File

@ -1,104 +0,0 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { emojiUnescape, emojiUrlFor } from "discourse/lib/text";
import { inject as service } from "@ember/service";
import { cached, tracked } from "@glimmer/tracking";
import { getReactionText } from "discourse/plugins/chat/discourse/lib/get-reaction-text";
import { htmlSafe } from "@ember/template";
import { modifier } from "ember-modifier";
import { on } from "@ember/modifier";
import and from "truth-helpers/helpers/and";
import concatClass from "discourse/helpers/concat-class";
export default class ChatMessageReaction extends Component {
<template>
{{! template-lint-disable modifier-name-case }}
{{#if (and @reaction this.emojiUrl)}}
<button
type="button"
tabindex="0"
class={{concatClass
"chat-message-reaction"
(if @reaction.reacted "reacted")
(if this.isActive "-active")
}}
data-emoji-name={{@reaction.emoji}}
title={{this.emojiString}}
{{on "click" this.handleClick passive=true}}
{{this.registerTooltip}}
>
<img
loading="lazy"
class="emoji"
width="20"
height="20"
alt={{this.emojiString}}
src={{this.emojiUrl}}
/>
{{#if (and this.showCount @reaction.count)}}
<span class="count">{{@reaction.count}}</span>
{{/if}}
</button>
{{/if}}
</template>
@service capabilities;
@service currentUser;
@service tooltip;
@service site;
@tracked isActive = false;
registerTooltip = modifier((element) => {
if (!this.popoverContent?.length) {
return;
}
const instance = this.tooltip.register(element, {
content: htmlSafe(this.popoverContent),
identifier: "chat-message-reaction-tooltip",
animated: false,
placement: "top",
fallbackPlacements: ["bottom"],
triggers: this.site.mobileView ? ["hold"] : ["hover"],
});
return () => {
instance?.destroy();
};
});
get showCount() {
return this.args.showCount ?? true;
}
get emojiString() {
return `:${this.args.reaction.emoji}:`;
}
get emojiUrl() {
return emojiUrlFor(this.args.reaction.emoji);
}
@action
handleClick(event) {
event.stopPropagation();
this.args.onReaction?.(
this.args.reaction.emoji,
this.args.reaction.reacted ? "remove" : "add"
);
this.tooltip.close();
}
@cached
get popoverContent() {
if (!this.args.reaction.count || !this.args.reaction.users?.length) {
return;
}
return emojiUnescape(getReactionText(this.args.reaction, this.currentUser));
}
}

View File

@ -0,0 +1,30 @@
{{#if (and @reaction this.emojiUrl)}}
<button
type="button"
tabindex="0"
class={{concat-class
"chat-message-reaction"
(if @reaction.reacted "reacted")
(if this.isActive "-active")
}}
data-emoji-name={{@reaction.emoji}}
data-tippy-content={{this.popoverContent}}
title={{this.emojiString}}
{{did-insert this.setup}}
{{will-destroy this.teardown}}
{{did-update this.refreshTooltip this.popoverContent}}
>
<img
loading="lazy"
class="emoji"
width="20"
height="20"
alt={{this.emojiString}}
src={{this.emojiUrl}}
/>
{{#if (and this.showCount @reaction.count)}}
<span class="count">{{@reaction.count}}</span>
{{/if}}
</button>
{{/if}}

View File

@ -0,0 +1,156 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { emojiUnescape, emojiUrlFor } from "discourse/lib/text";
import { cancel } from "@ember/runloop";
import { inject as service } from "@ember/service";
import setupPopover from "discourse/lib/d-popover";
import discourseLater from "discourse-common/lib/later";
import { tracked } from "@glimmer/tracking";
import { getReactionText } from "discourse/plugins/chat/discourse/lib/get-reaction-text";
export default class ChatMessageReaction extends Component {
@service capabilities;
@service currentUser;
@tracked isActive = false;
get showCount() {
return this.args.showCount ?? true;
}
@action
setup(element) {
this.setupListeners(element);
this.setupTooltip(element);
}
@action
teardown() {
cancel(this.longPressHandler);
this.teardownTooltip();
}
@action
setupListeners(element) {
this.element = element;
if (this.capabilities.touch) {
this.element.addEventListener("touchstart", this.onTouchStart, {
passive: true,
});
this.element.addEventListener("touchmove", this.cancelTouch, {
passive: true,
});
this.element.addEventListener("touchend", this.onTouchEnd);
this.element.addEventListener("touchCancel", this.cancelTouch);
}
this.element.addEventListener("click", this.handleClick, {
passive: true,
});
}
@action
teardownListeners() {
if (this.capabilities.touch) {
this.element.removeEventListener("touchstart", this.onTouchStart, {
passive: true,
});
this.element.removeEventListener("touchmove", this.cancelTouch, {
passive: true,
});
this.element.removeEventListener("touchend", this.onTouchEnd);
this.element.removeEventListener("touchCancel", this.cancelTouch);
}
this.element.removeEventListener("click", this.handleClick, {
passive: true,
});
}
@action
onTouchStart(event) {
event.stopPropagation();
this.isActive = true;
this.longPressHandler = discourseLater(() => {
this.touching = false;
}, 400);
this.touching = true;
}
@action
cancelTouch() {
cancel(this.longPressHandler);
this._tippyInstance?.hide();
this.touching = false;
this.isActive = false;
}
@action
onTouchEnd(event) {
event.preventDefault();
if (this.touching) {
this.handleClick(event);
}
cancel(this.longPressHandler);
this._tippyInstance?.hide();
this.isActive = false;
}
@action
setupTooltip(element) {
this._tippyInstance = setupPopover(element, {
trigger: "mouseenter",
interactive: false,
allowHTML: true,
offset: [0, 10],
onShow(instance) {
if (instance.props.content === "") {
return false;
}
},
});
}
@action
teardownTooltip() {
this._tippyInstance?.destroy();
}
@action
refreshTooltip() {
this._tippyInstance?.setContent(this.popoverContent || "");
}
get emojiString() {
return `:${this.args.reaction.emoji}:`;
}
get emojiUrl() {
return emojiUrlFor(this.args.reaction.emoji);
}
@action
handleClick(event) {
event.stopPropagation();
this.args.onReaction?.(
this.args.reaction.emoji,
this.args.reaction.reacted ? "remove" : "add"
);
this._tippyInstance?.clearDelayTimeouts();
}
get popoverContent() {
if (!this.args.reaction.count || !this.args.reaction.users?.length) {
return;
}
return emojiUnescape(getReactionText(this.args.reaction, this.currentUser));
}
}

View File

@ -34,6 +34,7 @@ import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
import ChatOnLongPress from "discourse/plugins/chat/discourse/modifiers/chat/on-long-press";
let _chatMessageDecorators = [];
let _tippyInstances = [];
export function addChatMessageDecorator(decorator) {
_chatMessageDecorators.push(decorator);
@ -296,6 +297,13 @@ export default class ChatMessage extends Component {
this.#teardownMentionedUsers();
}
#destroyTippyInstances() {
_tippyInstances.forEach((instance) => {
instance.destroy();
});
_tippyInstances = [];
}
@action
refreshStatusOnMentions() {
schedule("afterRender", () => {
@ -306,7 +314,7 @@ export default class ChatMessage extends Component {
);
mentions.forEach((mention) => {
updateUserStatusOnMention(getOwner(this), mention, user.status);
updateUserStatusOnMention(mention, user.status, _tippyInstances);
});
});
});
@ -588,5 +596,6 @@ export default class ChatMessage extends Component {
user.stopTrackingStatus();
user.off("status-changed", this, "refreshStatusOnMentions");
});
this.#destroyTippyInstances();
}
}

View File

@ -0,0 +1,5 @@
import { helper } from "@ember/component/helper";
export default helper(function noop() {
return () => {};
});

View File

@ -1,3 +1,11 @@
[data-theme="chat-composer-dropdown"] {
margin-left: 0.2rem;
.tippy-content {
padding: 0;
}
}
.chat-composer.is-disabled {
.no-touch & {
.chat-composer-dropdown__trigger-btn:hover {
@ -10,9 +18,7 @@
}
.chat-composer-dropdown__trigger-btn {
margin-left: 0.2rem;
transition: transform 0.25s ease-in-out;
.d-icon {
padding: 5px;
transition: transform 0.1s ease-in-out;
@ -20,25 +26,14 @@
border-radius: 100%;
}
&:focus,
&:hover,
&:active {
.d-icon {
color: var(--primary) !important;
}
background: none !important;
background-image: none !important;
}
&:hover {
transform: scale(1.1);
}
&.-expanded {
&[aria-expanded="true"] {
.d-icon {
transform: rotate(135deg);
transform-origin: center center;
transform-origin: center;
}
}
}
@ -50,9 +45,9 @@
}
.chat-composer-dropdown__action-btn {
background: none;
width: 100%;
justify-content: flex-start;
background: none;
.d-icon {
color: var(--primary);

View File

@ -24,14 +24,6 @@
}
}
[data-content][data-identifier="chat-message-reaction-tooltip"] {
font-size: var(--font-down-1);
.emoji {
padding-left: 0.5rem;
}
}
.chat-message {
align-items: flex-start;
padding: 0.25em 0.5em 0.25em 0.75em;
@ -234,6 +226,10 @@
}
}
&:has(.tippy-box) {
position: relative;
z-index: 1;
}
.chat-message-reaction-list .chat-message-react-btn {
display: none;
}

View File

@ -166,6 +166,9 @@ RSpec.describe "React to message", type: :system do
channel.click_reaction(message_1, "female_detective")
expect(channel).to have_reaction(message_1, "female_detective", "1")
expect(
channel.find_reaction(message_1, "female_detective")["data-tippy-content"],
).to include(other_user.username)
end
end
end

View File

@ -61,9 +61,7 @@ module(
});
test("it shows status on mentions", async function (assert) {
await render(
hbs`<ChatChannel @channel={{this.channel}} /><DInlineTooltip />`
);
await render(hbs`<ChatChannel @channel={{this.channel}} />`);
assertStatusIsRendered(
assert,
@ -78,9 +76,7 @@ module(
});
test("it updates status on mentions", async function (assert) {
await render(
hbs`<ChatChannel @channel={{this.channel}} /><DInlineTooltip />`
);
await render(hbs`<ChatChannel @channel={{this.channel}} />`);
const newStatus = {
description: "off to dentist",
@ -93,13 +89,11 @@ module(
const selector = statusSelector(mentionedUser.username);
await waitFor(selector);
assertStatusIsRendered(
assert,
statusSelector(mentionedUser.username),
newStatus
);
await assertStatusTooltipIsRendered(
assert,
statusSelector(mentionedUser.username),
@ -108,9 +102,7 @@ module(
});
test("it deletes status on mentions", async function (assert) {
await render(
hbs`<ChatChannel @channel={{this.channel}} /><DInlineTooltip />`
);
await render(hbs`<ChatChannel @channel={{this.channel}} />`);
this.appEvents.trigger("user-status:changed", {
[mentionedUser.id]: null,
@ -122,9 +114,7 @@ module(
});
test("it shows status on mentions on messages that came from Message Bus", async function (assert) {
await render(
hbs`<ChatChannel @channel={{this.channel}} /><DInlineTooltip />`
);
await render(hbs`<ChatChannel @channel={{this.channel}} />`);
await receiveChatMessageViaMessageBus();
@ -141,9 +131,7 @@ module(
});
test("it updates status on mentions on messages that came from Message Bus", async function (assert) {
await render(
hbs`<ChatChannel @channel={{this.channel}} /><DInlineTooltip />`
);
await render(hbs`<ChatChannel @channel={{this.channel}} />`);
await receiveChatMessageViaMessageBus();
const newStatus = {
@ -169,9 +157,7 @@ module(
});
test("it deletes status on mentions on messages that came from Message Bus", async function (assert) {
await render(
hbs`<ChatChannel @channel={{this.channel}} /><DInlineTooltip />`
);
await render(hbs`<ChatChannel @channel={{this.channel}} />`);
await receiveChatMessageViaMessageBus();
this.appEvents.trigger("user-status:changed", {
@ -195,7 +181,7 @@ module(
}
async function assertStatusTooltipIsRendered(assert, selector, status) {
await triggerEvent(selector, "mousemove");
await triggerEvent(selector, "mouseenter");
assert.equal(
document

View File

@ -1,16 +1,17 @@
import { bind } from "discourse-common/utils/decorators";
import deprecated from "discourse-common/lib/deprecated";
import { getOwner } from "discourse-common/lib/get-owner";
import LocalDateBuilder from "../lib/local-date-builder";
import { withPluginApi } from "discourse/lib/plugin-api";
import showModal from "discourse/lib/show-modal";
import { downloadCalendar } from "discourse/lib/download-calendar";
import { renderIcon } from "discourse-common/lib/icon-library";
import I18n from "I18n";
import { hidePopover, showPopover } from "discourse/lib/d-popover";
import {
addTagDecorateCallback,
addTextDecorateCallback,
} from "discourse/lib/to-markdown";
import generateDateMarkup from "discourse/plugins/discourse-local-dates/lib/local-date-markup-generator";
import { htmlSafe } from "@ember/template";
// Import applyLocalDates from discourse/lib/local-dates instead
export function applyLocalDates(dates, siteSettings) {
@ -347,9 +348,11 @@ function _calculateDuration(element) {
export default {
name: "discourse-local-dates",
@bind
showDatePopover(event) {
const tooltip = this.container.lookup("service:tooltip");
const owner = getOwner(this);
if (owner.isDestroyed || owner.isDestroying) {
return;
}
if (event?.target?.classList?.contains("download-calendar")) {
const dataset = event.target.dataset;
@ -360,25 +363,50 @@ export default {
},
]);
return tooltip.close();
// TODO: remove this when rewriting preview as a component
const parentPopover = event.target.closest("[data-tippy-root]");
if (parentPopover?._tippy) {
parentPopover._tippy.hide();
}
return;
}
if (!event?.target?.classList?.contains("discourse-local-date")) {
return;
}
const siteSettings = this.container.lookup("service:site-settings");
return tooltip.show(event.target, {
content: htmlSafe(buildHtmlPreview(event.target, siteSettings)),
const siteSettings = owner.lookup("service:site-settings");
showPopover(event, {
trigger: "click",
content: buildHtmlPreview(event.target, siteSettings),
allowHTML: true,
interactive: true,
appendTo: "parent",
onHidden: (instance) => {
instance.destroy();
},
});
},
hideDatePopover(event) {
hidePopover(event);
},
initialize(container) {
this.container = container;
window.addEventListener("click", this.showDatePopover, { passive: true });
window.addEventListener("click", this.showDatePopover);
const siteSettings = container.lookup("service:site-settings");
if (siteSettings.discourse_local_dates_enabled) {
$.fn.applyLocalDates = function () {
deprecated(
"`$.applyLocalDates()` is deprecated, import and use `applyLocalDates()` instead."
);
return applyLocalDates(this.toArray(), siteSettings);
};
withPluginApi("0.8.8", initializeDiscourseLocalDates);
}
},

View File

@ -23,29 +23,29 @@
}
}
.locale-dates-previews {
max-width: 250px;
div[data-tippy-root] {
.locale-dates-previews {
max-width: 360px;
.preview {
display: flex;
flex-direction: column;
padding: 5px;
.preview {
display: flex;
flex-direction: column;
padding: 5px;
margin: 0;
.timezone {
font-weight: 700;
}
.timezone {
font-weight: 700;
}
&.current {
background: var(--tertiary-low);
&.current {
background: var(--tertiary-low);
}
}
}
}
.download-calendar {
text-align: right;
cursor: pointer;
margin-top: 0.5em;
.download-calendar {
text-align: right;
cursor: pointer;
margin-top: 0.5em;
}
}
.discourse-local-dates-create-modal-footer {

View File

@ -31,46 +31,44 @@ describe "Local dates", type: :system do
expect(topic_page).to have_content(topic.title)
post_dates = topic_page.find_all("span[data-date]")
# Single date in a paragraph.
#
find("span[data-date]:nth-of-type(1)").click
expect(page.find("[data-content] .current .date-time")).to have_text(
"#{formatted_date_for_year(12, 15)}\n2:19 PM",
exact: true,
)
page.send_keys(:escape)
post_dates[0].click
tippy_date = topic_page.find(".tippy-content .current .date-time")
expect(tippy_date).to have_text("#{formatted_date_for_year(12, 15)}\n2:19 PM", exact: true)
# Two single dates in the same paragraph.
#
find("span[data-date]:nth-of-type(2)").click
expect(page.find("[data-content] .current .date-time")).to have_text(
"#{formatted_date_for_year(12, 15)}\n1:20 AM",
exact: true,
)
page.send_keys(:escape)
post_dates[1].click
tippy_date = topic_page.find(".tippy-content .current .date-time")
find("span[data-date]:nth-of-type(3)").click
expect(page.find("[data-content] .current .date-time")).to have_text(
"#{formatted_date_for_year(12, 15)}\n2:40 AM",
exact: true,
)
page.send_keys(:escape)
expect(tippy_date).to have_text("#{formatted_date_for_year(12, 15)}\n1:20 AM", exact: true)
post_dates[2].click
tippy_date = topic_page.find(".tippy-content .current .date-time")
expect(tippy_date).to have_text("#{formatted_date_for_year(12, 15)}\n2:40 AM", exact: true)
# Two date ranges in the same paragraph.
#
find("span[data-date]:nth-of-type(4)").click
expect(page.find("[data-content] .current .date-time")).to have_text(
post_dates[3].click
tippy_date = topic_page.find(".tippy-content .current .date-time")
expect(tippy_date).to have_text(
"#{formatted_date_for_year(12, 15)}\n11:25 AM → 12:26 AM",
exact: true,
)
page.send_keys(:escape)
find("span[data-date]:nth-of-type(6)").click
expect(page.find("[data-content] .current .date-time")).to have_text(
post_dates[5].click
tippy_date = topic_page.find(".tippy-content .current .date-time")
expect(tippy_date).to have_text(
"#{formatted_date_for_year(12, 22)} 11:57 AM → #{formatted_date_for_year(12, 23)} 11:58 AM",
exact: true,
)
page.send_keys(:escape)
end
end

View File

@ -1,7 +0,0 @@
import Component from "@glimmer/component";
export default class DummyComponent extends Component {
<template>
My custom component with foo: {{@model.foo}}
</template>
}

View File

@ -1,103 +0,0 @@
<StyleguideExample @title="<Dmenu />">
<Styleguide::Component @tag="dmenu component">
<:sample>
<DMenu
@label={{this.label}}
@offset={{this.offset}}
@arrow={{this.arrow}}
@maxWidth={{this.maxWidth}}
@identifier={{this.identifier}}
@interactive={{this.interactive}}
@triggers={{this.triggers}}
@untriggers={{this.untriggers}}
@content={{this.content}}
>
{{this.content}}
</DMenu>
</:sample>
</Styleguide::Component>
<Styleguide::Component @tag="dmenu component">
<:sample>
<DMenu
@offset={{this.offset}}
@arrow={{this.arrow}}
@maxWidth={{this.maxWidth}}
@identifier={{this.identifier}}
@interactive={{this.interactive}}
@triggers={{this.triggers}}
@untriggers={{this.untriggers}}
@content={{this.content}}
>
<:trigger>
{{this.label}}
</:trigger>
<:content>
{{this.content}}
</:content>
</DMenu>
</:sample>
</Styleguide::Component>
<Styleguide::Component @tag="menu service">
<:sample>
<button
type="button"
class="btn btn-default"
id="menu-instance"
>{{this.label}}</button>
</:sample>
<:actions>
<DButton @action={{this.registerMenu}}>Register</DButton>
</:actions>
</Styleguide::Component>
<Styleguide::Component @tag="menu service">
<:sample>
<button
type="button"
class="btn btn-default"
id="menu-instance-with-component"
>{{this.label}}</button>
</:sample>
<:actions>
<DButton @action={{this.registerMenuWithComponent}}>Register</DButton>
</:actions>
</Styleguide::Component>
<Styleguide::Controls>
<Styleguide::Controls::Row @name="Example label">
<Input @value={{this.label}} />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@content]">
<Input @value={{this.content}} />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@identifier]">
<Input @value={{this.identifier}} />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@offset]">
<Input @value={{this.offset}} @type="number" />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@triggers]">
<Input @value={{this.triggers}} />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@untriggers]">
<Input @value={{this.untriggers}} />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@maxWidth]">
<Input @value={{this.maxWidth}} @type="number" />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@interactive]">
<DToggleSwitch
@state={{this.interactive}}
{{on "click" this.toggleInteractive}}
/>
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@arrow]">
<DToggleSwitch @state={{this.arrow}} {{on "click" this.toggleArrow}} />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@inline]">
<DToggleSwitch @state={{this.inline}} {{on "click" this.toggleInline}} />
</Styleguide::Controls::Row>
</Styleguide::Controls>
</StyleguideExample>

View File

@ -1,112 +0,0 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { tracked } from "@glimmer/tracking";
import DummyComponent from "discourse/plugins/styleguide/discourse/components/dummy-component";
import { htmlSafe } from "@ember/template";
import { MENU } from "float-kit/lib/constants";
export default class Menus extends Component {
@service menu;
@tracked label = "What is this?";
@tracked triggers = MENU.options.triggers;
@tracked untriggers = MENU.options.untriggers;
@tracked arrow = MENU.options.arrow;
@tracked inline = MENU.options.inline;
@tracked interactive = MENU.options.interactive;
@tracked maxWidth = MENU.options.maxWidth;
@tracked identifier;
@tracked offset = MENU.options.offset;
@tracked _content = htmlSafe("<ul><li>Hello</li><li>World!</li></ul>");
get content() {
return this._content;
}
set content(value) {
this._content = htmlSafe(value);
}
get templateCode() {
return `<DMenu
@label={{html-safe "${this.label}"}}
@content={{html-safe "${this.content}"}}
/>`;
}
get templateCodeContent() {
return `<DMenu @maxWidth={{100}}>
<:trigger>
${this.label}
</:trigger>
<:content>
${this.content}
</:content>
</DMenu>`;
}
get serviceCode() {
return `this.menu.register(
document.queryselector(".my-element"),
{ content: htmlSafe(${this.content}) }
);`;
}
get serviceCodeComponent() {
return `this.menu.register(
document.queryselector(".my-element"),
{ component: MyComponent, data: { foo: 1 } }
);`;
}
@action
toggleArrow() {
this.arrow = !this.arrow;
}
@action
toggleInteractive() {
this.interactive = !this.interactive;
}
@action
toggleInline() {
this.inline = !this.inline;
}
@action
registerMenu() {
this.menuInstance?.destroy();
this.menuInstance = this.menu.register(
document.querySelector("#menu-instance"),
this.options
);
}
@action
registerMenuWithComponent() {
this.menuInstanceWithComponent?.destroy();
this.menuInstanceWithComponent = this.menu.register(
document.querySelector("#menu-instance-with-component"),
{
...this.options,
component: DummyComponent,
data: { foo: 1 },
}
);
}
get options() {
return {
offset: this.offset,
arrow: this.arrow,
maxWidth: this.maxWidth,
identifier: this.identifier,
interactive: this.interactive,
triggers: this.triggers ?? ["click"],
untriggers: this.untriggers ?? ["click"],
content: this.content,
};
}
}

View File

@ -0,0 +1,10 @@
<StyleguideExample @title="<DTooltip>">
<DButton>
{{i18n "styleguide.sections.rich_tooltip.hover_to_see"}}
<DTooltip>
<h3>{{i18n "styleguide.sections.rich_tooltip.header"}}</h3>
{{i18n "styleguide.sections.rich_tooltip.description"}}
</DTooltip>
</DButton>
</StyleguideExample>

View File

@ -1,93 +0,0 @@
{{! template-lint-disable no-potential-path-strings }}
<StyleguideExample @title="Toasts service">
<Styleguide::Component @tag="default">
<:actions>
<DButton
@translatedLabel="Show default toast"
@action={{fn this.showToast "default"}}
/>
</:actions>
</Styleguide::Component>
<Styleguide::Component @tag="success">
<:actions>
<DButton
@translatedLabel="Show success toast"
@action={{fn this.showToast "success"}}
/>
</:actions>
</Styleguide::Component>
<Styleguide::Component @tag="warning">
<:actions>
<DButton
@translatedLabel="Show warning toast"
@action={{fn this.showToast "warning"}}
/>
</:actions>
</Styleguide::Component>
<Styleguide::Component @tag="info">
<:actions>
<DButton
@translatedLabel="Show info toast"
@action={{fn this.showToast "info"}}
/>
</:actions>
</Styleguide::Component>
<Styleguide::Component @tag="error">
<:actions>
<DButton
@translatedLabel="Show error toast"
@action={{fn this.showToast "error"}}
/>
</:actions>
</Styleguide::Component>
<Styleguide::Component @tag="custom component">
<:actions>
<DButton
@translatedLabel="Show toast"
@action={{this.showCustomComponentToast}}
/>
</:actions>
</Styleguide::Component>
<Styleguide::Controls>
<Styleguide::Controls::Row @name="[@options.autoClose]">
<DToggleSwitch
@state={{this.autoClose}}
{{on "click" this.toggleAutoClose}}
/>
</Styleguide::Controls::Row>
{{#if this.autoClose}}
<Styleguide::Controls::Row @name="[@options.duration] ms">
<Input @value={{this.duration}} @type="number" />
</Styleguide::Controls::Row>
{{/if}}
<Styleguide::Controls::Row @name="[@options.class]">
<Input @value={{this.class}} />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row>
<b>Model props for default:</b>
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@options.data.title]">
<Input @value={{this.title}} />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@options.data.message]">
<Input @value={{this.message}} />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@options.data.icon]">
<IconPicker
@name="icon"
@value={{this.icon}}
@options={{hash maximum=1}}
@onChange={{action (mut this.icon)}}
/>
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="With an action">
<DToggleSwitch @state={{this.action}} {{on "click" this.toggleAction}} />
</Styleguide::Controls::Row>
</Styleguide::Controls>
</StyleguideExample>

View File

@ -1,70 +0,0 @@
import { action } from "@ember/object";
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { inject as service } from "@ember/service";
import { TOAST } from "float-kit/lib/constants";
import DummyComponent from "discourse/plugins/styleguide/discourse/components/dummy-component";
export default class Toasts extends Component {
@service toasts;
@tracked title = "Title";
@tracked message = "Message";
@tracked duration = TOAST.options.duration;
@tracked autoClose = TOAST.options.autoClose;
@tracked class;
@tracked action = true;
@tracked icon;
@action
showCustomComponentToast() {
this.toasts.show({
duration: this.duration,
autoClose: this.autoClose,
class: this.class,
component: DummyComponent,
data: {
foo: 1,
},
});
}
@action
showToast(theme) {
const actions = [];
if (this.action) {
actions.push({
label: "Ok",
class: "btn-primary",
action: (args) => {
// eslint-disable-next-line no-alert
alert("Closing toast:" + args.data.title);
args.close();
},
});
}
this.toasts[theme]({
duration: this.duration,
autoClose: this.autoClose,
class: this.class,
data: {
title: this.title,
message: this.message,
icon: this.icon,
actions,
},
});
}
@action
toggleAction() {
this.action = !this.action;
}
@action
toggleAutoClose() {
this.autoClose = !this.autoClose;
}
}

View File

@ -1,95 +0,0 @@
<StyleguideExample @title="<DTooltip />">
<Styleguide::Component @tag="tooltip component">
<:sample>
<DTooltip
@label={{this.label}}
@offset={{this.offset}}
@arrow={{this.arrow}}
@maxWidth={{this.maxWidth}}
@identifier={{this.identifier}}
@interactive={{this.interactive}}
@triggers={{this.triggers}}
@untriggers={{this.untriggers}}
@content={{this.content}}
@inline={{this.inline}}
/>
</:sample>
</Styleguide::Component>
<Styleguide::Component @tag="tooltip component">
<:sample>
<DTooltip
@offset={{this.offset}}
@arrow={{this.arrow}}
@maxWidth={{this.maxWidth}}
@identifier={{this.identifier}}
@interactive={{this.interactive}}
@triggers={{this.triggers}}
@untriggers={{this.untriggers}}
@content={{this.content}}
@inline={{this.inline}}
>
<:trigger>
{{this.label}}
</:trigger>
<:content>
{{this.content}}
</:content>
</DTooltip>
</:sample>
</Styleguide::Component>
<Styleguide::Component @tag="tooltip service">
<:sample>
<span id="tooltip-instance">{{this.label}}</span>
</:sample>
<:actions>
<DButton @action={{this.registerTooltip}}>Register</DButton>
</:actions>
</Styleguide::Component>
<Styleguide::Component @tag="tooltip service">
<:sample>
<span id="tooltip-instance-with-component">{{this.label}}</span>
</:sample>
<:actions>
<DButton @action={{this.registerTooltipWithComponent}}>Register</DButton>
</:actions>
</Styleguide::Component>
<Styleguide::Controls>
<Styleguide::Controls::Row @name="Example label">
<Input @value={{this.label}} />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@content]">
<Input @value={{this.content}} />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@identifier]">
<Input @value={{this.identifier}} />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@offset]">
<Input @value={{this.offset}} @type="number" />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@triggers]">
<Input @value={{this.triggers}} />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@untriggers]">
<Input @value={{this.untriggers}} />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@maxWidth]">
<Input @value={{this.maxWidth}} @type="number" />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@interactive]">
<DToggleSwitch
@state={{this.interactive}}
{{on "click" this.toggleInteractive}}
/>
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@arrow]">
<DToggleSwitch @state={{this.arrow}} {{on "click" this.toggleArrow}} />
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="[@inline]">
<DToggleSwitch @state={{this.inline}} {{on "click" this.toggleInline}} />
</Styleguide::Controls::Row>
</Styleguide::Controls>
</StyleguideExample>

View File

@ -1,112 +0,0 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { tracked } from "@glimmer/tracking";
import DummyComponent from "discourse/plugins/styleguide/discourse/components/dummy-component";
import { TOOLTIP } from "float-kit/lib/constants";
import { htmlSafe } from "@ember/template";
export default class Tooltips extends Component {
@service tooltip;
@tracked label = "What is this?";
@tracked triggers = TOOLTIP.options.triggers;
@tracked untriggers = TOOLTIP.options.untriggers;
@tracked arrow = TOOLTIP.options.arrow;
@tracked inline = TOOLTIP.options.inline;
@tracked interactive = TOOLTIP.options.interactive;
@tracked maxWidth = TOOLTIP.options.maxWidth;
@tracked identifier;
@tracked offset = TOOLTIP.options.offset;
@tracked _content = "Hello World!";
get content() {
return this._content;
}
set content(value) {
this._content = htmlSafe(value);
}
get templateCode() {
return `<DTooltip
@label="${this.label}"
@content="${this.content}"
/>`;
}
get templateCodeContent() {
return `<DTooltip @maxWidth={{100}}>
<:trigger>
${this.label}
</:trigger>
<:content>
${this.content}
</:content>
</DTooltip>`;
}
get serviceCode() {
return `this.tooltip.register(
document.queryselector(".my-element"),
{ content: "${this.content}" }
);`;
}
get serviceCodeComponent() {
return `this.tooltip.register(
document.queryselector(".my-element"),
{ component: MyComponent, data: { foo: 1 } }
);`;
}
@action
toggleArrow() {
this.arrow = !this.arrow;
}
@action
toggleInteractive() {
this.interactive = !this.interactive;
}
@action
toggleInline() {
this.inline = !this.inline;
}
@action
registerTooltip() {
this.tooltipInstance?.destroy();
this.tooltipInstance = this.tooltip.register(
document.querySelector("#tooltip-instance"),
this.options
);
}
@action
registerTooltipWithComponent() {
this.tooltipInstanceWithComponent?.destroy();
this.tooltipInstanceWithComponent = this.tooltip.register(
document.querySelector("#tooltip-instance-with-component"),
{
...this.options,
component: DummyComponent,
data: { foo: 1 },
}
);
}
get options() {
return {
offset: this.offset,
arrow: this.arrow,
maxWidth: this.maxWidth,
identifier: this.identifier,
interactive: this.interactive,
triggers: this.triggers,
untriggers: this.untriggers,
content: this.content,
};
}
}

View File

@ -1,35 +1,3 @@
<div class="styleguide__component">
{{#if @tag}}
<span class="styleguide__component-tag">{{@tag}}</span>
{{/if}}
{{#if (has-block "title")}}
<div class="styleguide__component-title">
{{yield to="title"}}
</div>
{{/if}}
{{#if (or (has-block) (has-block "sample"))}}
<div class="styleguide__component-sample">
{{#if (has-block)}}
{{yield}}
{{/if}}
{{#if (has-block "sample")}}
{{yield to="sample"}}
{{/if}}
</div>
{{/if}}
{{#if (has-block "actions")}}
<div class="styleguide__component-actions">
{{yield to="actions"}}
</div>
{{/if}}
{{#if (has-block "code")}}
<div class="styleguide__component-code">
{{yield to="code"}}
</div>
{{/if}}
<div class="component">
{{yield}}
</div>

View File

@ -18,9 +18,7 @@ import headerIcons from "../components/sections/molecules/header-icons";
import navigationBar from "../components/sections/molecules/navigation-bar";
import navigationStacked from "../components/sections/molecules/navigation-stacked";
import postMenu from "../components/sections/molecules/post-menu";
import tooltips from "../components/sections/molecules/tooltips";
import menus from "../components/sections/molecules/menus";
import toasts from "../components/sections/molecules/toasts";
import richTooltip from "../components/sections/molecules/rich-tooltip";
import signupCta from "../components/sections/molecules/signup-cta";
import topicListItem from "../components/sections/molecules/topic-list-item";
import topicNotifications from "../components/sections/molecules/topic-notifications";
@ -72,9 +70,7 @@ const SECTIONS = [
id: "navigation-stacked",
},
{ component: postMenu, category: "molecules", id: "post-menu" },
{ component: tooltips, category: "molecules", id: "tooltips" },
{ component: menus, category: "molecules", id: "menus" },
{ component: toasts, category: "molecules", id: "toasts" },
{ component: richTooltip, category: "molecules", id: "rich-tooltip" },
{ component: signupCta, category: "molecules", id: "signup-cta" },
{ component: topicListItem, category: "molecules", id: "topic-list-item" },
{

View File

@ -75,6 +75,12 @@
width: 100%;
position: relative;
.component {
padding: 2rem;
border: 2px dotted var(--primary-low);
margin-bottom: 2rem;
}
.component-properties {
width: 100%;
@ -229,42 +235,3 @@
}
}
}
.styleguide__component {
border: 2px dotted var(--primary-low);
margin-bottom: 2rem;
position: relative;
&-tag {
position: absolute;
top: 0;
right: 0;
padding: 3px 6px;
background: var(--primary-low);
max-width: 25%;
@include ellipsis;
}
&-sample {
display: flex;
padding: 2rem;
}
&-actions {
display: flex;
align-items: center;
padding: 1rem 2rem;
}
&-code {
display: flex;
.ember-view {
width: 100%;
}
pre {
margin: 0;
}
}
}

View File

@ -16,10 +16,6 @@ en:
paragraph: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
date_time_inputs:
title: "Date/Time inputs"
menus:
title: "Menus"
toasts:
title: "Toasts"
font_scale:
title: "Font System"
colors:
@ -87,12 +83,12 @@ en:
title: "Spinners"
empty_state:
title: "Empty State"
tooltips:
title: "Tooltips"
rich_tooltip:
title: "Rich Tooltip"
description: "Description"
header: "Header"
hover_to_see: "Hover to see a tooltip"
char_counter:
title: "Character Counter"
placeholder: "Enter your text here..."