@celestia-island/hikari 0.47.0 → 0.47.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
/* Alternative sign-in methods as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
/* Alternative sign-in methods as CENTERED INDEPENDENT framed icon tiles
|
|
2
|
+
* (login card). The block container `.s-auth-methods` and its spacing are
|
|
3
|
+
* owned by HkAuthCard's slot styles (styles/admin-tokens.scss); this file
|
|
4
|
+
* only styles the divider and the tile row the component renders into that
|
|
5
|
+
* slot.
|
|
6
|
+
*
|
|
7
|
+
* Deliberately NOT a button group (2026-09-14 user direction): no shared
|
|
8
|
+
* track, no shared frame — each provider carries its own border so the row
|
|
9
|
+
* stays as wide as its content, centered in the card. */
|
|
6
10
|
|
|
7
11
|
/* "Other ways to sign in" divider row between rules. */
|
|
8
12
|
.s-auth-methods-divider {
|
|
@@ -24,16 +28,91 @@
|
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
/* The component's own wrapper is `display: contents` — layout-wise the
|
|
27
|
-
divider and the
|
|
31
|
+
divider and the tile row remain direct children of HkAuthCard's
|
|
28
32
|
`.s-auth-methods` slot container. */
|
|
29
33
|
.s-auth-methods-list {
|
|
30
34
|
display: contents;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
/* The
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
/* The tile row: fill the card column and center the independent tiles
|
|
38
|
+
inside it. The gap between tiles is the INDEPENDENT-action spacing
|
|
39
|
+
(--space-12) — comfortable air, unlike a group's shoulder-to-shoulder
|
|
40
|
+
track gap. */
|
|
41
|
+
.s-auth-methods .s-auth-methods-tiles {
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-wrap: wrap;
|
|
44
|
+
align-items: center;
|
|
45
|
+
justify-content: center;
|
|
46
|
+
gap: var(--space-12, 0.75rem);
|
|
37
47
|
width: 100%;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* The HkTooltip wrapper span is the flex child between tiles — let it
|
|
51
|
+
shrink to the button so the row gap (not the wrapper) spaces them. */
|
|
52
|
+
.s-auth-methods .s-auth-methods-tiles > .hk-tooltip-wrapper {
|
|
53
|
+
display: inline-flex;
|
|
54
|
+
flex: none;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* One provider tile: a framed 44px square (the login-chooser tile size,
|
|
58
|
+
same as the icon group's md item). Each tile owns its whole frame —
|
|
59
|
+
border, hover wash, focus ring — with no track around the row. */
|
|
60
|
+
.s-auth-methods-tile {
|
|
61
|
+
display: inline-flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
justify-content: center;
|
|
64
|
+
width: 2.75rem;
|
|
65
|
+
height: 2.75rem;
|
|
66
|
+
padding: 0;
|
|
67
|
+
font: inherit;
|
|
68
|
+
color: rgb(var(--color-text, 25 30 40));
|
|
69
|
+
background: rgb(var(--color-surface, 255 255 255) / 55%);
|
|
70
|
+
border: 1px solid rgb(var(--color-border, 222 224 228) / 55%);
|
|
71
|
+
border-radius: var(--radius-md, 8px);
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
outline: none;
|
|
74
|
+
transition-property: background-color, border-color, color, box-shadow, opacity, transform;
|
|
75
|
+
transition-duration: var(--duration-short, 0.15s);
|
|
76
|
+
transition-timing-function: ease;
|
|
77
|
+
|
|
78
|
+
/* A real <button disabled> still matches :hover — guard the dead ones. */
|
|
79
|
+
&:hover:not(:disabled) {
|
|
80
|
+
color: rgb(var(--color-text, 25 30 40));
|
|
81
|
+
background: var(--c-primary-faint, rgb(var(--color-primary, 90 140 255) / 5%));
|
|
82
|
+
border-color: var(--c-primary-medium, rgb(var(--color-primary, 90 140 255) / 25%));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Tactile press feedback — the button-family cadence. */
|
|
86
|
+
&:active:not(:disabled) {
|
|
87
|
+
transform: scale(0.96);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Outset double ring, same grammar as .hk-btn. */
|
|
91
|
+
&:focus-visible {
|
|
92
|
+
box-shadow:
|
|
93
|
+
0 0 0 2px rgb(var(--color-surface, 255 255 255)),
|
|
94
|
+
0 0 0 4px rgb(var(--color-primary, 90 140 255));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
&:disabled {
|
|
98
|
+
opacity: 0.5;
|
|
99
|
+
cursor: not-allowed;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.s-auth-methods-tile-icon {
|
|
104
|
+
display: inline-flex;
|
|
105
|
+
align-items: center;
|
|
38
106
|
justify-content: center;
|
|
107
|
+
line-height: 0;
|
|
108
|
+
/* Brand marks (svg/img vnodes) at their intrinsic size; lucide comps
|
|
109
|
+
sized by the host. The box never grows past the square. */
|
|
110
|
+
max-width: 100%;
|
|
111
|
+
max-height: 100%;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.s-auth-methods-tile-initial {
|
|
115
|
+
font-size: var(--text-sm, 0.8125rem);
|
|
116
|
+
font-weight: 600;
|
|
117
|
+
line-height: 1;
|
|
39
118
|
}
|
|
@@ -28,27 +28,39 @@ const methods = [
|
|
|
28
28
|
];
|
|
29
29
|
|
|
30
30
|
describe("HkAuthMethodList", () => {
|
|
31
|
-
it("renders
|
|
31
|
+
it("renders centered independent framed tiles with an optional divider", () => {
|
|
32
32
|
// The card's `methods` slot owns the `.s-auth-methods` container —
|
|
33
|
-
// reproduce that context here (the divider and the
|
|
33
|
+
// reproduce that context here (the divider and the tile row are layout
|
|
34
34
|
// children of it through the component's display:contents wrapper).
|
|
35
35
|
const c = mount(
|
|
36
36
|
h("div", { class: "s-auth-methods" }, h(HkAuthMethodList, { divider: "其他方式登录", methods })),
|
|
37
37
|
);
|
|
38
38
|
const divider = c.querySelector(".s-auth-methods-divider");
|
|
39
39
|
expect(divider?.textContent).toContain("其他方式登录");
|
|
40
|
-
const
|
|
41
|
-
expect(
|
|
42
|
-
expect(group!.classList.contains("hk-icon-group-buttons")).toBe(true);
|
|
43
|
-
const buttons = c.querySelectorAll<HTMLButtonElement>(".s-auth-methods .hk-icon-group-item");
|
|
44
|
-
expect(buttons.length).toBe(2);
|
|
40
|
+
const tiles = c.querySelectorAll<HTMLButtonElement>(".s-auth-methods .s-auth-methods-tile");
|
|
41
|
+
expect(tiles.length).toBe(2);
|
|
45
42
|
// Icon-only: the label is the accessible name + tooltip, never visible text.
|
|
46
|
-
expect(
|
|
47
|
-
expect(
|
|
48
|
-
expect(
|
|
43
|
+
expect(tiles[0]!.getAttribute("aria-label")).toBe("GitHub");
|
|
44
|
+
expect(tiles[1]!.getAttribute("aria-label")).toBe("LinuxDo");
|
|
45
|
+
expect(tiles[0]!.textContent).not.toContain("GitHub");
|
|
49
46
|
});
|
|
50
47
|
|
|
51
|
-
it("wraps
|
|
48
|
+
it("never wraps the providers in a button-group track", () => {
|
|
49
|
+
// 2026-09-14 user direction: a shared group frame (one wide bordered
|
|
50
|
+
// track with the icons floating inside) is NOT this component's
|
|
51
|
+
// grammar — every provider is an independent framed tile. A regression
|
|
52
|
+
// back to HkIconButtonGroup (or any .hk-icon-group markup) must fail
|
|
53
|
+
// here, because the button group is reserved for selectors and tight
|
|
54
|
+
// action strips.
|
|
55
|
+
const c = mount(
|
|
56
|
+
h("div", { class: "s-auth-methods" }, h(HkAuthMethodList, { methods })),
|
|
57
|
+
);
|
|
58
|
+
expect(c.querySelector(".s-auth-methods .hk-icon-group")).toBeNull();
|
|
59
|
+
expect(c.querySelector(".s-auth-methods [role='group']")).toBeNull();
|
|
60
|
+
expect(c.querySelector(".s-auth-methods [role='radiogroup']")).toBeNull();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("wraps every tile in a tooltip carrying the provider label", () => {
|
|
52
64
|
const c = mount(
|
|
53
65
|
h("div", { class: "s-auth-methods" }, h(HkAuthMethodList, { methods })),
|
|
54
66
|
);
|
|
@@ -71,12 +83,12 @@ describe("HkAuthMethodList", () => {
|
|
|
71
83
|
},
|
|
72
84
|
})),
|
|
73
85
|
);
|
|
74
|
-
const
|
|
75
|
-
|
|
86
|
+
const tiles = c.querySelectorAll<HTMLButtonElement>(".s-auth-methods .s-auth-methods-tile");
|
|
87
|
+
tiles[1]!.click();
|
|
76
88
|
await nextTick();
|
|
77
89
|
// Both rows are asserted so a constant-literal emit cannot ride an
|
|
78
90
|
// accidental fixture coincidence.
|
|
79
|
-
|
|
91
|
+
tiles[0]!.click();
|
|
80
92
|
await nextTick();
|
|
81
93
|
expect(picked).toEqual(["linuxdo", "github"]);
|
|
82
94
|
});
|
|
@@ -95,15 +107,15 @@ describe("HkAuthMethodList", () => {
|
|
|
95
107
|
},
|
|
96
108
|
})),
|
|
97
109
|
);
|
|
98
|
-
const
|
|
99
|
-
expect(
|
|
100
|
-
// The prebuilt brand vnode renders inside the icon
|
|
101
|
-
expect(
|
|
110
|
+
const tiles = c.querySelectorAll<HTMLButtonElement>(".s-auth-methods .s-auth-methods-tile");
|
|
111
|
+
expect(tiles.length).toBe(3);
|
|
112
|
+
// The prebuilt brand vnode renders inside the icon box.
|
|
113
|
+
expect(tiles[0]!.querySelector(".brand-gh")).toBeTruthy();
|
|
102
114
|
// A missing icon falls back to the label initial (never an empty box).
|
|
103
|
-
expect(
|
|
115
|
+
expect(tiles[2]!.querySelector<HTMLElement>(".s-auth-methods-tile-initial")?.textContent).toBe("F");
|
|
104
116
|
// Disabled entries render dead and swallow clicks.
|
|
105
|
-
expect(
|
|
106
|
-
|
|
117
|
+
expect(tiles[1]!.disabled).toBe(true);
|
|
118
|
+
tiles[1]!.click();
|
|
107
119
|
await nextTick();
|
|
108
120
|
expect(picked).toBe("");
|
|
109
121
|
});
|
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
import { defineComponent } from "vue";
|
|
2
|
-
import
|
|
2
|
+
import HkTooltip from "./HkTooltip";
|
|
3
3
|
import "./HkAuthMethodList.scss";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* HkAuthMethodList — the auth card's third-party sign-in block (the
|
|
7
7
|
* "other ways to sign in" row under the credentials form).
|
|
8
8
|
*
|
|
9
|
-
* Since 2026-09-
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* (
|
|
16
|
-
* deployment offers several providers. The `select` event carries the
|
|
17
|
-
* provider `key` exactly as before, so consumers swap rendering
|
|
18
|
-
* without touching their OAuth flow.
|
|
9
|
+
* Since 2026-09-14 (user direction) the block renders as CENTERED
|
|
10
|
+
* INDEPENDENT icon tiles: every provider is its own framed 44px icon
|
|
11
|
+
* button with its own tooltip — NOT a button group. A group track (one
|
|
12
|
+
* shared frame stretched across the card with the icons floating in
|
|
13
|
+
* the middle) reads as an oversized empty box; the button-group
|
|
14
|
+
* component is reserved for its two fundamental jobs — a selector
|
|
15
|
+
* (single/multiple) and a tight action strip (see HkIconButtonGroup).
|
|
19
16
|
*
|
|
20
|
-
*
|
|
17
|
+
* Provider identity is revealed on hover: each tile wraps itself in an
|
|
18
|
+
* HkTooltip carrying the provider label, which matches the platform
|
|
19
|
+
* login-chooser pattern (Windows/macOS account tiles) and keeps the
|
|
20
|
+
* card compact when a deployment offers several providers. The
|
|
21
|
+
* `select` event carries the provider `key` exactly as before, so
|
|
22
|
+
* consumers swap rendering without touching their OAuth flow.
|
|
23
|
+
*
|
|
24
|
+
* The wrapper stays `display: contents`: the divider and the tile row
|
|
21
25
|
* remain layout children of HkAuthCard's `.s-auth-methods` slot
|
|
22
26
|
* container (it owns the side padding and vertical rhythm).
|
|
23
27
|
*/
|
|
@@ -35,11 +39,11 @@ export default defineComponent({
|
|
|
35
39
|
/** Accessible name + tooltip text (the hover reveal). */
|
|
36
40
|
label: string;
|
|
37
41
|
/** Prebuilt icon vnode (brand SVG, <img>, …) rendered inside the
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
42
|
+
* framed tile. Typed loose on purpose (same as HkIconButtonGroup
|
|
43
|
+
* options): hosts materialize hikari against their own vue store,
|
|
44
|
+
* and a hard VNode type breaks typecheck whenever the host's vue
|
|
45
|
+
* minor differs. A missing icon falls back to the label's
|
|
46
|
+
* initial. */
|
|
43
47
|
icon?: unknown;
|
|
44
48
|
disabled?: boolean;
|
|
45
49
|
}>,
|
|
@@ -47,7 +51,7 @@ export default defineComponent({
|
|
|
47
51
|
},
|
|
48
52
|
},
|
|
49
53
|
emits: {
|
|
50
|
-
/** A provider
|
|
54
|
+
/** A provider tile was clicked. */
|
|
51
55
|
select: (_key: string) => true,
|
|
52
56
|
},
|
|
53
57
|
setup(props, { emit }) {
|
|
@@ -58,18 +62,42 @@ export default defineComponent({
|
|
|
58
62
|
<span>{props.divider}</span>
|
|
59
63
|
</div>
|
|
60
64
|
)}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
65
|
+
{/* A plain row, deliberately NOT role="group": every tile is an
|
|
66
|
+
independent action (its own frame, its own tooltip), and the
|
|
67
|
+
divider above names the section for sighted users. Group
|
|
68
|
+
semantics belong to the selector/tight-strip components. */}
|
|
69
|
+
<div class="s-auth-methods-tiles">
|
|
70
|
+
{props.methods.map((method) => {
|
|
71
|
+
const tile = (
|
|
72
|
+
<button
|
|
73
|
+
key={method.key}
|
|
74
|
+
type="button"
|
|
75
|
+
class="s-auth-methods-tile"
|
|
76
|
+
data-key={method.key}
|
|
77
|
+
aria-label={method.label}
|
|
78
|
+
title={undefined} // the HkTooltip popup owns the hover text
|
|
79
|
+
disabled={method.disabled === true}
|
|
80
|
+
onClick={() => emit("select", method.key)}
|
|
81
|
+
>
|
|
82
|
+
<span class="s-auth-methods-tile-icon" aria-hidden="true">
|
|
83
|
+
{method.icon != null
|
|
84
|
+
? method.icon
|
|
85
|
+
: (
|
|
86
|
+
<span class="s-auth-methods-tile-initial">
|
|
87
|
+
{method.label.charAt(0).toUpperCase()}
|
|
88
|
+
</span>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
</span>
|
|
92
|
+
</button>
|
|
93
|
+
);
|
|
94
|
+
return (
|
|
95
|
+
<HkTooltip key={method.key} text={method.label} placement="top" delay={300}>
|
|
96
|
+
{tile}
|
|
97
|
+
</HkTooltip>
|
|
98
|
+
);
|
|
99
|
+
})}
|
|
100
|
+
</div>
|
|
73
101
|
</div>
|
|
74
102
|
);
|
|
75
103
|
},
|
|
@@ -25,13 +25,21 @@ export interface HkIconButtonGroupOption {
|
|
|
25
25
|
* HkIconButtonGroup — the icon-only variant of the button-group family
|
|
26
26
|
* (the same centered strip grammar as HkTabs, minus the text).
|
|
27
27
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* - "single"
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
28
|
+
* The group has exactly TWO fundamental jobs (2026-09-14 user direction
|
|
29
|
+
* — a group is a semantic component, not a generic box to throw buttons
|
|
30
|
+
* into), carried by three working modes:
|
|
31
|
+
* - a SELECTOR: "single" (radiogroup: one active key via v-model,
|
|
32
|
+
* re-clicking the active key is a no-op — radio semantics) or
|
|
33
|
+
* "multiple" (toggle set: v-model is a string[] of active keys,
|
|
34
|
+
* every item carries aria-pressed)
|
|
35
|
+
* - a TIGHT ACTION STRIP: "buttons" (no selection state) — related
|
|
36
|
+
* actions packed shoulder-to-shoulder in ONE shared track, the
|
|
37
|
+
* toolbar grammar
|
|
38
|
+
*
|
|
39
|
+
* Anything else is NOT a group: independent actions that merely live on
|
|
40
|
+
* the same row (e.g. a login card's third-party provider tiles) render
|
|
41
|
+
* as separate framed buttons with comfortable spacing — see
|
|
42
|
+
* HkAuthMethodList for the reference pattern.
|
|
35
43
|
*
|
|
36
44
|
* Every item is icon-only and rides one size taller than a standard
|
|
37
45
|
* icon button (md = 44px vs the 32px icon button / 40px text button) —
|